Foto de um elefante com um fundo preto e o logo do site Laravel News anunciando o Laravel 8

Novidades do Laravel 8: Models e Factories


novidades laravel-8

A nova versão do Laravel -a versão 8.0- foi lançada no último dia 08 e conta com diversas novidades. Separei dois tópicos interessantes sobre a nova versão para entendermos um pouco mais: Models e Models Factory, bora lá?

Antes de mais nada sugiro que dêm uma olhada no post oficial de lançamento desta versão, lá vocês poderão encontrar esses e os demais recursos presentes na nova versão assim como a documentação oficial, documentação esta que contém um guia de como fazer a migração das versões 7.x para a 8.

Aqui no blog vou cobrir todos os pontos relevantes desta nova versão então basta ficar de olho na tag novidades para ver todos os posts relacionados. E finalmente vamos ao que interessa!

Diretório para as Models

Uma feature que a comunidade já pedia a um bom tempo era que as Models do Eloquent tivesse um lugar para chamar de seu. Se por acaso você nunca modificou a estrutura de um projeto Laravel muito provavelmente ao gerar Models suas classes simplesmente apareciam "meio que jogadas" no diretório app/ da aplicação e era necessário movê-las/criá-las manualmente ou modificar o comportamento padrão do artisan para gerá-las no diretório desejado.

Na versão 8 o Laravel atendeu ao pedido da comunidade e por convenção foi adotado o diretório app/Models para agregar as Models geradas pelo artisan. É importante ressaltar que todos os comandos relacionados foram alterados para assumir que o diretório app/Models existe e estará sendo usado, caso contrário o framework irá assumir que o diretório padrão ainda é o antigo, ou seja, o diretório app/.

Models Factory

Ainda relacionado as Models, um recurso muito utilizado são as factories , geralmente relacionadas a execução de testes como o próprio Laravel se refere em sua documentação. Acontece que em suas versões anteriores as factories eram apenas funções e causavam a sensação de estarem meio "desatualizadas" com relação ao restante do framework (Que nos últimos anos vem se preocupando bastante com orientação a objetos.) Um arquivo de factory se parecia com isso:

/** @var  \Illuminate\Database\Eloquent\Factory $factory */

use App\Tweets;
use Faker\Generator as Faker;

$factory->define(Tweets::class, function (Faker $faker) {
    return [
        'tweet' => $faker->text(140),
        'user_id' => $faker->numberBetween(1, 100)
    ];
});

Sim, este é o arquivo completo.

Nesta factory estamos definindo que ao ser executada será criado um Tweet com um texto fake de 140 caracteres e atrelar este Tweet a um usuário aleatório de id entre 1 e 100.

Nesta nova atualização as model factories foram totalmente reescritas e agora são classes (aeeeeee!), além disso foi adicionado suporte a relacionamentos primários, o mesmo exemplo anterior agora se parece com isso:

namespace Database\Factories;

use App\Models\Tweet;
use Illuminate\Database\Eloquent\Factories\Factory;

class TweetFactory extends Factory
{
    /**
     * The name of the factory's corresponding model.
     *
     * @var  string
     */
    protected $model = Tweet::class;

    /**
     * Define the model's default state.
     *
     * @return  array
     */
    public function definition()
    {
        return [
            'tweet' => $this->faker->text(140),
            'user_id' => $this->faker->numberBetween(1, 100)
        ];
    }
}

Basicamente é uma classe auto-gerada que extende da classe Factory do Laravel que por sua vez contém a propriedade faker para utilizarmos como gerador automático para alguns tipos de dados. Na propriedade model é definido a qual Model a factory se refere e no método definition() retornamos o que anteriormente era o corpo da closure.

Utilização das Factories

Outro ponto interessante e que vale ser ressaltado é que agora podemos utilizar uma factory à partir da nossa Model, para isso precisamos adicionar na Model em questão a Trait:

Illuminate\Database\Eloquent\Factories\HasFactory

Nesta Trait há um método estático chamado factory e ao ser utilizado o Laravel se encarregará de encontrar a factory correspondente. Para encontrar a factory correta o framework irá buscar no diretório Database\Factories se existe uma classe com o mesmo nome da model utilizada e o sufixo Factory. Se por acaso não utilizarmos esta conversão de nomeclaturas podemos "ensinar" o Laravel a encontrar a factory correta, para isso basta sobrescrever o método newFactory na model em questão para retornar a classe correta, então vamos supor que para nossa model Tweet utilizaremos a factory FakeTweets:

/**
 *
 * @return  \Illuminate\Database\Eloquent\Factories\Factory
 */
protected static function newFactory()
{
    return \App\Fakery\FakeTweets::new();
}

