Fachadas

Fachadas

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”. Los facades proporcionan una interfaz “estática” a clases que están disponibles en el contenedor de servicios de la aplicación. Laravel incluye muchos facades que brindan acceso a casi todas las características de Laravel.

Los facades de Laravel actúan como “proxies estáticos” para las clases subyacentes en el contenedor de servicios, proporcionando la ventaja de una sintaxis concisa y expresiva, al tiempo que mantienen más testabilidad y flexibilidad que los métodos estáticos tradicionales.

Todos los facades de Laravel están definidos en el espacio de nombres Illuminate\Support\Facades. Por lo tanto, podemos acceder fácilmente a un facade de la siguiente manera:

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

Helper functions

Para complementar los facades, Laravel ofrece una variedad de “funciones helper globales” que facilitan aún más la interacción con las características comunes de Laravel. Algunas de las funciones helper comunes con las que puedes interactuar son view, response, url, config, entre otras. 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 el facade Illuminate\Support\Facades\Response para generar una respuesta JSON, simplemente podemos utilizar la función response. Debido a que las funciones helper están disponibles globalmente, no es necesario importar ninguna clase para usarlas:

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

When to use facades

El principal peligro de los facades es el “crecimiento descontrolado de la clase”. Dado que los facades son tan fáciles de usar y no requieren inyección explícita, puede ser tentador dejar que tus clases continúen creciendo y usar muchos facades en una sola clase. Cuando se utiliza la inyección de dependencias, este potencial se mitiga debido al feedback visual que ofrece un constructor grande, indicando que la clase está creciendo demasiado. Por lo tanto, al usar facades, presta especial atención al tamaño de tu clase para que su ámbito de responsabilidad permanezca estrecho. Si tu clase está creciendo demasiado, considera dividirla en varias clases más pequeñas.

Este enfoque te ayudará a mantener un código más limpio, modular y fácil de mantener a largo plazo.

Facades vs Dependency Injection

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 objeto simulado (mock) o de prueba y verificar que se hayan llamado varios métodos en el objeto simulado.

Normalmente, no sería posible simular o crear un stub de un método de clase verdaderamente estático. Sin embargo, dado que los facades de Laravel utilizan métodos dinámicos para redirigir las llamadas de métodos a objetos resueltos desde el contenedor de servicios, en realidad podemos probar los facades de la misma manera que 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 esperado:

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 helper functions

Además de los facades, Laravel incluye una variedad de funciones “helper” que pueden realizar tareas comunes como generar vistas, lanzar eventos, despachar trabajos o enviar respuestas HTTP. Muchas de estas funciones helper realizan la misma función que el facade correspondiente. Por ejemplo, esta llamada de facade y llamada de helper son equivalentes:

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

No hay diferencia práctica alguna entre los facades y las funciones helper. Cuando se usan las funciones helper, aún puedes probarlas exactamente como lo harías con el facade correspondiente. Por ejemplo, dado el siguiente enrutamiento:

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

El helper cache llamará al método get en la clase subyacente del facade Cache. Entonces, aunque estemos usando la función helper, podemos escribir la siguiente prueba para verificar que el método fue llamado con el argumento esperado:

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');
}

How facades work

En una aplicación de Laravel, un facade es una clase que proporciona acceso a un objeto desde el contenedor de servicios. El mecanismo que hace que esto funcione está en la clase base Facade en Laravel. Los facades de Laravel, así como cualquier facade personalizado 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 siguiente ejemplo, se hace una llamada al sistema de caché de Laravel. Al mirar este código, uno podría asumir que se está llamando al método estático get 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
{
    /**
     * Show the profile for the given user.
     */
    public function showProfile(string $id): View
    {
        $user = Cache::get('user:'.$id);
 
        return view('profile', ['user' => $user]);
    }
}

Observa que cerca del inicio del archivo estamos “importando” el facade Cache. Este facade actúa como un proxy para acceder a la implementación subyacente de la interfaz Illuminate\Contracts\Cache\Factory. Cualquier llamada que hagamos utilizando el facade se pasará a la instancia subyacente del servicio de caché de Laravel.

Si observamos la clase Illuminate\Support\Facades\Cache, verás que no hay ningún método estático get definido:

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

En lugar de eso, el 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 hace referencia a cualquier método estático en el 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.

Este enfoque permite que los facades en Laravel actúen como proxies estáticos que canalizan llamadas a objetos subyacentes resueltos dinámicamente desde el contenedor de servicios, facilitando así un acceso claro y sencillo a las funcionalidades del framework.

Real-Time Facade

Usando facades en tiempo real, puedes tratar cualquier clase en tu aplicación como si fuera un facade. Para ilustrar cómo se puede utilizar, primero examinemos un código que no utiliza 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
{
    /**
     * Publish the 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 de manera aislada, ya que podemos simular el 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 ser probado mientras no estamos obligados a pasar explícitamente una instancia de Publisher. Para generar un facade en tiempo real, prefixa 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
{
    /**
     * Publish the podcast.
     */
    public function publish(Publisher $publisher): void 
    public function publish(): void 
    {
        $this->update(['publishing' => now()]);
 
        $publisher->publish($this); 
        Publisher::publish($this); 
    }
}

Cuando se utiliza el facade en tiempo real, la implementación del publisher se resolverá desde el contenedor de servicios usando la porción del nombre de la interfaz o clase que aparece después del prefijo Facades. Al hacer pruebas, podemos usar las utilidades de prueba de facades integradas en Laravel para simular esta llamada de método:

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

Facade class Reference

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 API de un root de facade dado. También se incluye la clave de vinculación del contenedor de servicios cuando corresponda.

Facade Class Reference