Controller

Introduzione

Invece di definire tutta la logica per gestire le richieste come closure nei file delle rotte, potresti preferire organizzare questo comportamento utilizzando classi "controller". I controller possono raggruppare logiche di gestione delle richieste correlate in un’unica classe. Ad esempio, una classe UserController potrebbe gestire tutte le richieste relative agli utenti, come mostrare, creare, aggiornare e cancellare utenti. Per impostazione predefinita, i controller sono memorizzati nella directory app/Http/Controllers.

Scrivere i Controller

Controller di Base

Per creare rapidamente un nuovo controller, puoi eseguire il comando Artisan make:controller. Di default, tutti i controller della tua applicazione sono memorizzati nella directory app/Http/Controllers:

php artisan make:controller UserController

Diamo un’occhiata a un esempio di controller di base. Un controller può avere diversi metodi pubblici che rispondono alle richieste HTTP in arrivo:

    <?php

    namespace App\Http\Controllers;

    use App\Models\User;
    use Illuminate\View\View;

    class UserController extends Controller
    {
        /**
         * Mostra il profilo di un determinato utente.
         */
        public function show(string $id): View
        {
            return view('user.profile', [
                'user' => User::findOrFail($id)
            ]);
        }
    }

Una volta scritta una classe controller e un metodo, puoi definire una rotta verso il metodo del controller in questo modo:

    use App\Http\Controllers\UserController;

    Route::get('/user/{id}', [UserController::class, 'show']);

Quando una richiesta in arrivo corrisponde all’URI della rotta specificata, verrà invocato il metodo show della classe App\Http\Controllers\UserController e i parametri della rotta saranno passati al metodo.

I controller non sono obbligatoriamente estesi da una classe base. Tuttavia, a volte è comodo estendere una classe controller base che contiene metodi da condividere tra tutti i tuoi controller.

Single Action Controllers

Se un’azione del controller è particolarmente complessa, potresti trovare comodo dedicare un’intera classe controller a quella singola azione. Per fare ciò, puoi definire un unico metodo __invoke all’interno del controller:

<?php

namespace App\Http\Controllers;

class ProvisionServer extends Controller
{
    /**
     * Provision a new web server.
     */
    public function __invoke()
    {
        // ...
    }
}

Quando registri le rotte per controller a singola azione, non è necessario specificare un metodo del controller. Invece, puoi semplicemente passare il nome del controller al router:

use App\Http\Controllers\ProvisionServer;

Route::post('/server', ProvisionServer::class);

Puoi generare un controller invocabile utilizzando l’opzione --invokable del comando make:controller di Artisan:

php artisan make:controller ProvisionServer --invokable

I template dei controller possono essere personalizzati utilizzando la pubblicazione degli stub.

Middleware del Controller

Puoi assegnare uno o più middleware alle rotte del controller nei tuoi file di route:

    Route::get('/profile', [UserController::class, 'show'])->middleware('auth');

Oppure, potresti trovare comodo specificare il middleware all’interno della classe del tuo controller. Per farlo, il tuo controller dovrebbe implementare l’interfaccia HasMiddleware, che richiede che il controller abbia un metodo statico middleware. Da questo metodo, puoi restituire un array di middleware che dovrebbero essere applicati alle azioni del controller:

    <?php

    namespace App\Http\Controllers;

    use App\Http\Controllers\Controller;
    use Illuminate\Routing\Controllers\HasMiddleware;
    use Illuminate\Routing\Controllers\Middleware;

    class UserController extends Controller implements HasMiddleware
    {
        /**
         * Ottieni il middleware che dovrebbe essere assegnato al controller.
         */
        public static function middleware(): array
        {
            return [
                'auth',
                new Middleware('log', only: ['index']),
                new Middleware('subscribed', except: ['store']),
            ];
        }

        // ...
    }

Puoi anche definire il middleware del controller come closure, che fornisce un modo comodo per definire un middleware inline senza scrivere un’intera classe middleware:

    use Closure;
    use Illuminate\Http\Request;

    /**
     * Ottieni il middleware che dovrebbe essere assegnato al controller.
     */
    public static function middleware(): array
    {
        return [
            function (Request $request, Closure $next) {
                return $next($request);
            },
        ];
    }

Controller per una Risorsa

Se consideri ogni modello Eloquent nella tua applicazione come una "risorsa", è comune eseguire lo stesso set di azioni su ogni risorsa. Ad esempio, immagina che la tua applicazione contenga un modello Photo e un modello Movie. È probabile che gli utenti possano creare, leggere, aggiornare o eliminare queste risorse.

