Facciate (Facades)

Introduzione

In tutta la documentazione di Laravel, vedrai esempi di codice che interagiscono con le funzionalità di Laravel tramite “facades”. Le facades forniscono un’interfaccia “statica” alle classi disponibili nel container dei servizi dell’applicazione. Laravel include molte facades che forniscono accesso a quasi tutte le funzionalità di Laravel.

Le facades di Laravel fungono da “proxy statici” per le classi sottostanti nel container dei servizi, offrendo il vantaggio di una sintassi concisa ed espressiva pur mantenendo una maggiore testabilità e flessibilità rispetto ai metodi statici tradizionali. Va benissimo se non comprendi completamente come funzionano le facades: continua ad imparare su Laravel.

Tutte le facades di Laravel sono definite nello spazio dei nomi Illuminate\Support\Facades. Quindi, possiamo accedere facilmente a una facade come segue:

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
 
Route::get('/cache', function () {
    return Cache::get('key');
});

In tutta la documentazione di Laravel, molti degli esempi utilizzeranno le facades per dimostrare varie funzionalità del framework.

Funzioni di supporto

Per integrare le facades, Laravel offre una varietà di “funzioni di supporto” globali che semplificano ulteriormente l’interazione con le funzionalità comuni di Laravel. Alcune delle funzioni di supporto comuni con cui puoi interagire sono view, response, url, config e altre ancora. Ogni funzione di supporto offerta da Laravel è documentata insieme alla relativa funzionalità; tuttavia, è disponibile un elenco completo all’interno della documentazione dedicata alle funzioni di supporto.

Ad esempio, invece di utilizzare la facade Illuminate\Support\Facades\Response per generare una risposta JSON, possiamo semplicemente utilizzare la funzione response. Poiché le funzioni di supporto sono globalmente disponibili, non è necessario importare alcuna classe per utilizzarle:

use Illuminate\Support\Facades\Response;
 
Route::get('/users', function () {
    return Response::json([
        // ...
    ]);
});
 
Route::get('/users', function () {
    return response()->json([
        // ...
    ]);
});

Quando utilizzare le facades

Le facades offrono molti vantaggi. Forniscono una sintassi concisa e memorabile che ti consente di utilizzare le funzionalità di Laravel senza dover ricordare lunghe classi che devono essere iniettate o configurate manualmente. Inoltre, grazie all’utilizzo unico dei metodi dinamici di PHP, le facades sono facili da testare.

Tuttavia, è necessaria un’attenzione particolare nell’utilizzo delle facades. Il pericolo principale delle facades è la “espansione dello scope della classe”. Poiché le facades sono così facili da usare e non richiedono l’iniezione, può essere facile lasciare che le tue classi continuino a crescere e utilizzino molte facades in una singola classe. Utilizzando l’iniezione delle dipendenze, questo potenziale viene mitigato dal feedback visivo di un grande costruttore che indica che la tua classe sta diventando troppo grande. Pertanto, quando si utilizzano le facades, presta particolare attenzione alla dimensione della tua classe in modo che il suo scope di responsabilità rimanga limitato. Se la tua classe sta diventando troppo grande, considera di suddividerla in più classi più piccole.

Facades vs Iniezione delle Dipendenze

Uno dei principali vantaggi dell’iniezione delle dipendenze è la possibilità di sostituire le implementazioni della classe iniettata. Questo è utile durante i test, poiché puoi iniettare un mock o uno stub e verificare che vari metodi siano stati chiamati sullo stub.

In genere, non sarebbe possibile fare il mock o lo stub di un metodo di classe veramente statico. Tuttavia, poiché le facades utilizzano metodi dinamici per instradare le chiamate dei metodi verso gli oggetti risolti dal service container, in realtà possiamo testare le facades allo stesso modo in cui testeremmo un’istanza di classe iniettata. Ad esempio, dato il seguente percorso:

use Illuminate\Support\Facades\Cache;
 
Route::get('/cache', function () {
    return Cache::get('key');
});

Utilizzando i metodi di testing delle facades di Laravel, possiamo scrivere il seguente test per verificare che il metodo Cache::get sia stato chiamato con l’argomento che ci aspettavamo:

use Illuminate\Support\Facades\Cache;
 
/**
 * A basic functional test example.
 */
public function test_basic_example(): void
{
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');
 
    $response = $this->get('/cache');
 
    $response->assertSee('value');
}

