Facades
Introducción
A lo largo de la documentación de Laravel, verás ejemplos de código que interactúan con las características de Laravel a través de «facades». Las facades proporcionan una interfaz «estática» a las clases que están disponibles en el contenedor de servicios de la aplicación. Laravel viene con muchas facades que proporcionan acceso a casi todas las características de Laravel.
Las facades de Laravel sirven como «proxies estáticos» a las clases subyacentes en el contenedor de servicios, proporcionando el beneficio de una sintaxis concisa y expresiva mientras mantienen más capacidad de prueba y flexibilidad que los métodos estáticos tradicionales. Está perfectamente bien si no entiendes completamente cómo funcionan las facades; simplemente sigue adelante y continúa aprendiendo sobre Laravel.
Todas las facades de Laravel están definidas en el espacio de nombres Illuminate\Support\Facades
. Así que, podemos acceder fácilmente a una facade de la siguiente manera:
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
Route::get('/cache', function () {
return Cache::get('key');
});
A lo largo de la documentación de Laravel, muchos de los ejemplos usarán facades para demostrar varias características del framework.
Funciones Helper
Para complementar las facades, Laravel ofrece una variedad de «funciones helper» globales que hacen aún más fácil interactuar con las características comunes de Laravel. Algunas de las funciones helper comunes con las que puedes interactuar son view
, response
, url
, config
, y más. Cada función helper ofrecida por Laravel está documentada con su característica correspondiente; sin embargo, una lista completa está disponible dentro de la documentación dedicada a los helpers.
Por ejemplo, en lugar de usar la facade Illuminate\Support\Facades\Response
para generar una respuesta JSON, simplemente podemos usar la función response
. Debido a que las funciones helper están disponibles globalmente, no necesitas importar ninguna clase para usarlas:
use Illuminate\Support\Facades\Response;
Route::get('/users', function () {
return Response::json([
// ...
]);
});
Route::get('/users', function () {
return response()->json([
// ...
]);
});
Cuándo Utilizar Facades
Las facades tienen muchos beneficios. Proporcionan una sintaxis concisa y memorable que te permite usar las características de Laravel sin recordar nombres de clases largos que deben ser inyectados o configurados manualmente. Además, debido a su uso único de métodos dinámicos de PHP, son fáciles de probar.
Sin embargo, se debe tener cuidado al usar facades. El principal peligro de las facades es el «scope creep» de la clase. Dado que las facades son tan fáciles de usar y no requieren inyección, puede ser fácil dejar que tus clases sigan creciendo y usen muchas facades en una sola clase. Usando la inyección de dependencias, este potencial se mitiga por la retroalimentación visual que un constructor grande te da de que tu clase está creciendo demasiado. Así que, cuando uses facades, presta especial atención al tamaño de tu clase para que su ámbito de responsabilidad se mantenga estrecho. Si tu clase se está volviendo demasiado grande, considera dividirla en varias clases más pequeñas.
Facades vs. Inyección de Dependencias
Uno de los principales beneficios de la inyección de dependencias es la capacidad de intercambiar implementaciones de la clase inyectada. Esto es útil durante las pruebas, ya que puedes inyectar un mock o stub y afirmar que varios métodos fueron llamados en el stub.
Típicamente, no sería posible hacer mock o stub de un método de clase verdaderamente estático. Sin embargo, dado que las facades usan métodos dinámicos para hacer proxy de las llamadas a métodos a objetos resueltos desde el contenedor de servicios, en realidad podemos probar las facades tal como probaríamos una instancia de clase inyectada. Por ejemplo, dada la siguiente ruta:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
Usando los métodos de prueba de facades de Laravel, podemos escribir la siguiente prueba para verificar que el método Cache::get
fue llamado con el argumento que esperábamos:
Pest
use Illuminate\Support\Facades\Cache;
test('basic example', function () {
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
});
Facades vs. Funciones Helper
Además de las facades, Laravel incluye una variedad de funciones «helper» que pueden realizar tareas comunes como generar vistas, disparar eventos, despachar trabajos o enviar respuestas HTTP. Muchas de estas funciones helper realizan la misma función que una facade correspondiente. Por ejemplo, esta llamada a una facade y la llamada a un helper son equivalentes:
return Illuminate\Support\Facades\View::make('profile');
return view('profile');
No hay absolutamente ninguna diferencia práctica entre las facades y las funciones helper. Al usar funciones helper, aún puedes probarlas exactamente como lo harías con la facade correspondiente. Por ejemplo, dada la siguiente ruta:
Route::get('/cache', function () {
return cache('key');
});
El helper cache
va a llamar al método get
en la clase subyacente de la facade Cache
. Así que, aunque estemos usando la función helper, podemos escribir la siguiente prueba para verificar que el método fue llamado con el argumento que esperábamos:
use Illuminate\Support\Facades\Cache;
/**
* Un ejemplo básico de prueba funcional.
*/
public function test_basic_example(): void
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$response = $this->get('/cache');
$response->assertSee('value');
}
Cómo Funcionan las Facades
En una aplicación Laravel, una facade es una clase que proporciona acceso a un objeto desde el contenedor. La maquinaria que hace que esto funcione está en la clase Facade
. Las facades de Laravel, y cualquier facade personalizada que crees, extenderán la clase base Illuminate\Support\Facades\Facade
.
La clase base Facade
hace uso del método mágico __callStatic()
para diferir las llamadas desde tu facade a un objeto resuelto desde el contenedor. En el ejemplo a continuación, se hace una llamada al sistema de caché de Laravel. Al mirar este código, uno podría asumir que el método estático get
se está llamando en la clase Cache
:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
class UserController extends Controller
{
/**
* Muestra el perfil del usuario dado.
*/
public function showProfile(string $id): View
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
Observa que cerca de la parte superior del archivo estamos «importando» la facade Cache
. Esta facade sirve como un proxy para acceder a la implementación subyacente de la interfaz Illuminate\Contracts\Cache\Factory
. Cualquier llamada que hagamos usando la facade será pasada a la instancia subyacente del servicio de caché de Laravel.
Si miramos la clase Illuminate\Support\Facades\Cache
, verás que no hay un método estático get
:
class Cache extends Facade
{
/**
* Obtiene el nombre registrado del componente.
*/
protected static function getFacadeAccessor(): string
{
return 'cache';
}
}
En su lugar, la facade Cache
extiende la clase base Facade
y define el método getFacadeAccessor()
. El trabajo de este método es devolver el nombre de una vinculación del contenedor de servicios. Cuando un usuario referencia cualquier método estático en la facade Cache
, Laravel resuelve la vinculación de caché desde el contenedor de servicios y ejecuta el método solicitado (en este caso, get
) contra ese objeto.
Facades en Tiempo Real
Usando facades en tiempo real, puedes tratar cualquier clase en tu aplicación como si fuera una facade. Para ilustrar cómo se puede usar esto, primero examinemos un código que no usa facades en tiempo real. Por ejemplo, supongamos que nuestro modelo Podcast
tiene un método publish
. Sin embargo, para publicar el podcast, necesitamos inyectar una instancia de Publisher
:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publica el podcast.
*/
public function publish(Publisher $publisher): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
Inyectar una implementación de publisher
en el método nos permite probar fácilmente el método en aislamiento, ya que podemos hacer mock del publisher
inyectado. Sin embargo, requiere que siempre pasemos una instancia de publisher
cada vez que llamamos al método publish
. Usando facades en tiempo real, podemos mantener la misma capacidad de prueba sin necesidad de pasar explícitamente una instancia de Publisher
. Para generar una facade en tiempo real, antepone el espacio de nombres de la clase importada con Facades
:
<?php
namespace App\Models;
use App\Contracts\Publisher;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* Publica el podcast.
*/
public function publish(Publisher $publisher): void
public function publish(): void
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
Publisher::publish($this);
}
}
Cuando se usa la facade en tiempo real, la implementación del publisher se resolverá del contenedor de servicios usando la parte del nombre de la interfaz o clase que aparece después del prefijo Facades
. Al probar, podemos usar los helpers de prueba de facades integrados de Laravel para simular esta llamada al método:
Pest
<?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();
});
Referencia de Clases Facade
A continuación, encontrarás cada facade y su clase subyacente. Esta es una herramienta útil para profundizar rápidamente en la documentación de la API para una raíz de facade dada. La clave de vinculación del contenedor de servicios también se incluye donde sea aplicable.
Facade | Clase | Vinculación del Contenedor de Servicios |
---|---|---|
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 |
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 |