Per questo caso d’uso comune, il routing delle risorse di Laravel assegna le tipiche rotte di creazione, lettura, aggiornamento ed eliminazione ("CRUD") a un controller con una sola riga di codice. Per iniziare, possiamo usare l’opzione --resource del comando Artisan make:controller per creare rapidamente un controller che gestisca queste azioni:

php artisan make:controller PhotoController --resource

Questo comando genererà un controller in app/Http/Controllers/PhotoController.php. Il controller conterrà un metodo per ciascuna delle operazioni di risorsa disponibili. Successivamente, puoi registrare una rotta di risorsa che punta al controller:

    use App\Http\Controllers\PhotoController;

    Route::resource('photos', PhotoController::class);

Questa singola dichiarazione di rotta crea più rotte per gestire diverse azioni sulla risorsa. Il controller generato avrà già i metodi scheletro per ciascuna di queste azioni. Ricorda, puoi sempre ottenere una rapida panoramica delle rotte della tua applicazione eseguendo il comando Artisan route:list.

Puoi anche registrare molti controller di risorsa contemporaneamente passando un array al metodo resources:

    Route::resources([
        'photos' => PhotoController::class,
        'posts' => PostController::class,
    ]);

Azioni gestite dai controller delle risorse

Verbo URI Azione Nome Rotta
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

Personalizzare il Comportamento per un Model Mancante

Normalmente, viene generata una risposta HTTP 404 se non viene trovato un modello di risorsa con binding implicito. Tuttavia, puoi personalizzare questo comportamento chiamando il metodo missing quando definisci una rotta per risorsa. Il metodo missing accetta una closure che verrà eseguita se non si trova un modello legato implicitamente per una qualsiasi delle rotte della risorsa:

use App\Http\Controllers\PhotoController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;

Route::resource('photos', PhotoController::class)
        ->missing(function (Request $request) {
            return Redirect::route('photos.index');
        });

Modelli Soft Deleted

Normalmente, il binding implicito dei modelli non recupera i modelli che sono stati soft deleted e restituirà invece una risposta HTTP 404. Tuttavia, puoi indicare al framework di permettere i modelli soft deleted utilizzando il metodo withTrashed quando definisci la route della risorsa:

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->withTrashed();

Chiamare withTrashed senza argomenti permetterà i modelli soft deleted per le route della risorsa show, edit e update. Puoi specificare un sottoinsieme di queste route passando un array al metodo withTrashed:

Route::resource('photos', PhotoController::class)->withTrashed(['show']);

Specificare il Modello della Risorsa

Se stai usando il route model binding e desideri che i metodi del controller delle risorse indichino un’istanza del modello, puoi usare l’opzione --model quando generi il controller:

php artisan make:controller PhotoController --model=Photo --resource

Generazione delle Form Request

Puoi usare l’opzione --requests quando crei un controller di tipo resource per far sì che Artisan generi le classi di form request per i metodi di storage e update del controller:

php artisan make:controller PhotoController --model=Photo --resource --requests

Rotte di Risorse Parziali

Quando si dichiara una rotta di risorsa, è possibile specificare un sottoinsieme di azioni che il controller dovrebbe gestire invece dell’intero insieme delle azioni predefinite:

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->only([
    'index', 'show'
]);

Route::resource('photos', PhotoController::class)->except([
    'create', 'store', 'update', 'destroy'
]);

Rotte per Risorse – API

Quando dichiari rotte risorsa destinate ad essere consumate da API, solitamente vorrai escludere rotte che presentano template HTML come create ed edit. Per comodità, puoi usare il metodo apiResource per escludere automaticamente queste due rotte:

use App\Http\Controllers\PhotoController;

Route::apiResource('photos', PhotoController::class);

Puoi registrare molti controller risorsa API contemporaneamente passando un array al metodo apiResources:

use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;

Route::apiResources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);

Per generare rapidamente un controller risorsa API che non includa i metodi create o edit, usa l’opzione --api quando esegui il comando make:controller:

php artisan make:controller PhotoController --api

Risorse Annidate

A volte potresti aver bisogno di definire delle rotte per una risorsa annidata. Per esempio, una risorsa foto può avere più commenti associati. Per annidare i controller delle risorse, puoi usare la notazione a "punto" nella dichiarazione della rotta:

use App\Http\Controllers\PhotoCommentController;

Route::resource('photos.comments', PhotoCommentController::class);

Questa rotta registrerà una risorsa annidata che potrà essere accessibile tramite URI come le seguenti:

/photos/{photo}/comments/{comment}

Limitazione delle Risorse Annidate

La funzionalità di implicit model binding di Laravel può automaticamente limitare i binding annidati in modo che il modello figlio risolto appartenga al modello padre. Utilizzando il metodo scoped quando definisci la tua risorsa annidata, puoi abilitare la limitazione automatica e indicare a Laravel quale campo utilizzare per recuperare la risorsa figlio.

