Controllers

Introduzione ai Controller

Invece di definire tutta la logica di gestione delle richieste come Closure nei file di rotta, potresti voler organizzare questo comportamento utilizzando classi “Controller”. I controller possono raggruppare la logica di gestione delle richieste HTTP correlate in una singola classe. Ad esempio, una classe UserController potrebbe gestire tutte le richieste HTTP in entrata relative agli utenti, inclusa la visualizzazione, la creazione, l’aggiornamento e l’eliminazione degli utenti. Per impostazione predefinita, i controller sono memorizzati nella directory app/Http/Controllers.

Scrivere Controller

Controller di Base

Per iniziare a creare un controller, eseguiamo il comando Artisan make:controller. Questo comando creerà un nuovo file controller nella directory app/Http/Controllers della tua applicazione. Per creare un nuovo controller, esegui il seguente comando nel tuo terminale:

php artisan make:controller UserController

Questo comando genererà una classe controller simile alla seguente:

<?php

namespace App\Http\Controllers;

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

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

Ora puoi definire una rotta per questo metodo del controller in questo modo:

use App\Http\Controllers\UserController;

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

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

Controller & Namespaces

È molto importante notare che non abbiamo avuto bisogno di specificare lo namespace completo del controller quando abbiamo definito la rotta del controller. Poiché RouteServiceProvider carica i tuoi file di rotta all’interno di un gruppo di route che contiene lo namespace, abbiamo specificato solo la parte del nome della classe che viene dopo la porzione App\Http\Controllers dello namespace.

Se scegli di annidare o organizzare i tuoi controller utilizzando directory più specifiche all’interno della directory App\Http\Controllers, usa semplicemente il nome della classe specifico relativo allo namespace radice App\Http\Controllers. Quindi, se la tua classe controller completa è App\Http\Controllers\Photos\AdminController, dovresti registrare le rotte per il controller in questo modo:

Route::get('/photos/admin', [Photos\AdminController::class, 'index']);

Middleware del Controller

Il middleware può essere assegnato alle rotte del controller nei tuoi file di rotta:

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

Tuttavia, è più comodo specificare il middleware all’interno del costruttore del tuo controller. Usando il metodo middleware dal costruttore del tuo controller, puoi assegnare middleware alle azioni del controller. Puoi persino limitare il middleware a determinati metodi sulla classe controller:

class UserController extends Controller
{
    /**
     * Istanzia un nuovo controller.
     */
    public function __construct()
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->middleware('subscribed')->except('store');
    }
}

I controller ti consentono anche di registrare middleware usando una Closure. Questo fornisce un modo conveniente per definire un middleware per un singolo controller senza definire un’intera classe middleware:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

$this->middleware(function (Request $request, $next) {
    if (! Auth::user()->isAdmin()) {
        //...
    }

    return $next($request);
});

Controller Risorsa (Resource Controllers)

Se pensi a ciascun modello Eloquent nella tua applicazione come una “risorsa”, è tipico eseguire le stesse serie di azioni su ciascuna risorsa nella tua applicazione. 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.

A causa di questo caso d’uso comune, Laravel fornisce i controller risorsa che semplificano la creazione di logica CRUD (Create, Read, Update, Delete) attorno a una risorsa. Per creare un tale controller, possiamo eseguire il comando Artisan make:controller con l’opzione --resource:

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, dovresti registrare una rotta del controller risorsa:

use App\Http\Controllers\PhotoController;

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

Questa singola dichiarazione di rotta crea più rotte per gestire una varietà di azioni sulla risorsa. Il controller generato avrà già metodi stub per ciascuna di queste azioni. Ricorda, puoi sempre dare un’occhiata rapida alle rotte della tua applicazione eseguendo il comando Artisan route:list.

Puoi anche registrare più controller risorsa contemporaneamente passando un array al metodo resources:

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

La tabella seguente elenca le azioni gestite dal controller risorsa, i loro nomi e i loro URI:

VerboURIAzioneNome Rotta
GET/photosindexphotos.index
GET/photos/createcreatephotos.create
POST/photosstorephotos.store
GET/photos/{photo}showphotos.show
GET/photos/{photo}/editeditphotos.edit
PUT/PATCH/photos/{photo}updatephotos.update
DELETE/photos/{photo}destroyphotos.destroy

Specificare il Modello Risorsa

Se stai usando il route model binding e desideri che i metodi del controller risorsa effettuino il type-hint di un’istanza del modello, puoi usare l’opzione --model quando generi il controller:

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

