Laravel

Stéphane Hulard

Consultant, Formateur, Contributeur.

Laravel ?

Un peu d'histoire…

2011: Premier commit,
2013: v4.0.0,
2015: v5.0.0,
2019: v5.9.0 ?

Des racines communes…

… pour les habitués à Symfony 😊

Une surcouche spécifique !

Et une documentation très accessible…

Tout est dans la boite

▶ composer.json
▶ .env
▶ ORM (ActiveRecord) + Migrations
▶ CLI
▶ Templates
▶ Intégration frontend (Elixir)

Un peu de contexte

                            
# Création d'un projet vierge
composer create-project laravel/laravel
                            
                            
# Démarrage du serveur web intégré à PHP
./artisan serve

# Homepage: http://localhost:8000
                            
                        

Arborescence

                                
├── app
│   ├── Console
│   │   └── Kernel.php
│   ├── Exceptions
│   ├── Http
│   │   ├── Controllers
│   │   ├── Middleware
│   │   └── Kernel.php
│   ├── Providers
│   └── User.php
├── bootstrap
                                
                            
                                
├── config
│   ├── app.php
│   └── …
├── database
│   ├── migations
│   ├── seeds
├── routes
│   ├── api.php
│   ├── web.php
│   └── …
├── public
├── resources
└── storages
                                
                            

Une première version

                            //routes/api.php
Route::post('/upload', function() {
    request('image')->store('images');
});
                        

Facades 🤔

                            Route::post('/upload', function() {
    ...
});
                        

Helpers 😱

                            /** @var UploadedFile */
request('image')->store('images');
                        

… not so bad !

Injection

                            use Illuminate\Http\Request;

Route::post('/upload', function(Request $request) {
    $request
        ->file('image')
        ->store('images');
});
                            
                        

Validation

                            Route::post('/upload', function(Request $request) {
    $request->validate([
        'image' => 'required|image'
    ]);

    $request->file('image')->store('images');
});
                            
                        

Contrats

                            use Illuminate\Contracts\Filesystem\Filesystem;

Route::post(
    '/upload',
    function(Request $request, Filesystem $filesystem) {
        //[...] Validation

        $file = $request->file('image');
        $filesystem->put(
            sprintf('images/%s', $file->hashName()),
            $file
        );
    }
);
                            
                        

Separation of concerns

Chaque chose à sa place…

…Contrôleur, Route, Service.

Contrôleur

                                namespace App\Http\Controllers\Api\User;

class UploadProfilePicture
{
    private $filesystem;

    public function __construct(Filesystem $filesystem) {
        $this->filesystem = $filesystem;
    }

    public function __invoke(Request $request) {
        // Action…
    }
}
                                
                            
                                namespace App\Http\Controllers\Api\User;

class UploadProfilePicture
{
    private $filesystem;

    public function __construct(Filesystem $filesystem) {
        $this->filesystem = $filesystem;
    }

    public function __invoke(Request $request) {
        // Action…
    }
}
                                
                            
                                namespace App\Http\Controllers\Api\User;

class UploadProfilePicture
{
    private $filesystem;

    public function __construct(Filesystem $filesystem) {
        $this->filesystem = $filesystem;
    }

    public function __invoke(Request $request) {
        // Action…
    }
}
                                
                            

Route

                            Route::post(
    '/upload',
    'Api\User\UploadProfilePicture'
);
                            
                        

Services et conteneur

Les services sont des objets…

… construits et gérés par le conteneur …

… injectés à l'exécution.

Définition AppServiceProvider::boot

                                use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Foundation\Application;

$this->app->bind(
    Filesystem::class,
    function(Application $app) {
        return $app
            ->make(FilesystemManager::class)
            ->disk('local');
    }
);
                                
                            
                                use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Foundation\Application;

$this->app->bind(
    Filesystem::class,
    function(Application $app) {
        return $app
            ->make(FilesystemManager::class)
            ->disk('local');
    }
);
                                
                            
                                use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Filesystem\FilesystemManager;
use Illuminate\Foundation\Application;