Facades Vs. Funzioni Helper

Oltre alle facades, Laravel include una varietà di “funzioni helper” che possono eseguire compiti comuni come generare viste, scatenare eventi, inviare lavori o inviare risposte HTTP. Molte di queste funzioni helper svolgono la stessa funzione di una corrispondente facades. Ad esempio, questa chiamata di facades e la chiamata di funzione helper sono equivalenti:

return Illuminate\Support\Facades\View::make('profile');
 
return view('profile');

Non vi è alcuna differenza pratica tra le facades e le funzioni helper. Quando si utilizzano le funzioni helper, è ancora possibile testarle esattamente come si farebbe con la corrispondente facades. Ad esempio, dato il seguente percorso:

Route::get('/cache', function () {
    return cache('key');
});

La funzione helper di cache chiamerà il metodo get sulla classe sottostante la facades di Cache. Quindi, anche se stiamo utilizzando la funzione helper, possiamo scrivere il seguente test per verificare che il metodo sia stato chiamato con l’argomento previsto:

use Illuminate\Support\Facades\Cache;
 
/**
 * A basic functional test example.
 */
public function test_basic_example(): void
{
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');
 
    $response = $this->get('/cache');
 
    $response->assertSee('value');
}

Come funzionano le facades

In un’applicazione Laravel, una facade è una classe che fornisce accesso a un oggetto dal container di servizio. La logica che rende tutto ciò possibile è implementata nella classe Facade. Le facades di Laravel, così come le facades personalizzate che crei, estenderanno la classe base Illuminate\Support\Facades\Facade.

La classe base Facade fa uso del metodo magico __callStatic() per deferire le chiamate dalla tua facade a un oggetto risolto dal container di servizio. Nell’esempio seguente, viene effettuata una chiamata al sistema di cache di Laravel. A una prima occhiata a questo codice, si potrebbe supporre che il metodo statico get venga chiamato sulla classe Cache:

<?php
 
namespace App\Http\Controllers;
 
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
 
class UserController extends Controller
{
    /**
     * Show the profile for the given user.
     */
    public function showProfile(string $id): View
    {
        $user = Cache::get('user:'.$id);
 
        return view('profile', ['user' => $user]);
    }
}

Si noti che nella parte superiore del file stiamo “importando” la facade Cache. Questa facade funge da proxy per accedere all’implementazione sottostante dell’interfaccia Illuminate\Contracts\Cache\Factory. Qualsiasi chiamata che facciamo utilizzando la facade verrà passata all’istanza sottostante del servizio di cache di Laravel.

Se osserviamo la classe Illuminate\Support\Facades\Cache, vedremo che non esiste un metodo statico get:

class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     */
    protected static function getFacadeAccessor(): string
    {
        return 'cache';
    }
}

Invece, la facade Cache estende la classe base Facade e definisce il metodo getFacadeAccessor(). Il compito di questo metodo è restituire il nome di un binding del service container. Quando un utente fa riferimento a qualsiasi metodo statico sulla facade Cache, Laravel risolve il binding della cache dal service container ed esegue il metodo richiesto (in questo caso, get) su quell’oggetto.

Facades in tempo reale

Utilizzando le facades in tempo reale, è possibile trattare qualsiasi classe dell’applicazione come se fosse una facade. Per illustrare come ciò possa essere utilizzato, esaminiamo prima del codice che non utilizza le facades in tempo reale. Ad esempio, supponiamo che il nostro modello Podcast abbia un metodo publish. Tuttavia, per pubblicare il podcast, è necessario iniettare un’istanza di Publisher:

<?php
 
namespace App\Models;
 
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
 
class Podcast extends Model
{
    /**
     * Publish the podcast.
     */
    public function publish(Publisher $publisher): void
    {
        $this->update(['publishing' => now()]);
 
        $publisher->publish($this);
    }
}

Iniettando un’implementazione del publisher nel metodo, ci consente di testare facilmente il metodo in modo isolato poiché possiamo simulare l’iniettore del publisher. Tuttavia, ci richiede di passare sempre un’istanza di publisher ogni volta che chiamiamo il metodo publish. Utilizzando le facades in tempo reale, possiamo mantenere la stessa testabilità senza essere obbligati a passare esplicitamente un’istanza di Publisher. Per generare una facade in tempo reale, aggiungi il prefisso “Facades” al namespace della classe importata:

<?php
 
namespace App\Models;
 
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
 
class Podcast extends Model
{
    /**
     * Publish the podcast.
     */
    public function publish(): void
    {
        $this->update(['publishing' => now()]);
 
        Publisher::publish($this);
    }
}

Quando viene utilizzata la facade in tempo reale, l’implementazione del publisher verrà risolta dal container dei servizi utilizzando la parte del nome dell’interfaccia o della classe che appare dopo il prefisso “Facades“. Durante i test, possiamo utilizzare gli appositi metodi di test delle facades integrati in Laravel per simulare questa chiamata di metodo:

<?php
 
namespace Tests\Feature;
 
use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
 
class PodcastTest extends TestCase
{
    use RefreshDatabase;
 
    /**
     * A test example.
     */
    public function test_podcast_can_be_published(): void
    {
        $podcast = Podcast::factory()->create();
 
        Publisher::shouldReceive('publish')->once()->with($podcast);
 
        $podcast->publish();
    }
}

Classe Facade di riferimento

Di seguito troverai ogni facade e la sua classe sottostante. Questo è uno strumento utile per accedere rapidamente alla documentazione dell’API per una determinata radice di facade. È inclusa anche la chiave di associazione del container dei servizi quando applicabile.

FacadeClassService Container Binding
AppIlluminate\Foundation\Applicationapp
ArtisanIlluminate\Contracts\Console\Kernelartisan
AuthIlluminate\Auth\AuthManagerauth
Auth (Instance)Illuminate\Contracts\Auth\Guardauth.driver
BladeIlluminate\View\Compilers\BladeCompilerblade.compiler
BroadcastIlluminate\Contracts\Broadcasting\Factory 
Broadcast (Instance)Illuminate\Contracts\Broadcasting\Broadcaster 
BusIlluminate\Contracts\Bus\Dispatcher 
CacheIlluminate\Cache\CacheManagercache
Cache (Instance)Illuminate\Cache\Repositorycache.store
ConfigIlluminate\Config\Repositoryconfig
CookieIlluminate\Cookie\CookieJarcookie
CryptIlluminate\Encryption\Encrypterencrypter
DateIlluminate\Support\DateFactorydate
DBIlluminate\Database\DatabaseManagerdb
DB (Instance)Illuminate\Database\Connectiondb.connection
EventIlluminate\Events\Dispatcherevents
FileIlluminate\Filesystem\Filesystemfiles
GateIlluminate\Contracts\Auth\Access\Gate 
HashIlluminate\Contracts\Hashing\Hasherhash
HttpIlluminate\Http\Client\Factory 
LangIlluminate\Translation\Translatortranslator
LogIlluminate\Log\LogManagerlog
MailIlluminate\Mail\Mailermailer
NotificationIlluminate\Notifications\ChannelManager 
PasswordIlluminate\Auth\Passwords\PasswordBrokerManagerauth.password
Password (Instance)Illuminate\Auth\Passwords\PasswordBrokerauth.password.broker
Pipeline (Instance)Illuminate\Pipeline\Pipeline 
QueueIlluminate\Queue\QueueManagerqueue
Queue (Instance)Illuminate\Contracts\Queue\Queuequeue.connection
Queue (Base Class)Illuminate\Queue\Queue 
RedirectIlluminate\Routing\Redirectorredirect
RedisIlluminate\Redis\RedisManagerredis
Redis (Instance)Illuminate\Redis\Connections\Connectionredis.connection
RequestIlluminate\Http\Requestrequest
ResponseIlluminate\Contracts\Routing\ResponseFactory 
Response (Instance)Illuminate\Http\Response 
RouteIlluminate\Routing\Routerrouter
SchemaIlluminate\Database\Schema\Builder 
SessionIlluminate\Session\SessionManagersession
Session (Instance)Illuminate\Session\Storesession.store
StorageIlluminate\Filesystem\FilesystemManagerfilesystem
Storage (Instance)Illuminate\Contracts\Filesystem\Filesystemfilesystem.disk
URLIlluminate\Routing\UrlGeneratorurl
ValidatorIlluminate\Validation\Factoryvalidator
Validator (Instance)Illuminate\Validation\Validator 
ViewIlluminate\View\Factoryview
View (Instance)Illuminate\View\View 
ViteIlluminate\Foundation\Vite