Rotta Risorsa Parziale

Quando si dichiara una rotta risorsa, è possibile specificare un sottoinsieme di azioni che il controller dovrebbe gestire invece del set completo di 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'
]);

API Resource Routes

Quando si dichiarano rotte risorsa che saranno consumate da API, si desidera comunemente escludere rotte che presentano viste HTML come create e edit. Per comodità, è possibile utilizzare il metodo apiResource per escludere automaticamente queste due rotte:

use App\Http\Controllers\PhotoController;

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

È possibile registrare più 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 rotte per una risorsa annidata. Ad esempio, un post fotografico potrebbe avere più commenti che possono essere allegati al post. Per annidare i controller risorsa, usa la notazione “punto” nella dichiarazione della rotta:

use App\Http\Controllers\PhotoCommentController;

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

Questa rotta registrerà una risorsa annidata a cui è possibile accedere con URI come il seguente:

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

Denominazione delle Rotte Risorsa

Per impostazione predefinita, tutte le azioni del controller risorsa hanno un nome; tuttavia, puoi sovrascrivere questi nomi passando un array names con le tue opzioni:

use App\Http\Controllers\PhotoController;

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

Denominazione dei Parametri delle Rotte Risorsa

Per impostazione predefinita, Route::resource creerà i parametri di rotta per le tue rotte risorsa basati sulla versione “singolare” del nome della risorsa. Puoi facilmente sovrascrivere questo su base per risorsa usando il metodo parameters. L’array passato al metodo parameters dovrebbe essere un array associativo di nomi di risorse e nomi di parametri:

use App\Http\Controllers\AdminUserController;

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

L’esempio sopra genera i seguenti URI per la rotta show della risorsa:

/users/{admin_user}

Supplementare i Controller Risorsa

Se hai bisogno di aggiungere rotte aggiuntive a un controller risorsa oltre al set predefinito di rotte risorsa, dovresti definire quelle rotte prima della tua chiamata a Route::resource; altrimenti, le rotte definite dal metodo resource potrebbero involontariamente avere la precedenza sulle tue rotte supplementari:

use App\Http\Controllers\PhotoController;

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

Iniezione delle Dipendenze (Dependency Injection) & Controller

Iniezione nel Costruttore

Il service container di Laravel viene utilizzato per risolvere tutte le istanze dei controller Laravel. Di conseguenza, sei in grado di effettuare il type-hint di qualsiasi dipendenza di cui il tuo controller potrebbe aver bisogno nel suo costruttore. Le dipendenze dichiarate verranno automaticamente risolte e iniettate nell’istanza del controller:

<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * L'istanza del repository utente.
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}

L’iniezione di dipendenze nel costruttore è particolarmente utile quando il controller necessita dello stesso servizio in più metodi. Questo approccio porta a codice più pulito e testabile, poiché le dipendenze sono esplicitamente dichiarate e fornite esternamente, facilitando il mocking durante i test.

Iniezione nei Metodi

Oltre all’iniezione nel costruttore, puoi anche effettuare il type-hint delle dipendenze nei metodi del tuo controller. Un caso d’uso comune per l’iniezione nei metodi è l’iniezione dell’istanza Illuminate\Http\Request nel metodo del tuo controller:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

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

        // Memorizza l'utente...

        return redirect('/users');
    }
}

Se il metodo del tuo controller si aspetta anche input da un parametro di rotta, elenca i parametri della rotta dopo le altre dipendenze. Ad esempio, se la tua rotta è definita così:

use App\Http\Controllers\UserController;

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

Puoi ancora effettuare il type-hint 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');
    }
}

L’iniezione di Form Request è un altro uso potente dell’iniezione nei metodi. Type-hintando una classe Form Request, Laravel convaliderà automaticamente la richiesta in entrata prima che il metodo del controller venga eseguito. Questo sposta la logica di validazione fuori dal controller, mantenendolo più snello e focalizzato sulla sua responsabilità principale.

Route Model Binding

Molto comunemente, avrai bisogno di recuperare un modello Eloquent basato su un ID o un altro identificatore presente nell’URI della richiesta. Il Route Model Binding di Laravel fornisce un modo conveniente per iniettare automaticamente le istanze del modello direttamente nei metodi del tuo controller.

Binding Implicito

Laravel risolve automaticamente i modelli Eloquent definiti nelle rotte o nelle azioni del controller i cui nomi di variabili type-hintate corrispondono a un segmento di rotta. Ad esempio:

use App\Http\Controllers\UserController;
use App\Models\User;

// Rotta definita come:
Route::get('/users/{user}', [UserController::class, 'show']);

// Metodo del controller definito come:
public function show(User $user)
{
    return view('user.profile', ['user' => $user]);
}

Poiché la variabile $user è type-hintata come il modello Eloquent App\Models\User e il nome della variabile corrisponde al segmento URI {user}, Laravel inietterà automaticamente l’istanza del modello che ha un ID corrispondente al valore corrispondente dall’URI. Se un’istanza del modello corrispondente non viene trovata nel database, verrà generata automaticamente una risposta HTTP 404.

Naturalmente, il binding implicito è possibile anche quando si utilizza un controller risorsa.

Personalizzazione della Chiave

A volte potresti voler risolvere i modelli Eloquent usando una colonna diversa da id. Per fare ciò, puoi specificare la colonna nella definizione del parametro della rotta:

use App\Http\Controllers\PostController;
use App\Models\Post;

Route::get('/posts/{post:slug}', [PostController::class, 'show']);

Alternativamente, puoi sovrascrivere il metodo getRouteKeyName sul tuo modello Eloquent:

/**
 * Ottieni la chiave della rotta per il modello.
 */
public function getRouteKeyName(): string
{
    return 'slug';
}

Binding Esplicito

Non sei obbligato a usare il binding implicito basato sulla convenzione di Laravel. Puoi anche definire esplicitamente come i parametri di rotta vengono mappati ai modelli. Per registrare un binding esplicito, usa il metodo model del router per specificare la classe per un dato parametro. Dovresti definire i tuoi binding espliciti del modello all’inizio del metodo boot della tua classe App\Providers\RouteServiceProvider:

use App\Models\User;
use Illuminate\Support\Facades\Route;

/**
 * Definisci i tuoi binding del modello di rotta, pattern filter, ecc.
 */
public function boot(): void
{
    Route::model('user', User::class);

    //...
}

Successivamente, definisci una rotta che contiene un parametro {user}:

use App\Http\Controllers\UserController;

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

Poiché abbiamo associato il parametro {user} al modello App\Models\User, un’istanza di User verrà iniettata nella rotta. Quindi, ad esempio, una richiesta a users/1 inietterà l’istanza User dal database che ha un ID di 1.

Se desideri definire la tua logica di risoluzione del route model binding, puoi usare il metodo Route::bind. La Closure passata al metodo bind riceverà il valore del segmento URI e dovrebbe restituire l’istanza della classe che dovrebbe essere iniettata nella rotta:

use App\Models\User;
use Illuminate\Support\Facades\Route;

/**
 * Definisci i tuoi binding del modello di rotta, pattern filter, ecc.
 */
public function boot(): void
{
    Route::bind('user', function (string $value) {
        return User::where('name', $value)->firstOrFail();
    });

    //...
}

Personalizzazione del Comportamento per Modelli Mancanti

Tipicamente, una risposta HTTP 404 verrà generata se un modello implicitamente associato non viene trovato. Tuttavia, puoi personalizzare questo comportamento chiamando il metodo missing quando definisci la tua rotta. Il metodo missing accetta una Closure che verrà invocata se un modello implicitamente associato non può essere trovato:

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

Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
        ->missing(function (Request $request) {
            return Redirect::route('locations.index');
        });

Controller a Azione Singola (Single Action Controllers)

Se desideri definire un controller che gestisce solo una singola azione, puoi posizionare un singolo metodo __invoke sulla classe controller. Laravel non richiede che le classi controller estendano una classe base. Tuttavia, se non estendi la classe base del controller, dovrai fornire manualmente l’accesso ad altre funzionalità del controller come il metodo middleware.

Per generare un controller invocabile, usa l’opzione --invokable del comando Artisan make:controller:

php artisan make:controller ShowUserProfile --invokable

Questo genererà un controller con solo il metodo __invoke:

<?php

namespace App\Http\Controllers;

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

class ShowUserProfile extends Controller
{
    /**
     * Mostra il profilo per l'utente specificato.
     */
    public function __invoke(string $id): View
    {
        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}

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

use App\Http\Controllers\ShowUserProfile;

Route::get('/user/{id}', ShowUserProfile::class);

Questo approccio è utile per controller molto semplici e focalizzati, promuovendo la leggibilità e aderendo al principio di singola responsabilità a livello di controller.