$this->app->bind(
    Filesystem::class,
    function(Application $app) {
        return $app
            ->make(FilesystemManager::class)
            ->disk('local');
    }
);
                                
                            

Contextual Binding

                                ...
use App\Http\Controllers\Api\User;

$this->app
     ->when(User\UploadProfilePicture::class)
     ->needs(Filesystem::class),
     ->give(function(Application $app) {
        return $app
            ->make(FilesystemManager::class)
            ->disk('local');
     });
                                
                            
                                ...
use App\Http\Controllers\Api\User;

$this->app
     ->when(User\UploadProfilePicture::class)
     ->needs(Filesystem::class),
     ->give(function(Application $app) {
        return $app
            ->make(FilesystemManager::class)
            ->disk('local');
     });
                                
                            
                                ...
use App\Http\Controllers\Api\User;

$this->app
     ->when(User\UploadProfilePicture::class)
     ->needs(Filesystem::class),
     ->give(function(Application $app) {
        return $app
            ->make(FilesystemManager::class)
            ->disk('local');
     });
                                
                            
                                ...
use App\Http\Controllers\Api\User;

$this->app
     ->when(User\UploadProfilePicture::class)
     ->needs(Filesystem::class),
     ->give(function(Application $app) {
        return $app
            ->make(FilesystemManager::class)
            ->disk('local');
     });
                                
                            

Valider en amont de l'action

Arrêter le processus le plus tôt possible…

Middlewares et FormRequest.

Middlewares ?

Centraliser les comportements…

FormRequest

./artisan make:request UploadUserProfile
                                namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\UploadedFile;

class UploadUserProfile extends FormRequest
{
    public function rules() {
        return ['image' => 'required|image'];
    }

    public function image(): UploadedFile {
        return $this->validated()['image'];
    }
}
                                
                            
                                namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\UploadedFile;

class UploadUserProfile extends FormRequest
{
    public function rules() {
        return ['image' => 'required|image'];
    }

    public function image(): UploadedFile {
        return $this->validated()['image'];
    }
}
                                
                            
                                namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Http\UploadedFile;

class UploadUserProfile extends FormRequest
{
    public function rules() {
        return ['image' => 'required|image'];
    }

    public function image(): UploadedFile {
        return $this->validated()['image'];
    }
}
                                
                            

Contrôleur

                                namespace App\Http\Controllers\Api\User;

use App\Http\Requests\UploadUserProfile;

class UploadProfilePicture
{
    public function __construct(Filesystem $filesystem)
    {
        $this->filesystem = $filesystem;
    }

    public function __invoke(UploadUserProfile $request)
    {
        $image = $request->image();
        $this->filesystem->put('images', $image);
    }
}
                                
                            
                                namespace App\Http\Controllers\Api\User;

use App\Http\Requests\UploadUserProfile;

class UploadProfilePicture
{
    public function __construct(Filesystem $filesystem)
    {
        $this->filesystem = $filesystem;
    }

    public function __invoke(UploadUserProfile $request)
    {
        $image = $request->image();
        $this->filesystem->put('images', $image);
    }
}
                                
                            

🧁 Cerise sur le gâteau !

Vous pouvez désactiver les facades !

                                /* config/app.php */
[
    /*
    |--------------------------------------------------------------------------
    | Class Aliases
    |--------------------------------------------------------------------------
    |
    | This array of class aliases will be registered when this application
    | is started. However, feel free to register as many as you wish as
    | the aliases are "lazy" loaded so they don't hinder performance.
    |
    */

    'aliases' => [
        …
    ],
]
                                
                            

Et voilà !

Une micro application,
Testable, maintenable,
Et surtout pas de magie !

Au final…

Laravel est un outil et seulement un outil …
Très rapide pour prototyper …
Très riche en fonctionnalités par défaut …
Mais pas adapté à tout les cas !

Attention à…

Ne pas prendre tout les raccourcis possible…
Prendre le temps d'apprendre…
Comprendre l'architecture de votre code.

https://joind.in/talk/a77e5