Eloquent: Fábricas
Introdução
Ao testar sua aplicação ou ao semear seu banco de dados, você pode precisar inserir alguns registros. Em vez de especificar manualmente o valor de cada coluna, Laravel permite definir um conjunto de atributos padrão para cada um de seus modelos Eloquent usando fábricas de modelos.
Para ver um exemplo de como escrever uma fábrica, veja o arquivo database/factories/UserFactory.php
na sua aplicação. Esta fábrica vem com todos os novos aplicativos Laravel e contém a seguinte definição da:
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
*/
class UserFactory extends Factory
{
/**
* A senha atual usada pela fábrica.
*/
protected static ?string $password;
/**
* Defina o estado padrão do modelo.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'email_verified_at' => now(),
'password' => static::$password ??= Hash::make('password'),
'remember_token' => Str::random(10),
];
}
/**
* Indique que o endereço de e-mail do modelo não deve ser verificado.
*/
public function unverified(): static
{
return $this->state(fn (array $attributes) => [
'email_verified_at' => null,
]);
}
}
Como você pode ver, na sua forma mais básica, as fábricas são classes que estendem a classe de fábrica base do Laravel e definem um método definition
. O método definition
retorna o conjunto padrão de valores de atributos que devem ser aplicados quando se cria um modelo usando a fábrica.
Através do helper fake
, as fábricas têm acesso à biblioteca PHP Faker, que permite gerar dados aleatórios de forma conveniente para testes e inicialização de bancos de dados.
Nota
Você pode alterar o idioma do seu aplicativo Faker atualizando a opção faker_locale
no arquivo de configuração config/app.php
.
Definindo fábricas de modelos
Gerando Fábricas
Para criar uma fábrica, execute o comando make:factory
no Artisan:
php artisan make:factory PostFactory
A nova classe será colocada em seu diretório de banco de dados database/factories
.
Convenções de Descoberta de Modelos e Fábricas
Depois de definir suas fábricas, você pode usar o método estático factory
fornecido por seus modelos pela trait Illuminate\Database\Eloquent\Factories\HasFactory
para criar uma instância da fábrica desse modelo.
O método factory
da trait HasFactory
usará convenções para determinar a fábrica apropriada para o modelo ao qual ela é atribuída. Especificamente, o método procurará uma fábrica no namespace Database\Factories
que tenha um nome de classe correspondente ao nome do modelo e com um sufixo Factory
. Se essas convenções não se aplicarem a seu projeto ou à sua fábrica específica, você pode substituir o método newFactory
no seu modelo para retornar uma instância da fábrica correspondente diretamente.
use Illuminate\Database\Eloquent\Factories\Factory;
use Database\Factories\Administration\FlightFactory;
/**
* Crie uma nova instância de fábrica para o modelo.
*/
protected static function newFactory(): Factory
{
return FlightFactory::new();
}
Então, defina uma propriedade model
na fábrica correspondente:
use App\Administration\Flight;
use Illuminate\Database\Eloquent\Factories\Factory;
class FlightFactory extends Factory
{
/**
* O nome do modelo correspondente da fábrica.
*
* @var class-string<\Illuminate\Database\Eloquent\Model>
*/
protected $model = Flight::class;
}
Estados da Fábrica
Os métodos de manipulação de estado permitem que você defina modificações discretas que podem ser aplicadas às suas fábricas de modelo em qualquer combinação. Por exemplo, sua fábrica Database\Factories\UserFactory
pode conter um método suspended
que modifica o valor padrão de um de seus atributos.
Os métodos de transformação do estado normalmente chamam o método state
fornecido pela classe de fábrica base do Laravel. O método state
aceita um closure que receberá o array de atributos brutos definidos para a fábrica e deve retornar um array de atributos para modificar:
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* Indica que o usuário está suspenso.
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
});
}
Estado "Trashed"
Se seu modelo Eloquent puder ser soft deleted, você pode invocar o método de estado trashed
integrado para indicar que o modelo criado já deve ter sido "soft deleted". Você não precisa definir manualmente o estado trashed
, pois ele está automaticamente disponível para todas as fábricas:
use App\Models\User;
$user = User::factory()->trashed()->create();
Callback de fábrica
Os callbacks da fábrica são registrados usando os métodos afterMaking
e afterCreating
e permitem que você execute tarefas adicionais após fazer ou criar um modelo. Você deve registrar esses callbacks definindo um método configure na classe da fábrica. Este método será automaticamente chamado pelo Laravel quando a fábrica for instanciada.
namespace Database\Factories;
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class UserFactory extends Factory
{
/**
* Configurar a fábrica de modelos.
*/
public function configure(): static
{
return $this->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}
// ...
}
Você também pode registrar retornos de fábrica dentro métodos de estado para realizar tarefas adicionais específicas para um determinado estado:
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;
/**
* Indica que o usuário está suspenso.
*/
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => 'suspended',
];
})->afterMaking(function (User $user) {
// ...
})->afterCreating(function (User $user) {
// ...
});
}
Criando Modelos Usando Fábricas
Instanciando Modelos
Uma vez que você tenha definido suas fábricas, você pode usar o método estático factory
fornecido aos seus modelos pela trait Illuminate\Database\Eloquent\Factories\HasFactory
a fim de instanciar uma instância da fábrica para esse modelo. Vamos dar um olhada em alguns exemplos de criação de modelos. Primeiro, vamos usar o método make
para criar modelos sem persistir no banco:
use App\Models\User;
$user = User::factory()->make();
Você pode criar uma coleção de vários modelos usando o método count
.
$users = User::factory()->count(3)->make();
Aplicando Estados
Você também pode aplicar qualquer um dos seus estados nos modelos. Se você gostaria de aplicar múltiplas transformações do estado aos modelos, você simplesmente pode chamar os métodos de transformação do estado diretamente:
$users = User::factory()->count(5)->suspended()->make();
Sobrescrevendo Atributos
Se você gostaria de substituir alguns dos valores padrão do seu modelo, você pode passar um array de valores para o método make
. Somente os atributos especificados serão substituídos enquanto o restante dos atributos permanecerão com seus valores padrão como especificado pela fábrica.
$user = User::factory()->make([
'name' => 'Abigail Otwell',
]);
Alternativamente, o método state
pode ser chamado diretamente na instância de fábrica para executar uma transformação de estado inline:
$user = User::factory()->state([
'name' => 'Abigail Otwell',
])->make();
NOTA
Proteção de atribuição em massa é automaticamente desativado ao criar modelos usando fábricas.
Modelos Persistentes
O método create
instancia as instâncias do modelo e persiste-as no banco de dados usando o método save
do Eloquent.
use App\Models\User;
// Crie uma única instância App\Models\User...
$user = User::factory()->create();
// Crie três instâncias de App\Models\User...
$users = User::factory()->count(3)->create();
Você pode sobrescrever os atributos padrão do modelo da fábrica passando uma matriz de atributos para o método create
:
$user = User::factory()->create([
'name' => 'Abigail',
]);
Sequências
Às vezes você pode desejar alternar o valor de um atributo de modelo dado para cada modelo criado. Você pode alcançar isso definindo uma transformação de estado como uma sequência. Por exemplo, você pode querer alternar o valor de uma coluna admin
entre Y
e N
por cada usuário criado:
use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Sequence;
$users = User::factory()
->count(10)
->state(new Sequence(
['admin' => 'Y'],
['admin' => 'N'],
))
->create();
Neste exemplo, serão criados 5 usuários com um valor de admin
igual a Y
e 5 usuários com um valor de admin
igual a N
.
Se necessário, você pode incluir um closure como o valor da sequência. O closure será invocado sempre que a sequência precisar de um novo 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 uma sequência de closure, você pode acessar as propriedades $index
ou $count
na instância da sequência injetada no closure. A propriedade $index
contém o número de iterações pela sequência que ocorreram até agora, enquanto a propriedade $count
contém o número total de vezes que a sequência será invocada:
$users = User::factory()
->count(10)
->sequence(fn (Sequence $sequence) => ['name' => 'Name '.$sequence->index])
->create();
Para maior comodidade, sequências também podem ser aplicadas usando o método sequence
, que simplesmente invoca o método interno state
. O método sequence
aceita um closure ou uma matriz de atributos sequenciais.
$users = User::factory()
->count(2)
->sequence(
['name' => 'First User'],
['name' => 'Second User'],
)
->create();
Relações de fábrica
Relação Has Many
Em seguida vamos explorar o modo de fazer relações em Eloquent usando os métodos da fábrica do Laravel. Primeiro, vamos supor que nosso aplicativo tem um modelo App\Models\User
e outro modelo chamado App\Models\Post
. Além disso vamos supor que o modelo User
define uma relação hasMany
com o modelo Post
. Podemos criar um usuário com três postagens usando o método has
fornecido pelas fábricas do Laravel. O método has
aceita uma instância de fábrica:
use App\Models\Post;
use App\Models\User;
$user = User::factory()
->has(Post::factory()->count(3))
->create();
Por convenção, ao passar um modelo Post
para o método has
, o Laravel supõe que o modelo User
deve ter um método posts
que define a relação. Se necessário, você pode especificar explicitamente o nome da relação que gostaria de manipular:
$user = User::factory()
->has(Post::factory()->count(3), 'posts')
->create();
É claro que você pode executar manipulações de estado nos modelos relacionados. Além disso, você pode passar uma transformação de estado baseada em um closure se sua alteração de estado exigir acesso ao modelo pai:
$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 conveniência, você pode usar o método de relacionamento da fábrica mágica do Laravel para construir relacionamentos. Por exemplo, o seguinte exemplo usará a convenção para determinar que os modelos relacionados devem ser criados através do método de relacionamento posts
no modelo User
:
$user = User::factory()
->hasPosts(3)
->create();
Quando usando métodos mágicos para criar relações de fábrica, você pode passar um array de atributos para substituir na classe relacionada.
$user = User::factory()
->hasPosts(3, [
'published' => false,
])
->create();
Você pode fornecer uma transformação de estado baseada em closure se sua mudança de estado requer acesso ao modelo pai:
$user = User::factory()
->hasPosts(3, function (array $attributes, User $user) {
return ['user_type' => $user->type];
})
->create();
Relacionamento Belongs To
Agora que exploramos como construir relações "has many" usando fábricas, vamos explorar o inverso da relação. O método for
pode ser usado para definir o modelo pai do qual as instâncias criadas pela fábrica pertencem. Por exemplo, podemos criar três instâncias do modelo App\Models\Post
que pertencem a um único usuário:
use App\Models\Post;
use App\Models\User;
$posts = Post::factory()
->count(3)
->for(User::factory()->state([
'name' => 'Jessica Archer',
]))
->create();
Se você já tem uma instância de modelo pai que deve ser associada aos modelos que está criando, você pode passar a instância do modelo para o método for
:
$user = User::factory()->create();
$posts = Post::factory()
->count(3)
->for($user)
->create();
Usando Métodos Mágicos
Para conveniência, você pode usar os métodos de relacionamento do fábrica de mágica Laravel para definir relacionamentos "belongs to". O seguinte exemplo usará a convenção para determinar que os três posts devem "pertencer" ao user
na relação do modelo Post
:
$posts = Post::factory()
->count(3)
->forUser([
'name' => 'Jessica Archer',
])
->create();
Relacionamento Many to Many
Assim como relacionamentos has many, as relações "many to many" podem ser criadas usando o método has
:
use App\Models\Role;
use App\Models\User;
$user = User::factory()
->has(Role::factory()->count(3))
->create();
Atributos de Tabela Pivô
Se você precisa definir atributos que devem ser definidos na tabela pivô ou intermediária que liga os modelos, você pode usar o método hasAttached
. Este método aceita um array de nomes de atributos da tabela pivô e valores como seu segundo argumento:
use App\Models\Role;
use App\Models\User;
$user = User::factory()
->hasAttached(
Role::factory()->count(3),
['active' => true]
)
->create();
Você pode fornecer um estado de transformação baseado em closure se o seu estado de mudança requer acesso ao modelo relacionado:
$user = User::factory()
->hasAttached(
Role::factory()
->count(3)
->state(function (array $attributes, User $user) {
return ['name' => $user->name.' Role'];
}),
['active' => true]
)
->create();
Se você já tem instâncias do modelo que gostaria de anexar aos modelos que está criando, você pode passar as instâncias do modelo para o método hasAttached
. Neste exemplo, os mesmos três papéis serão anexados a todos os três usuários:
$roles = Role::factory()->count(3)->create();
$user = User::factory()
->count(3)
->hasAttached($roles, ['active' => true])
->create();
Usando Métodos Mágicos
Para conveniência, você pode usar os métodos de relações mágicas do Laravel para definir relações muitos-para-muitos. Por exemplo, o seguinte exemplo usará a convenção para determinar que os modelos relacionados devem ser criados por meio de um método de relação roles
no modelo User
:
$user = User::factory()
->hasRoles(1, [
'name' => 'Editor'
])
->create();
Relações Polimórficas
Relações polimórficas também podem ser criadas usando fábricas. Relações polimórficas "morph many" são criados da mesma maneira que relações "has many" típicas. Por exemplo, se um modelo App\Models\Post
tem uma relação morphMany
com um modelo App\Models\Comment
:
use App\Models\Post;
$post = Post::factory()->hasComments(3)->create();
Transformar em relacionamentos
Métodos mágicos não podem ser utilizados para criar relacionamentos morphTo
. Em vez disso, o método for
deve ser utilizado diretamente e o nome do relacionamento deve ser fornecido explicitamente. Por exemplo, imagine que o modelo Comment
tenha um método commentable
que define um relacionamento morphTo
. Neste caso, podemos criar três comentários que pertencem a uma única postagem usando o método for
diretamente:
$comments = Comment::factory()->count(3)->for(
Post::factory(), 'commentable'
)->create();
Relacionamentos polimórficos muitos para muitos
As relações polimórficas "muitos para muitos" (morphToMany
/ morphedByMany
) podem ser criadas exatamente como as relações não-polimórficas "muitos para muitos":
use App\Models\Tag;
use App\Models\Video;
$videos = Video::factory()
->hasAttached(
Tag::factory()->count(3),
['public' => true]
)
->create();
Claro, o método mágico has
também pode ser usado para criar relacionamentos polimórficos "muitos a muitos":
$videos = Video::factory()
->hasTags(3, ['public' => true])
->create();
Definindo relacionamentos dentro das fábricas
Para definir uma relação dentro de sua fábrica de modelos, normalmente atribuirá um novo caso de fábrica para a chave estrangeira da relação. Isso é normalmente feito para as "relações inversas" tais como relações belongsTo
e morphTo
. Por exemplo, se você gostaria de criar um novo usuário ao criar uma nova postagem, você pode fazer o seguinte:
use App\Models\User;
/**
* Defina o estado padrão do modelo.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory(),
'title' => fake()->title(),
'content' => fake()->paragraph(),
];
}
Se as colunas do relacionamento dependerem da fábrica que o define, você pode atribuir um closure a um atributo. O closure receberá o array de atributos avaliados da fábrica:
/**
* Defina o estado padrão do 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(),
];
}
Reutilizando um Modelo Existente para Relações
Se você tem modelos que compartilham uma relação comum com outro modelo, você pode usar o método recycle
para garantir uma única instância do modelo relacionado é reciclado para todas as relações criadas pela fábrica.
Por exemplo, imagine que você tem Airline
, Flight
e Ticket
como modelos onde o ticket pertence a uma companhia aérea e um voo, e o voo também pertence a uma companhia aérea. Ao criar os tickets, provavelmente você vai querer a mesma companhia aérea para ambos os casos, então você pode passar uma instância da companhia aérea para o método recycle
:
Ticket::factory()
->recycle(Airline::factory()->create())
->create();
O método recycle
pode ser especialmente útil se você tiver modelos pertencentes a um usuário ou equipe comum.
O método recycle
também aceita uma coleção de modelos existentes. Quando uma coleção é fornecida ao método recycle
, um modelo aleatório dessa coleção será escolhido quando a fábrica precisar de um modelo desse tipo:
Ticket::factory()
->recycle($airlines)
->create();