Hace unos días, estaba arreglando una prueba inestable y resultó que necesitaba algunos valores y dentro de mi fábrica. Laravel envuelve FakerPHP, al que normalmente accedemos a través del asistente . FakerPHP viene con modificadores como y , pero solo puedes usar uno a la vez, por lo que no puedes hacer , que es exactamente lo que necesitaba. únicos válidos fake() valid() unique() fake()->unique()->valid() Esto me hizo pensar, ¿y si queremos crear nuestro propio modificador? Por ejemplo, o cualquier otro modificador. ¿Cómo podemos el marco? uniqueAndValid() ampliar Pensando en voz alta Estaré abandonando mi línea de pensamiento. Antes de lanzarme a cualquier solución excesivamente diseñada, siempre quiero comprobar si existe una opción más sencilla y entender a qué me estoy enfrentando. Entonces, echemos un vistazo al asistente : fake() function fake($locale = null) { if (app()->bound('config')) { $locale ??= app('config')->get('app.faker_locale'); } $locale ??= 'en_US'; $abstract = \Faker\Generator::class.':'.$locale; if (! app()->bound($abstract)) { app()->singleton($abstract, fn () => \Faker\Factory::create($locale)); } return app()->make($abstract); } Al leer el código, podemos ver que Laravel vincula un singleton al contenedor. Sin embargo, si inspeccionamos el resumen, es una clase normal que no implementa ninguna interfaz y el objeto se crea a través de una fábrica. Esto complica las cosas. ¿Por qué? Porque si fuera una interfaz, podríamos simplemente crear una nueva clase que extienda la clase base , agregar algunas características nuevas y volver a vincularla al contenedor. Pero no tenemos este lujo. \Faker\Generator Hay una fábrica involucrada. Esto significa que no es una simple creación de instancias; se está ejecutando cierta lógica. En este caso, la fábrica agrega algunos proveedores (PhoneNumber, Text, UserAgent, etc.). Entonces, incluso si intentamos volver a vincular, tendremos que usar la fábrica, que devolverá el original. \Faker\Generator ¿Soluciones 🤔? Uno podría pensar: "¿Qué nos impide crear nuestra propia fábrica que devuelva el nuevo generador como se describe en el punto 1?" Bueno, nada, podemos hacer eso, ¡pero no lo haremos! Usamos un marco por varias razones, una de ellas son las actualizaciones. ¿Qué pasará si FakerPHP agrega un nuevo proveedor o realiza una actualización importante? Laravel ajustará el código y las personas que no hayan realizado ningún cambio no notarán nada. Sin embargo, quedaríamos excluidos y nuestro código podría incluso fallar (lo más probable). Entonces sí, no queremos llegar tan lejos. ¿Asi que que hacemos? Ahora que hemos explorado las opciones básicas, podemos empezar a pensar en opciones más avanzadas, como patrones de diseño. No necesitamos una implementación exacta, solo algo familiar para nuestro problema. Por eso siempre digo que es bueno conocerlos. En este caso, podemos "decorar" la clase agregando nuevas funciones manteniendo las antiguas. ¿Suena bien? ¡Veamos cómo! Generator Primero, creemos una nueva clase, : FakerGenerator <?php namespace App\Support; use Closure; use Faker\Generator; use Illuminate\Support\Traits\ForwardsCalls; class FakerGenerator { use ForwardsCalls; public function __construct(private readonly Generator $generator) { } public function uniqueAndValid(Closure $validator = null): UniqueAndValidGenerator { return new UniqueAndValidGenerator($this->generator, $validator); } public function __call($method, $parameters): mixed { return $this->forwardCallTo($this->generator, $method, $parameters); } } Este será nuestro "decorador" (más o menos). Es una clase simple que espera el base como una dependencia e introduce un nuevo modificador, . También utiliza el rasgo de Laravel, que le permite enviar llamadas al objeto base. Generator uniqueAndValid() ForwardsCalls Este rasgo tiene dos métodos: y . Utilice este último cuando desee encadenar métodos en el objeto decorado. En nuestro caso siempre tendremos una única llamada. forwardCallTo forwardDecoratedCallTo También necesitamos implementar , que es el modificador personalizado, pero ese no es el objetivo del artículo. Si está interesado en la implementación, esta clase es básicamente una mezcla de y que se incluyen con FakerPHP, puede encontrarla . UniqueAndValidGenerator ValidGenerator UniqueGenerator aquí Ahora, el marco, en : ampliemos AppServiceProvider <?php namespace App\Providers; use Closure; use Faker\Generator; use App\Support\FakerGenerator; use Illuminate\Support\ServiceProvider; class AppServiceProvider extends ServiceProvider { public function register(): void { $this->app->extend( $this->fakerAbstractName(), fn (Generator $base) => new FakerGenerator($base) ); } private function fakerAbstractName(): string { // This is important, it matches the name bound by the fake() helper return Generator::class . ':' . app('config')->get('app.faker_locale'); } } El método comprueba si se ha vinculado al contenedor un resumen que coincide con el nombre dado. Si es así, anula su valor con el resultado del cierre, eche un vistazo: extend() // Laravel: src/Illuminate/Container/Container.php public function extend($abstract, Closure $closure) { $abstract = $this->getAlias($abstract); if (isset($this->instances[$abstract])) { // You are interested here $this->instances[$abstract] = $closure($this->instances[$abstract], $this); $this->rebound($abstract); } else { $this->extenders[$abstract][] = $closure; if ($this->resolved($abstract)) { $this->rebound($abstract); } } } Es por eso que definimos el método , que genera el mismo nombre que el asistente en el contenedor. fakerAbstractName() fake() Vuelve a verificar el código de arriba si te lo perdiste, dejé un comentario. Ahora, cada vez que llamemos a , se devolverá una instancia de y tendremos acceso al modificador personalizado que introdujimos. Cada vez que invocamos una llamada que no existe en la clase , se activará y la enviará al base utilizando el método . fake() FakerGenerator FakerGenerator __call() Generator forwardCallTo() ¡Eso es todo! Finalmente puedo hacer , ¡y funciona de maravilla! fake()->uniqueAndValid()->randomElement() Antes de concluir, quiero señalar que este no es un patrón decorativo puro. Sin embargo, los patrones no son textos sagrados; modifíquelos para que se ajusten a sus necesidades y resuelva el problema. Conclusión Los marcos son increíblemente útiles y Laravel viene con muchas funciones integradas. Sin embargo, no pueden cubrir todos los casos extremos de sus proyectos y, en ocasiones, es posible que llegue a un callejón sin salida. Cuando eso sucede, siempre puedes ampliar el marco. Hemos visto lo simple que es y espero que hayas entendido la idea principal, que se aplica más allá de este ejemplo de Faker. Empiece siempre de forma sencilla y busque la solución más sencilla al problema. La complejidad llegará cuando sea necesaria, por lo que si la herencia básica funciona, no hay necesidad de implementar un decorador ni nada más. Cuando amplíe el marco, asegúrese de no ir demasiado lejos, donde la pérdida supere la ganancia. No querrás terminar manteniendo una parte del marco por tu cuenta.