#Introducción
Cuando prueba su aplicación o llena su base de datos, puede necesitar insertar algunos registros en su base de datos. En lugar de especificar manualmente el valor de cada columna, Laravel le permite definir un conjunto de atributos predeterminados para cada uno de sus modelos Eloquent usando fábricas de modelos.
Para ver un ejemplo de cómo escribir una fábrica, eche un vistazo al archivo database/factories/UserFactory.php en su aplicación. Esta fábrica se incluye con todas las nuevas aplicaciones Laravel y contiene la siguiente definición de fábrica:
namespace Database\Factories;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
/**
* Definir el estado predeterminado del modelo.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
];
}
}
Como puede ver, en su forma más básica, las fábricas son clases que extienden la clase base de fábrica de Laravel y definen un método definition. El método definition devuelve el conjunto predeterminado de valores de atributos que se deben aplicar al crear un modelo usando la fábrica.
A través del helper fake, las fábricas tienen acceso a la biblioteca PHP Faker, que le permite generar cómodamente varios tipos de datos aleatorios para pruebas y llenado de datos.
Puede establecer la configuración regional de Faker de su aplicación agregando una opción faker_locale en su archivo de configuración config/app.php.
#Definiendo fábricas de modelos
#Generando fábricas
Para crear una fábrica, ejecute el comando Artisan make:factory:
php artisan make:factory PostFactory
La nueva clase de fábrica se colocará en su directorio database/factories.
#Convenciones para descubrimiento de modelos y fábricas
Una vez que haya definido sus fábricas, puede usar el método estático factory que sus modelos reciben a través del trait Illuminate\Database\Eloquent\Factories\HasFactory para instanciar una instancia de fábrica para ese modelo.
El método factory del trait HasFactory usará convenciones para determinar la fábrica adecuada para el modelo al que está asignado el trait. Específicamente, el método buscará una fábrica en el espacio de nombres Database\Factories que tenga un nombre de clase que coincida con el nombre del modelo y que termine con Factory. Si estas convenciones no se aplican a su aplicación o fábrica en particular, puede sobrescribir el método newFactory en su modelo para devolver directamente una instancia de la fábrica correspondiente al modelo:
use Illuminate\Database\Eloquent\Factories\Factory;
use Database\Factories\Administration\FlightFactory;
/**
* Crear una nueva instancia de fábrica para el modelo.
*/
protected static function newFactory(): Factory
{
return FlightFactory::new();
}
Luego, defina una propiedad model en la fábrica correspondiente:
use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;
class FlightFactory extends Factory
{
/**
* El nombre del modelo correspondiente a la fábrica.
*
* @var class-string<\Illuminate\Database\Eloquent\Model>
*/
protected $model = Flight::class;
}
#Estados de fábrica
Los métodos de manipulación de estados le permiten definir modificaciones discretas que pueden aplicarse a sus fábricas de modelos en cualquier combinación. Por ejemplo, su fábrica Database\Factories\UserFactory podría contener un método de estado suspended que modifique uno de sus valores de atributo predeterminados.
Los métodos de transformación de estado normalmente llaman al método state proporcionado por la clase base de fábrica de Laravel. El método state acepta un closure que recibirá el arreglo de atributos sin procesar definidos para la fábrica y debe devolver un arreglo de atributos a modificar:
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* Indicar que el usuario está suspendido.
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
});
}
#Estado "Trashed"
Si su modelo Eloquent puede ser soft deleted, puede invocar el método de estado incorporado trashed para indicar que el modelo creado ya debe estar "soft deleted". No necesita definir manualmente el estado trashed ya que está disponible automáticamente para todas las fábricas:
use App\Models\User;
$user = User::factory()->trashed()->create();
#Callbacks de fábrica
Los callbacks de fábrica se registran usando los métodos afterMaking y afterCreating y le permiten realizar tareas adicionales después de crear o persistir un modelo. Debe registrar estos callbacks definiendo un método configure en su clase de fábrica. Este método será llamado automáticamente por Laravel cuando se instancie la fábrica:
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
/**
* Configurar la fábrica del modelo.
*/
public function configure(): static
{
return $this->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}
// ...
}
También puede registrar callbacks de fábrica dentro de métodos de estado para realizar tareas adicionales específicas de un estado dado:
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* Indicar que el usuario está suspendido.
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
})->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}
#Creando modelos usando fábricas
#Instanciando modelos
Una vez que haya definido sus fábricas, puede usar el método estático factory que sus modelos reciben a través del trait Illuminate\Database\Eloquent\Factories\HasFactory para instanciar una instancia de fábrica para ese modelo. Veamos algunos ejemplos de creación de modelos. Primero, usaremos el método make para crear modelos sin persistirlos en la base de datos:
use App\Models\User;
$user = User::factory()->make();
Puede crear una colección de muchos modelos usando el método count:
$users = User::factory()->count(3)->make();
#Aplicando estados
También puede aplicar cualquiera de sus estados a los modelos. Si desea aplicar múltiples transformaciones de estado a los modelos, simplemente puede llamar directamente a los métodos de transformación de estado:
$users = User::factory()->count(5)->suspended()->make();
#Sobrescribiendo atributos
Si desea sobrescribir algunos de los valores predeterminados de sus modelos, puede pasar un arreglo de valores al método make. Solo los atributos especificados serán reemplazados mientras que el resto permanecerá con sus valores predeterminados según lo especificado por la fábrica:
$user = User::factory()->make([
'name' => 'Abigail Otwell',
]);
Alternativamente, el método state puede llamarse directamente en la instancia de la fábrica para realizar una transformación de estado en línea:
$user = User::factory()->state([
'name' => 'Abigail Otwell',
])->make();
La protección contra asignación masiva se desactiva automáticamente al crear modelos usando fábricas.
#Persistiendo modelos
El método create instancia modelos y los persiste en la base de datos usando el método save de Eloquent:
use App\Models\User;
// Crear una instancia única de App\Models\User...
$user = User::factory()->create();
// Crear tres instancias de App\Models\User...
$users = User::factory()->count(3)->create();
Puede sobrescribir los atributos predeterminados del modelo de la fábrica pasando un arreglo de atributos al método create:
$user = User::factory()->create([
'name' => 'Abigail',
]);
#Secuencias
A veces puede desear alternar el valor de un atributo dado del modelo para cada modelo creado. Puede lograr esto definiendo una transformación de estado como una secuencia. Por ejemplo, puede querer alternar el valor de una columna admin entre Y y N para cada usuario creado:
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;
$users = User::factory()
->count(10)
->state(new Sequence(
['admin' => 'Y'],
['admin' => 'N'],
))
->create();
En este ejemplo, se crearán cinco usuarios con un valor admin de Y y cinco usuarios con un valor admin de N.
Si es necesario, puede incluir un closure como valor de la secuencia. El closure se invocará cada vez que la secuencia necesite un nuevo valor:
use Illuminate\Database\Eloquent\Factories\Sequence;
$users = User::factory()
->count(10)
->state(new Sequence(
fn (Sequence $sequence) => ['role' => UserRoles::all()->random()],
))
->create();
Dentro de un closure de secuencia, puede acceder a las propiedades $index o $count en la instancia de secuencia que se inyecta en el closure. La propiedad $index contiene el número de iteraciones que han ocurrido hasta ahora, mientras que la propiedad $count contiene el número total de veces que la secuencia será invocada:
$users = User::factory()
->count(10)
->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index])
->create();
Para mayor comodidad, las secuencias también pueden aplicarse usando el método sequence, que simplemente invoca internamente el método state. El método sequence acepta un closure o arreglos de atributos secuenciados:
$users = User::factory()
->count(2)
->sequence(
['name' => 'First User'],
['name' => 'Second User'],
)
->create();
#Relaciones en fábricas
#Relaciones Has Many
A continuación, exploremos cómo construir relaciones de modelos Eloquent usando los métodos fluidos de fábrica de Laravel. Primero, supongamos que nuestra aplicación tiene un modelo App\Models\User y un modelo App\Models\Post. Además, supongamos que el modelo User define una relación hasMany con Post. Podemos crear un usuario que tenga tres posts usando el método has proporcionado por las fábricas de Laravel. El método has acepta una instancia de fábrica:
use App\Models\Post;
use App\Models\User;
$user = User::factory()
->has(Post::factory()->count(3))
->create();
Por convención, al pasar un modelo Post al método has, Laravel asumirá que el modelo User debe tener un método posts que defina la relación. Si es necesario, puede especificar explícitamente el nombre de la relación que desea manipular:
$user = User::factory()
->has(Post::factory()->count(3), 'posts')
->create();
Por supuesto, puede realizar manipulaciones de estado en los modelos relacionados. Además, puede pasar una transformación de estado basada en un closure si su cambio de estado requiere acceso al modelo padre:
$user = User::factory()
->has(
Post::factory()
->count(3)
->state(function (array $attributes, User $user) {
return ['user_type' => $user->type];
})
)
->create();
#Usando métodos mágicos
Para mayor comodidad, puede usar los métodos mágicos de relaciones de fábrica de Laravel para construir relaciones. Por ejemplo, el siguiente ejemplo usará la convención para determinar que los modelos relacionados deben crearse a través de un método de relación posts en el modelo User:
$user = User::factory()
->hasPosts(3)
->create();
Al usar métodos mágicos para crear relaciones de fábrica, puede pasar un arreglo de atributos para sobrescribir en los modelos relacionados:
$user = User::factory()
->hasPosts(3, [
'published' => false,
])
->create();
Puede proporcionar una transformación de estado basada en un closure si su cambio de estado requiere acceso al modelo padre:
$user = User::factory()
->hasPosts(3, function (array $attributes, User $user) {
return ['user_type' => $user->type];
})
->create();
#Relaciones Belongs To
Ahora que hemos explorado cómo construir relaciones "has many" usando fábricas, exploremos la inversa de la relación. El método for puede usarse para definir el modelo padre al que pertenecen los modelos creados por la fábrica. Por ejemplo, podemos crear tres instancias del modelo App\Models\Post que pertenecen a un solo usuario:
use App\Models\Post;
use App\Models\User;
$posts = Post::factory()
->count(3)
->for(User::factory()->state([
'name' => 'Jessica Archer',
]))
->create();
Si ya tiene una instancia del modelo padre que debe asociarse con los modelos que está creando, puede pasar la instancia del modelo al método for:
$user = User::factory()->create();
$posts = Post::factory()
->count(3)
->for($user)
->create();
#Usando métodos mágicos
Para mayor comodidad, puede usar los métodos mágicos de relaciones de fábrica de Laravel para definir relaciones "belongs to". Por ejemplo, el siguiente ejemplo usará la convención para determinar que los tres posts deben pertenecer a la relación user en el modelo Post:
$posts = Post::factory()
->count(3)
->forUser([
'name' => 'Jessica Archer',
])
->create();
#Relaciones Many to Many
Al igual que las relaciones has many, las relaciones "many to many" pueden crearse usando el método has:
use App\Models\Role;
use App\Models\User;
$user = User::factory()
->has(Role::factory()->count(3))
->create();
#Atributos de tabla pivote
Si necesita definir atributos que deben establecerse en la tabla pivote/intermedia que enlaza los modelos, puede usar el método hasAttached. Este método acepta un arreglo de nombres y valores de atributos de la tabla pivote como segundo argumento:
use App\Models\Role;
use App\Models\User;
$user = User::factory()
->hasAttached(
Role::factory()->count(3),
['active' => true]
)
->create();
Puede proporcionar una transformación de estado basada en un closure si su cambio de estado requiere acceso al modelo relacionado:
$user = User::factory()
->hasAttached(
Role::factory()
->count(3)
->state(function (array $attributes, User $user) {
return ['name' => $user->name.' Role'];
}),
['active' => true]
)
->create();
Si ya tiene instancias de modelos que desea adjuntar a los modelos que está creando, puede pasar las instancias de modelo al método hasAttached. En este ejemplo, los mismos tres roles se adjuntarán a los tres usuarios:
$roles = Role::factory()->count(3)->create();
$user = User::factory()
->count(3)
->hasAttached($roles, ['active' => true])
->create();
#Usando métodos mágicos
Para mayor comodidad, puede usar los métodos mágicos de relaciones de fábrica de Laravel para definir relaciones many to many. Por ejemplo, el siguiente ejemplo usará la convención para determinar que los modelos relacionados deben crearse a través de un método de relación roles en el modelo User:
$user = User::factory()
->hasRoles(1, [
'name' => 'Editor'
])
->create();
#Relaciones polimórficas
Las relaciones polimórficas también pueden crearse usando fábricas. Las relaciones polimórficas "morph many" se crean de la misma manera que las relaciones típicas "has many". Por ejemplo, si un modelo App\Models\Post tiene una relación morphMany con un modelo App\Models\Comment:
use App\Models\Post;
$post = Post::factory()->hasComments(3)->create();
#Relaciones Morph To
No se pueden usar métodos mágicos para crear relaciones morphTo. En su lugar, debe usarse directamente el método for y debe proporcionarse explícitamente el nombre de la relación. Por ejemplo, imagine que el modelo Comment tiene un método commentable que define una relación morphTo. En esta situación, podemos crear tres comentarios que pertenecen a un solo post usando directamente el método for:
$comments = Comment::factory()->count(3)->for(
Post::factory(), 'commentable'
)->create();
#Relaciones polimórficas Many to Many
Las relaciones polimórficas "many to many" (morphToMany / morphedByMany) pueden crearse igual que las relaciones "many to many" no polimórficas:
use App\Models\Tag;
use App\Models\Video;
$videos = Video::factory()
->hasAttached(
Tag::factory()->count(3),
['public' => true]
)
->create();
Por supuesto, el método mágico has también puede usarse para crear relaciones polimórficas "many to many":
$videos = Video::factory()
->hasTags(3, ['public' => true])
->create();
#Definiendo relaciones dentro de fábricas
Para definir una relación dentro de su fábrica de modelos, normalmente asignará una nueva instancia de fábrica a la clave foránea de la relación. Esto normalmente se hace para relaciones "inversas" como belongsTo y morphTo. Por ejemplo, si desea crear un nuevo usuario al crear un post, puede hacer lo siguiente:
use App\Models\User;
/**
* Definir el estado predeterminado del modelo.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}
Si las columnas de la relación dependen de la fábrica que las define, puede asignar un closure a un atributo. El closure recibirá el arreglo de atributos evaluados de la fábrica:
/**
* Definir el estado predeterminado del modelo.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'user_type' => function (array $attributes) {
return User::find($attributes['user_id'])->type;
},
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}
#Reciclando un modelo existente para relaciones
Si tiene modelos que comparten una relación común con otro modelo, puede usar el método recycle para asegurar que una sola instancia del modelo relacionado se recicle para todas las relaciones creadas por la fábrica.
Por ejemplo, imagine que tiene modelos Airline, Flight y Ticket, donde el ticket pertenece a una aerolínea y a un vuelo, y el vuelo también pertenece a una aerolínea. Al crear tickets, probablemente querrá la misma aerolínea para el ticket y para el vuelo, por lo que puede pasar una instancia de aerolínea al método recycle:
Ticket::factory()
->recycle(Airline::factory()->create())
->create();
Puede encontrar el método recycle particularmente útil si tiene modelos que pertenecen a un usuario o equipo común.
El método recycle también acepta una colección de modelos existentes. Cuando se proporciona una colección al método recycle, se elegirá un modelo aleatorio de la colección cuando la fábrica necesite un modelo de ese tipo:
Ticket::factory()
->recycle($airlines)
->create();