Facade

Introduzione

Nella documentazione di Laravel, troverai esempi di codice che interagiscono con le funzionalità di Laravel tramite le "facades". Le facades offrono un’interfaccia "statica" alle classi disponibili nel service container dell’applicazione. Laravel include molte facades che danno accesso a quasi tutte le funzionalità di Laravel.

Le facades di Laravel fungono da "proxy statici" per le classi sottostanti nel service container, offrendo il vantaggio di una sintassi concisa ed espressiva, mantenendo al contempo maggiore testabilità e flessibilità rispetto ai metodi statici tradizionali. Va benissimo se non comprendi completamente come funzionano le facades da subito: segui il flusso e nel tempo ti sarà più chiaro.

Tutte le facades di Laravel sono definite nel namespace Illuminate\Support\Facades. Quindi, possiamo accedere facilmente a una facade così:

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

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

Nella documentazione di Laravel, molti esempi utilizzeranno le facades per dimostrare diverse funzionalità del framework.

Funzioni Helper

Per integrare le facades, Laravel offre diverse "funzioni helper" globali che rendono ancora più facile interagire con le funzionalità comuni di Laravel. Alcune delle funzioni helper più usate sono view, response, url, config e altre. Ogni funzione helper di Laravel è documentata con la relativa funzionalità; tuttavia, una lista completa è disponibile nella documentazione helper.

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

    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 facile da ricordare che ti permette di usare le funzionalità di Laravel senza dover ricordare nomi di classi lunghi che devono essere iniettati o configurati manualmente. Inoltre, grazie al loro uso unico dei metodi dinamici di PHP, sono facili da testare.

Tuttavia, bisogna fare attenzione quando si utilizzano le facades. Il principale pericolo delle facades è il cosiddetto "scope creep" delle classi. Dato che le facades sono così facili da usare e non richiedono injection, può essere facile permettere alle tue classi di crescere e utilizzare molte facades in una singola classe.

Utilizzando la dependency injection, questo potenziale è mitigato dal feedback visivo dato da un costruttore bello grande che ti fa capire che la tua classe sta diventando troppo "voluminosa".

Insomma: quando usi le facades, presta particolare attenzione alla dimensione della tua classe in modo che il suo ambito di responsabilità rimanga ristretto. Se la tua classe diventa troppo grande, considera di suddividerla in più classi più piccole.

Facade vs Dependency Injection

Uno dei principali vantaggi della dependency injection è 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.

Normalmente, non sarebbe possibile creare un mock o uno stub di un metodo di classe veramente statico. Tuttavia, poiché le facades utilizzano metodi dinamici per fare da proxy alle chiamate ai metodi degli oggetti risolti dal service container, in realtà possiamo testare le facades proprio come faremmo con un’istanza di classe iniettata. Per esempio, considera la seguente rotta:

    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 previsto:

use Illuminate\Support\Facades\Cache;

test('basic example', function () {
    Cache::shouldReceive('get')
         ->with('key')
         ->andReturn('value');

    $response = $this->get('/cache');

    $response->assertSee('value');
});
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 Helper

Oltre alle facades, Laravel include una varietà di funzioni "helper" che possono svolgere compiti comuni come generare viste, attivare eventi, inviare job o inviare risposte HTTP. Molte di queste funzioni helper svolgono la stessa funzione di una facade corrispondente. Ad esempio, questa chiamata alla facade e questa chiamata all’helper sono equivalenti:

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

    return view('profile');

Non c’è assolutamente alcuna differenza pratica tra facades e funzioni helper. Quando si utilizzano le funzioni helper, è possibile testarle esattamente come si farebbe con la facade corrispondente. Ad esempio, data la seguente route:

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

L’helper cache chiamerà il metodo get sulla classe sottostante la facade Cache. Quindi, anche se stiamo usando la funzione helper, possiamo scrivere il seguente test per verificare che il metodo sia stato chiamato con l’argomento che ci aspettavamo:

    use Illuminate\Support\Facades\Cache;

    /**
     * Un esempio di test funzionale di base.
     */
    public function test_basic_example(): void
    {
        Cache::shouldReceive('get')
             ->with('key')
             ->andReturn('value');

        $response = $this->get('/cache');

        $response->assertSee('value');
    }

Come funzionano le Facade

In un’applicazione Laravel, una facade è una classe che fornisce accesso a un oggetto dal container. Il meccanismo che consente tutto ciò è nella classe Facade. Le facades di Laravel, e tutte le facades personalizzate che crei, estendono la classe base Illuminate\Support\Facades\Facade.

