Nel capitolo precedente della nostra Guida al framework Laravel, parlando dei Service Provider, abbiamo introdotto l’argomento delle Facades. Nella programmazione orientata agli oggetti, il Facade Pattern consiste nella creazione di un oggetto che serve da interfaccia frontale ad un codice più complesso.
Questa definizione può anche soddisfare chi si ferma ai concetti base, ma per noi che vogliamo approfondire non è sufficiente. Per cui andiamo a vedere come funzionano le Facades in Laravel.
Il framework fa un uso massiccio di questi oggetti: la maggior parte delle classi che utilizziamo sono Facades, con una sintassi del genere:
Facade::doSomething();
Facade::doSomething()->doSomethingElse();
Dal codice appena esaminato, notiamo che i metodi vengono eseguiti staticamente. E in effetti, nella documentazione di Laravel leggiamo che le Facades servono come “proxy statici a classi sottostanti, offrendo il vantaggio di una sintassi tersa ed espressiva, pur mantenendo una maggiore testabilità e flessibilità rispetto ai metodi statici tradizionali”.
Per capire meglio questi concetti, andiamo a creare una Facade Personalizzata. Artisan non supporta la creazione di questi componenti, per cui dobbiamo procedere manualmente. Creiamo una cartella all’interno di app, dove andremo ad organizzare tutte le nostre estensioni. La chiameremo Extensions. E all’interno di questa nuova cartella ne creiamo altre due, rispettivamente Facades e Classes.
Adesso creiamo una nuova classe all’interno della cartella Facades appena creata, che estenderà la Facade base di Laravel.
<?php
namespace AppExtensionsFacades;
use IlluminateSupportFacadesFacade;
class TestFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'testKey';
}
}
Ogni Facade deve implementare un solo metodo, getFacadeAccessor() che restituisce una stringa. Capiremo nel corso di questa lezione a che cosa ci serve.
Adesso dobbiamo creare una classe che è parte integrante di quella business logic che il design pattern Facade nasconde. La classe la posizioniamo all’interno della nuova cartella app/Extensions/Classes e potrà avere un qualsiasi nome.
<?php
namespace AppExtensionsClasses;
class Test
{
public function hello()
{
return 'Ciao, io sono la classe reale della Facade';
}
}
Il prossimo step è registrare nel Service Provider il binding tra la Facade e la classe reale. Quindi apriamo la classe AppServiceProvider, contenuta nella cartella app/Http/Providers e definiamo il binding in questo modo.
<?php
namespace AppProviders;
use AppExtensionsClassesTest;
use IlluminateSupportServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind('testKey', fn() => new Test());
}
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
}
}
Definiamo l’alias nel file config/app.php, all’interno del blocco ‘aliases’ e sucessivamente lanciamo il comando php artisan optimize
'aliases' => Facade::defaultAliases()->merge([
'TestFacade' => AppExtensionsFacadesTestFacade::class
])->toArray()
Da questo momento possiamo usare la Facade per accedere ai metodi della classe reale
TestFacade::hello();
Ma com’è possibile tutto ciò? È possibile grazie a PHP) Vediamo nel dettaglio cosa succede dietro le quinte. Ci ricordiamo che i Service Providers vengono caricati all’avvio del framework, quindi dopo il boot la proprietà app è disponibile all’uso e contiene la coppia Facade => Classe Reale.
La proprietà $app è accessibile anche dalla classe IlluminateSupportFacadesFacade che abbiamo esteso con la nostra TestFacade.
Quest’ultima eredita l’implementazione del metodo magico __callStatic, per gestire la chiamata di metodi statici che in realtà non sono stati definiti nella classe. Tale gestione prevede il recupero del valore del metodo getFacadeAccessor() che abbiamo implementato, quindi la stringa ‘testKey’.
La stessa stringa l’abbiamo definita anche nel binding del ServiceProvider. La Facade fa un match tra questa stringa e gli elementi della proprietà $app. Quando risolve il match, utilizzerà l’istanza della classe reale.