Annidamento Superficiale

Spesso non è del tutto necessario avere sia gli ID del genitore che quelli del figlio all’interno di un URI, poiché l’ID del figlio è già un identificatore univoco. Quando si utilizzano identificatori univoci come chiavi primarie auto-incrementanti per identificare i tuoi modelli nei segmenti URI, puoi scegliere di usare l’"annidamento superficiale":

    use App\Http\Controllers\CommentController;

    Route::resource('photos.comments', CommentController::class)->shallow();

Questa definizione di route definirà le seguenti rotte:

Verb URI Action Route Name
GET /photos/{photo}/comments index photos.comments.index
GET /photos/{photo}/comments/create create photos.comments.create
POST /photos/{photo}/comments store photos.comments.store
GET /comments/{comment} show comments.show
GET /comments/{comment}/edit edit comments.edit
PUT/PATCH /comments/{comment} update comments.update
DELETE /comments/{comment} destroy comments.destroy

Assegnare Nomi alle Resource Route

Per impostazione predefinita, tutte le azioni del controller delle risorse hanno un nome di route; tuttavia, puoi sovrascrivere questi nomi passando un array names con i nomi di route desiderati:

use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->names([
    'create' => 'photos.build'
]);

Nominare i Parametri delle Route delle Risorse

Per impostazione predefinita, Route::resource creerà i parametri delle route per le tue risorse basandosi sulla versione "singolare" del nome della risorsa. Puoi facilmente sovrascriverlo per ogni singola risorsa usando il metodo parameters. L’array passato al metodo parameters dovrebbe essere un array associativo di nomi di risorse e nomi dei parametri:

    use App\Http\Controllers\AdminUserController;

    Route::resource('users', AdminUserController::class)->parameters([
        'users' => 'admin_user'
    ]);

L’esempio sopra genera il seguente URI per la route show della risorsa:

    /users/{admin_user}

Rotte delle Risorse con Scoping

La funzionalità di scoped implicit model binding di Laravel può automaticamente delimitare i binding annidati, assicurando che il modello figlio risolto appartenga al modello genitore. Usando il metodo scoped quando definisci la tua risorsa annidata, puoi abilitare lo scoping automatico e indicare a Laravel quale campo deve essere usato per recuperare la risorsa figlio:

use App\Http\Controllers\PhotoCommentController;

Route::resource('photos.comments', PhotoCommentController::class)->scoped([
    'comment' => 'slug',
]);

Questa rotta registrerà una risorsa annidata con scoping che può essere accessibile con URI come i seguenti:

/photos/{photo}/comments/{comment:slug}

Quando utilizzi un binding implicito personalizzato come parametro di rotta annidato, Laravel delimita automaticamente la query per recuperare il modello annidato dal genitore, seguendo le convenzioni per indovinare il nome della relazione sul genitore. In questo caso, si presumerà che il modello Photo abbia una relazione chiamata comments (il plurale del nome del parametro di rotta) che può essere usata per recuperare il modello Comment.

Localizzazione degli URI delle risorse

Per impostazione predefinita, Route::resource creerà URI delle risorse utilizzando verbi e regole di plurale in inglese. Se hai bisogno di localizzare i verbi delle azioni create ed edit, puoi usare il metodo Route::resourceVerbs. Questo può essere fatto all’inizio del metodo boot all’interno di App\Providers\AppServiceProvider della tua applicazione:

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Route::resourceVerbs([
            'create' => 'crea',
            'edit' => 'modifica',
        ]);
    }

Il pluralizzatore di Laravel supporta diverse lingue che puoi configurare in base alle tue esigenze. Una volta personalizzati i verbi e la lingua per la pluralizzazione, una registrazione della rotta risorsa come Route::resource('pubblicazione', PublicacionController::class) produrrà i seguenti URI:

    /pubblicazione/crea
    /pubblicazione/{pubblicazione}/modifica

Aggiungere Rotte ai Resource Controllers

Se hai bisogno di aggiungere rotte extra a un resource controller oltre al set predefinito di rotte, definisci queste rotte prima di chiamare il metodo Route::resource. Altrimenti, le rotte definite dal metodo resource potrebbero avere la precedenza sulle tue rotte aggiuntive:

use App\Http\Controller\PhotoController;

Route::get('/photos/popular', [PhotoController::class, 'popular']);
Route::resource('photos', PhotoController::class);

Ricorda di mantenere i tuoi controller focalizzati su un solo aspetto specifico della tua applicazione. Se ti accorgi di aver bisogno frequentemente di metodi al di fuori delle azioni standard della risorsa, considera di dividere il tuo controller in due controller più piccoli.