La classe base Facade utilizza il metodo magico __callStatic() per reindirizzare le chiamate dalla tua facade a un oggetto risolto dal container. Nell’esempio di seguito, viene effettuata una chiamata al sistema di cache di Laravel. Guardando velocemente questo codice, si potrebbe pensare 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]);
    }
}

Notate che vicino alla 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 facciamo usando la facade verrà inoltrata all’istanza sottostante del servizio di cache di Laravel.

Se guardiamo alla classe Illuminate\Support\Facades\Cache, vedremo che al suo interno 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 un metodo statico sulla facade Cache, Laravel risolve il binding cache dal service container ed esegue il metodo richiesto (in questo caso, get) su quell’oggetto.

Facade in Tempo Reale

Utilizzando le facade in tempo reale, puoi trattare qualsiasi classe nella tua applicazione come se fosse una facade. Per illustrare come può essere utilizzato, esaminiamo prima del codice che non usa le facades in tempo reale. Per esempio, supponiamo che il nostro modello Podcast abbia un metodo publish. Tuttavia, per pubblicare il podcast, dobbiamo 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);
    }
}

Iniettare un’implementazione di Publisher nel metodo ci permette di testare facilmente il metodo in isolamento, poiché possiamo simulare il Publisher iniettato. Tuttavia, questo richiede di passare sempre un’istanza di Publisher ogni volta che chiamiamo il metodo publish. Utilizzando le facade in tempo reale, possiamo mantenere la stessa possibilità di test senza dover passare esplicitamente un’istanza di Publisher. Per generare una facade in tempo reale, prefissa il namespace della classe importata con Facades:

<?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 di Publisher sarà risolta dal service container usando la parte del nome dell’interfaccia o della classe che appare dopo il prefisso Facades. Durante i test, possiamo utilizzare gli helper di testing delle facades integrati di Laravel per simulare questa chiamata al metodo:

<?php

use App\Models\Podcast;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

test('podcast can be published', function () {
    $podcast = Podcast::factory()->create();

    Publisher::shouldReceive('publish')->once()->with($podcast);

    $podcast->publish();
});
<?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();
    }
}

Reference delle Facade

Di seguito troverai tutte le facade e le loro classi sottostanti. La chiave del service container binding è inclusa dove applicabile.

Facade Classe Binding nel Service Container
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Auth Illuminate\Auth\AuthManager auth
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcaster  
Broadcast Illuminate\Contracts\Broadcasting\Factory  
Bus Illuminate\Contracts\Bus\Dispatcher  
Cache (Instance) Illuminate\Cache\Repository cache.store
Cache Illuminate\Cache\CacheManager cache
Config Illuminate\Config\Repository config
Context Illuminate\Log\Context\Repository  
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
Date Illuminate\Support\DateFactory date
DB (Instance) Illuminate\Database\Connection db.connection
DB Illuminate\Database\DatabaseManager db
Event Illuminate\Events\Dispatcher events
Exceptions (Instance) Illuminate\Contracts\Debug\ExceptionHandler  
Exceptions Illuminate\Foundation\Exceptions\Handler  
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate  
Hash Illuminate\Contracts\Hashing\Hasher hash
Http Illuminate\Http\Client\Factory  
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\LogManager log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager  
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Password Illuminate\Auth\Passwords\PasswordBrokerManager auth.password
Pipeline (Instance) Illuminate\Pipeline\Pipeline  
Process Illuminate\Process\Factory  
Queue (Base Class) Illuminate\Queue\Queue  
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue Illuminate\Queue\QueueManager queue
RateLimiter Illuminate\Cache\RateLimiter  
Redirect Illuminate\Routing\Redirector redirect
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Redis Illuminate\Redis\RedisManager redis
Request Illuminate\Http\Request request
Response (Instance) Illuminate\Http\Response  
Response Illuminate\Contracts\Routing\ResponseFactory  
Route Illuminate\Routing\Router router
Schedule Illuminate\Console\Scheduling\Schedule  
Schema Illuminate\Database\Schema\Builder  
Session (Instance) Illuminate\Session\Store session.store
Session Illuminate\Session\SessionManager session
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
Storage Illuminate\Filesystem\FilesystemManager filesystem
URL Illuminate\Routing\UrlGenerator url
Validator (Instance) Illuminate\Validation\Validator  
Validator Illuminate\Validation\Factory validator
View (Instance) Illuminate\View\View  
View Illuminate\View\Factory view
Vite Illuminate\Foundation\Vite  
Lascia un commento

Lascia un commento

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