Skip to content

Laravel conventions

Introduction

These conventions aim to standardize our Laravel development practices, focusing on:

  1. Reducing ambiguity
  2. Enhancing consistency
  3. Optimizing tool integration (IDEs, static analyzers, etc.)

Strategy

  1. Consistency is Key: When Laravel offers multiple approaches, we define a single, consistent method in these conventions.
  2. Performance and Scalability: Adopt patterns that enhance application performance and maintainability as it scales.
  3. Security-First Mindset: Incorporate security best practices throughout the development process.
  4. Leverage Laravel's Intentions: Maximize the use of native Laravel features and official packages. Deviate only with clear justification.

This guide assumes familiarity with the latest Laravel version and modern PHP development practices. It focuses on our specific conventions and rationales rather than explaining basic concepts.

Project Structure

For large projects (> 100 Models), use modules to separate the codebase into smaller parts. Use the standard Laravel structure for each module:

text
Modules/
    ...
    Course/
        Actions/
        Event/
        Exceptions/
        Console
            Commands/
        Http/
            Controllers/
                StoreCourseEnrollmentController.php
                StoreCourseEnrollmentRequest.php
            Middleware/
            Resources/
        Jobs/
        Listeners/
        Models/
        Policies/
        Rules/
        View/
        CourseServiceProvider.php
    ...
    ...other modules

Exceptions:

  1. Do not create a separate directory for Request classes: they are not reusable and often have the same reason for change as the Controller, so they should be in the same directory (Controllers).
  2. Do not create a separate directory for Providers classes: move them to the root of the module directory. ServiceProvide provide usually does job on module level and Module root is the best place for it. every module usually has a single provider and this it does not clutter the directory.

Modules are relatively independent parts of the application that can be developed and tested separately. Of course, modules should communicate with each other, but they should not depend on each other. Such communication possible using:

DI vs. Facades vs. Facade aliases vs. helper functions

DI and Facades SHOULD be used in PHP code, helpers SHOULD be used in Blade views.

The biggest advantage of using dependency injection is the explicit and clear contract of the class. Public methods indicate what tasks this class can perform. Constructor parameters specify what the class needs for these tasks. In large, long-lasting projects, this is essential. Classes can be easily tested and used in any conditions; you need to provide them with the necessary dependencies (yes, we know, Laravel Facades are easy to test too).

On the other hand, Laravel doesn't provide alternatives to some helpers and Facades. Also, it's hard for me to imagine, for example, HTTP controllers outside of a Laravel application. Therefore, it's quite appropriate to use helper functions and Laravel Facades in this context.

So, the general rule is to prefer DI, then Facade, then helpers.

Exceptions for helpers (ok to use):

  • config()
  • routing:
    • route()
    • url()
  • response (scope: in HTTP layer only [controllers and middlewares]):
    • abort()
    • response()
    • redirect()
    • view()
  • path helpers (\Illuminate\Contracts\Foundation\Application contact doesn't have these methods):
    • app_path()
    • base_path()
    • database_path()
    • lang_path()
    • public_path()
    • resource_path()
    • storage_path()
  • queue:
    • dispatch() (it's not the same as Bus::dispatch: dispatch() respects to UniqueLock, the Facade doesn't)
    • dispatch_sync() (for consistency with dispatch())
  • time:
    • now()
    • today()
  • localization:
    • __() (preferred)
    • trans()
    • trans_choice()

Facade root aliases

Don’t use Facade root aliases (comment out Facade::defaultAliases()->merge([...])): it’s extra magic that’s easy to avoid. Exceptions:

  • \Vite alias for \Illuminate\Support\Facades\Vite (why: there are no helper alternatives for Vite::asset() that is commonly used in Blade views)

Models

See separate file See separate file See separate file See separate file

Factories

See separate file

Migrations

See separate file

Seeders

See separate file

Artisan commands

See separate file

Controllers

See separate file

Requests

See separate file

Responses

See separate file

Routing and API Design

See separate file

Authorization

See separate file

Validation

See separate file

Exceptions

See separate file

Jobs

See separate file

Events

See separate file

Configs

See separate file

Tests

See separate file

Materials

  1. Matthias Noback: Recipes for Decoupling
  2. Adel Faizrakhmanov: Architecture of Complex Web Applications
  3. Spatie guidelines

🦄