Para utilizar a factory em questão só precisamos então chamar nossa Model e seu método factory, segue um exemplo de como podemos utilizar este recurso atualmente:

// para utilizar a Model sem haver persistência em banco
$tweet = Tweet::factory()->make();
// para utilizar a Model com persistência em banco, como seria num seeder por exemplo
Tweet::factory()->count(10)->create();

Factory States

Ainda sobre as factories um recurso interessante que está sendo lançado nesta versão são os "estados" para factories. Basicamente as factories poderão utilizar alguns métodos que podemos escrever e que modifique alguma informação no momento de sua utilização. Vamos supor que nossa model Tweet tenha a propriedade featured que é basicamente uma flag true ou false que indique se este tweet está em destaque, com este novo recurso uma factory pode definir um método que quando utilizado irá modificar a propriedade featured:

// classe TweetFactory.php
public function featured()
{
    return $this->state([
        'featured' => true,
    ]);
}

Ao utilizar esta factory podemos indicar se queremos ou não modificar o "estado" deste tweet colocando-o como em destaque ou não, para isso basta encadear este método na utilização da factory:

// para criar 3 Tweets marcados como featured:
Tweet::factory()->count(3)->featured()->create();
// para criar 3 tweets sem estarem marcados como featured:
Tweet::factory()->count(3)->create();

Este recurso se torna interessante pelo fato de evitar a criação de duas factories diferentes apenas para modificar um "pedaço pequeno" de registro. Em uma única factory agora conseguimos definir, por exemplo, se queremos um tweet curto ou longo, em destaque ou não:

// classe TweetFactory.php
public function shortTweet()
{
    return $this->state([
        'tweet' => $this->faker->text(70)
    ]);
}

public function featured()
{
    return $this->state([
        'featured' => true,
    ]);
}

E a partir disso podemos criar Models ou persistências de forma mais dinâmica alterando comportamentos padrões:

// criar 5 tweets que não estão em destaque
Tweet::factory()->count(5)->create();
// criar 5 tweets que estão em destaque
Tweet::factory()->count(5)->featured()->create();
// criar 5 tweets curtos e que estão em destaque
Tweet::factory()->count(5)->shortTweet()->featured()->create();

De qualquer maneira podemos criar métodos de "estado" nas nossas factories apenas por conveniência, o Laravel nos dá a possibilidade de utilizarmos o recurso de "estados" mesmo que não tenhamos definido os métodos na classe original:

Tweet::factory()
    ->state([
        'featured' => true,
    ])
    ->create();

Factory Callbacks

E como não poderia parar por aí, mais um recurso relacionado as factories adicionados nesta versão são os callbacks para factories.

Os callbacks para factories são registrados usando os métodos afterMaking e afterCreating e permitem que executemos algumas ações depois de fazer (make) ou criar (create) uma Model. Estes callbacks devem ser escritos dentro do método configure na factory em questão e o Laravel se encarregará de chamar estes métodos nos momentos corretos. Vamos supor que toda vez que uma factory criar (create) ou fazer (make) um tweet nós queremos gerar um log de debug sobre isso, vamos ver como os callbacks poderiam nos ajudar:

public function configure()
{
    return $this->afterCreating(function (Tweet $tweet) {
        Log::debug('Um tweet foi criado');
    })
    ->afterMaking(function (Tweet $tweet) {
        Log::debug('Um tweet foi feito');
    });
}

Desta forma toda vez que os métodos make() ou create() forem chamados um log de debug será criado. Aqui utilizei apenas para criar um log mas toda criatividade é bem-vinda para extrair ao máximo o poder desta nova funcionalidade!

Sequências

Durante a utilização das factories pode ser que seja necessário utilizar uma sequência específica de registros ou que desejemos alternar o estado de alguns registros. Para tal podemos definir o "estado" da factory utilizando uma instância de Sequence, vamos supor que queremos criar 20 tweets alternados entre "em destaque" ou não (flag featured como true ou false), bastaria utilizar o "estado" da seguinte maneira:

Tweet::factory()
    ->count(20)
    ->state(new Sequence(
        ['featured' => true],
        ['featured'=> false]
    ))
    ->create();

Com isso serão criados 20 Tweets alternando a propriedade featured como true e false entre os registros criados.

Factory Relationships

Como se já não estivéssemos cheios de novidades relacionadas as Models e Factories, o Laravel não para! A partir da versão 8 as factories terão suporte a relacionamentos \o/

Relacionamentos através de "Definitions"

Podemos querer atrelar relacionamentos a nossa Model utilizando o método definition da nossa factory. Por exemplo, se quisermos atrelar um User a um Tweet seria assim:

public function definition()
{
    return [
        'tweet' => $this->faker->text(140),
        'user_id' => User::factory()
    ];
}

E como poderíamos imaginar, podemos também utilizar as demais facilidades das factories como os "estados" na definição do relacionamento:

public function definition()
{
    return [
        'tweet' => $this->faker->text(140),
        'user_id' => User::factory()->state(['type' => 'verified'])
    ];
}

E se por acaso precisássemos utilizar uma propriedade da Model do relacionamento em nossa factory? Bom, o Laravel se adiantou nessa e nos permite utilizar uma função para "resolver" o relacionamento recém criado e utilizar uma de suas propriedades:

public function definition()
{
    return [
        'tweet' => $this->faker->text(140),
        'user_id' => User::factory()->state(['type' => 'unverified']),
        'user_type' => function (array $attributes) {
            return User::find($attributes['user_id'])->type;
        }
    ];
}

Uma ressalva importante a ser feita é que caso utilizemos esta forma de criar relacionamentos através das factories, mesmo que usássemos o método make em Tweet, o usuário (da model User) será criado no banco de dados e não em memória, então precisamos tomar cuidado para não "sujarmos" o banco da nossa aplicação ao nos utilizarmos deste recurso.

Relacionamentos do Eloquent

Quem já está acostumado com o Eloquent e gosta da maneira como o Laravel define relacionamentos agora tem ainda mais razões para a adoração! Poderemos utilizar a forma como o definimos relacionamentos nas Models também nas factories, ou seja, poderemos utilizar o has e belongsTo por exemplo.

Relacionamentos Has Many

Vamos explorar os relacionamentos do Eloquent e ver como podemos utilizá-los em conjunto com os métodos fluentes do Laravel.

No cenário atual podemos assumir que um User tenha vários (hasMany) Tweets, desta forma podemos utilizar as factories de acordo com os relacionamentos definidos nas Models. Então vamos ver como utilizar a factory de usuários para criar um usuário já com dois tweets:

User::factory()
    ->has(Tweet::factory()->count(2), 'tweets')
    ->create();

Pronto! Usuário criado e dois tweets também criados e atrelados ao usuário em questão.

Um ponto importante é a utilização do segundo parâmetro no método has(), neste caso utilizamos 'tweets'. Este é um parâmetro opcional e serve para indicar o nome do relacionamento da model em questão caso não estivermos utilizando as nomeclaturas padrão do Laravel, massa né!?

Obviamente podemos utilizar todos os recursos das factories, como os "estados", em conjunto com o relacionamento:

User::factory()
    ->has(Tweet::factory()->count(2)->shortTweet()->featured(), 'tweets')
    ->create();

E ainda tem mais! Se quisermos deixar a coisa ainda mais "limpa" existem alguns métodos mágicos que podem ser utilizados, no exemplo do hasMany que utilizamos anteriormente, poderíamos definir da seguinte forma:

User::factory()
    ->hasTweets(5)
    ->create();

E para utilizar os "estados" desta forma? Muito fácil! Basta adicionarmos um segundo parâmetro ao relacionamento contendo um array:

User::factory()
    ->hasTweets(5, ['featured' => true])
    ->create();

Relacionamentos BelongsTo

Bom, agora você deve estar se perguntando e quanto ao inverso do relacionamento, afinal um tweet pertence a um usuário, correto? Para ficar ainda mais "fluída" a escrita dos métodos, para representar o relacionamento belongsTo o Laravel criou o método for (para). Vamos ver como ficaria na nossa aplicação com o relacionamento inverso então, vamos utilizar a factory referente a model Tweet e relacionar com o User utilizando for.

Tweet::factory()
    ->count(5)
    ->for(User::factory())
    ->create();

E aí novamente podemos utilizar as facilidades das factories para compor nosso User e brincar a vontade. Entendo que este ponto já ficou claro então não vou poluir ainda mais este post com exemplos de "estados", belezinha?

Assim como no hasMany existe o método mágico has para deixar ainda mais "limpa" a sintaxe, para o belongsTo poderemos utilizar o for também, vamos ver como é que fica:

Tweet::factory()
    ->count(2)
    ->forUser()
    ->create();

Basicamente todos relacionamentos do Eloquent possuem "representantes" nas factories, caso queira entender como funciona o Many to Many ou Relacionamentos Polimórficos deixei aqui os links, mas caso queira poupar um pouco de tempo já deixo adiantado: é basicamente o que já vimos =)

Se você curtiu alguma destas novas funcionalidades do Laravel 8, gostaria de ver mais novidades desta nova versão ou quiser sugerir um tópico não exite em me dar um ping no Twitter .

-- Up the Laravel's \o/

← Post Anterior
Próximo Post →