Controller di Risorse – Singleton

A volte, la tua applicazione avrà risorse che possono avere solo un’unica istanza. Ad esempio, il "profilo" di un utente può essere modificato o aggiornato, ma un utente potrebbe non avere più di un "profilo". Allo stesso modo, un’immagine può avere un singolo "thumbnail". Queste risorse sono chiamate "singleton resources", il che significa che può esistere una sola istanza della risorsa. In questi casi, puoi registrare un controller di risorse "singleton":

use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;

Route::singleton('profile', ProfileController::class);

La definizione di risorsa singleton sopra registrerà le rotte qui di seguito. Come puoi vedere, le rotte di "creazione" non sono registrate per le risorse singleton, e le rotte registrate non accettano un identificatore poiché può esistere solo un’istanza della risorsa:

Metodo URI Azione Nome Rotta
GET /profile show profile.show
GET /profile/edit edit profile.edit
PUT/PATCH /profile update profile.update

Le risorse singleton possono anche essere annidate all’interno di una risorsa standard:

Route::singleton('photos.thumbnail', ThumbnailController::class);

In questo esempio, la risorsa photos conterebbe tutte le rotte standard per una risorsa; tuttavia, la risorsa thumbnail sarebbe una risorsa singleton con le seguenti rotte:

Metodo URI Azione Nome Rotta
GET /photos/{photo}/thumbnail show photos.thumbnail.show
GET /photos/{photo}/thumbnail/edit edit photos.thumbnail.edit
PUT/PATCH /photos/{photo}/thumbnail update photos.thumbnail.update

Risorse Singleton – Creazione

A volte, potresti voler definire le rotte di creazione e memorizzazione per una risorsa singleton. Per farlo, puoi utilizzare il metodo creatable quando registri la rotta della risorsa singleton:

Route::singleton('photos.thumbnail', ThumbnailController::class)->creatable();

In questo esempio, verranno registrate le seguenti rotte. Come puoi vedere, una rotta DELETE sarà registrata anche per le risorse singleton creabili:

Verbo URI Azione Nome Rotta
GET /photos/{photo}/thumbnail/create create photos.thumbnail.create
POST /photos/{photo}/thumbnail store photos.thumbnail.store
GET /photos/{photo}/thumbnail show photos.thumbnail.show
GET /photos/{photo}/thumbnail/edit edit photos.thumbnail.edit
PUT/PATCH /photos/{photo}/thumbnail update photos.thumbnail.update
DELETE /photos/{photo}/thumbnail destroy photos.thumbnail.destroy

Se vuoi che Laravel registri la rotta DELETE per una risorsa singleton ma non registri le rotte di creazione o memorizzazione, puoi utilizzare il metodo destroyable:

Route::singleton(...)->destroyable();

Risorse Singleton – API

Il metodo apiSingleton può essere utilizzato per registrare una risorsa singleton che verrà gestita tramite un’API, rendendo quindi inutili le rotte create ed edit:

Route::apiSingleton('profile', ProfileController::class);

Naturalmente, le risorse API singleton possono anche essere creatable, il che registrerà le rotte store e destroy per la risorsa:

Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();

Iniezione delle Dipendenze e Controller

Iniezione nel Costruttore

Il service container di Laravel viene utilizzato per risolvere tutti i controller di Laravel. Di conseguenza, puoi usare il type-hint per qualsiasi dipendenza il tuo controller possa necessitare nel suo costruttore. Le dipendenze dichiarate saranno automaticamente risolte e iniettate nell’istanza del controller:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * Crea una nuova istanza del controller.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}

Injection dei Metodi

Oltre all’injection nel costruttore, puoi anche specificare il tipo delle dipendenze nei metodi del tuo controller. Un caso d’uso comune per l’injection dei metodi è iniettare l’istanza Illuminate\Http\Request nei metodi del tuo controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Salva un nuovo utente.
     */
    public function store(Request $request): RedirectResponse
    {
        $name = $request->name;

        // Salva l'utente...

        return redirect('/users');
    }
}

Se il metodo del tuo controller si aspetta anche input da un parametro di una route, elenca gli argomenti della route dopo le altre dipendenze. Per esempio, se la tua route è definita così:

use App\Http\Controllers\UserController;

Route::put('/user/{id}', [UserController::class, 'update']);

Puoi comunque specificare il tipo di Illuminate\Http\Request e accedere al parametro id definendo il metodo del tuo controller come segue:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * Aggiorna l'utente specificato.
     */
    public function update(Request $request, string $id): RedirectResponse
    {
        // Aggiorna l'utente...

        return redirect('/users');
    }
}
Lascia un commento

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *