
Artigo atualizado a 20 de fevereiro de 2022 com a nova versão Laravel 9
O Laravel Eloquent é uma das funcionalidades centrais da framework Laravel, em boa parte graças ao seu suporte para definir, criar e gerir as relações entre tabelas de dados. Neste tutorial mostro-te como criar e usar as relações Eloquent, para ficares operacional sem nenhum conhecimento prévio do assunto; lê também o tutorial Laravel 9 para iniciantes, que propõe outra abordagem para aprender Laravel com o Eloquent.
O que são as relações numa base de dados?
Comecemos pelo mais básico. O que é exatamente uma relação? Uma relação significa que tens duas ou mais tabelas com registos ligados entre si. Digamos que tens uma tabela users e que cada utilizador pode ter vários posts. Como ligar os dois? Normalmente acrescenta-se uma coluna user_id à tabela posts, para saberes de imediato a que utilizador pertence cada artigo. É essa «ligação», que permite determinar que registos pertencem uns aos outros, a que se chama uma relação.
Que tipos de relações Eloquent existem?
Existem vários tipos de relações Eloquent. O tipo certo depende quase sempre de teres um ou (potencialmente) vários elementos de cada lado, tanto na primeira tabela como na segunda.
No exemplo acima temos uma tabela users e uma tabela posts. Digamos que cada artigo só pode pertencer a um utilizador. Para isso, acrescentamos uma coluna user_id à tabela posts, e é na tabela posts que a ligação fica guardada. No cenário inverso, bem mais improvável, se cada artigo pudesse pertencer a vários autores (users) e cada autor só contribuísse para um artigo, acrescentaríamos uma coluna post_id à tabela users. É um exemplo de relação relativamente simples, do tipo One-To-Many.
Mas o que acontece se cada utilizador puder ter vários artigos e cada artigo puder ter vários autores? Como resolver isso? É a isto que se chama uma relação Many-To-Many. Neste tipo de relação, mais complexa, não basta acrescentar uma coluna a uma tabela e dar o assunto por encerrado: é preciso uma tabela pivô. Não te preocupes se parecer complicado, já lá vamos.
De um modo geral, consideram-se os seguintes tipos de relação entre dados:
- Relação «um para um» (One-To-One)
- Relação «um para muitos» (One-To-Many)
- Has-One-Of-Many (por exemplo, o registo mais recente de entre muitos)
- Relações HasOneThrough e HasManyThrough
- Relação Many-To-Many
Relações «um para um» com o Eloquent
Comecemos pela relação mais simples: a relação «Has One» ou «One-To-One». Significa que um registo está ligado a outro registo, e apenas a um.
Retomemos o exemplo do blog, com utilizadores e artigos, e digamos que cada utilizador pode ter um perfil. Podias guardar toda a informação do perfil no modelo User, mas não seria ideal. Aqui prefiro guardá-la numa tabela separada: se mais tarde quisermos transferir um perfil para outro utilizador, dá jeito.
Começa por criar um modelo Profile, o User já vem gerado por omissão. As colunas exatas do modelo Profile pouco importam aqui, mas convém criar o modelo e a migração ao mesmo tempo:
php artisan make:model Profile -mComo se trata de uma relação um para um, temos a rara possibilidade de escolher entre:
- colocar um
user_idno modeloProfile, - ou acrescentar um
profile_idao modeloUser.
Podes escolher o que quiseres, mas repara que a relação fala de um User que tem um Profile. Quando dizes que algo tem outra coisa, a coluna vai normalmente para o segundo modelo. Diz-se que o primeiro modelo «possui» o segundo.
É isso que vou fazer:
/** Acrescenta isto à tua migração da tabela profile ou cria uma nova migração */
$table->foreignId('user_id');Corre as migrações e ficamos prontos para implementar isto.
Abre o modelo User e acrescenta a função pública seguinte. Podes dar-lhe o nome que quiseres, mas a convenção é o snake_case:
public function profile()
{
return $this->hasOne(Profile::class);
}O hasOne espera agora que a função profile() use a coluna user_id do modelo Profile. Se a tua coluna tiver outro nome, acrescenta um segundo argumento ao hasOne com esse nome:
return $this->hasOne(Profile::class, 'author_id');Ainda assim, não vale a pena afastares-te da convenção.
Acabaste de acrescentar a tua primeira relação Eloquent! Continua a ler para aprenderes a usá-la.
Acrescentar o inverso desta relação
Na maior parte dos casos podemos também definir o inverso de uma relação. Parece complicado, mas não é: basta acrescentar ao modelo Profile uma função que devolve o modelo a que ele pertence (User, neste caso). No teu modelo Profile:
public function user()
{
return $this->belongsTo(User::class, 'author_id');
}Usar uma relação Eloquent
Usar esta relação Eloquent do tipo One-To-One é muito simples. Sempre que tiveres uma instância do objeto User, podes chamar $user->profile ou $user->profile()->enchainerDautreMethodeIci().
Isto porque $user é uma instância da classe User e, nesse modelo, acrescentámos o método de relação profile().
Repara num ponto importante: podes aceder tanto a ->profile, como se fosse uma propriedade, como a ->profile(), como se fosse uma função. A diferença é que ->profile devolve diretamente uma instância Eloquent, enquanto ->profile() te deixa encadear outros métodos. Por exemplo ->profile()->orderBy('xxx', 'ASC')->get().
Em resumo, ->profile é um atalho para ->profile()->get().
$user = auth()->user();
$profile = $user->profile;
$name = $user->profile->display_name;
// Criar um perfil
$profile = $user->profile()->create([
//
]);
$user = Profile::find(1)->user;Relação Eloquent do tipo One-To-Many
Outra relação muito importante, talvez a mais importante de todas, é a relação um para muitos. Também conhecida por hasMany, descreve o caso em que um elemento tem vários outros elementos. É muito parecida com a anterior.
Continuando o exemplo do blog, digamos que um perfil tem vários artigos. Abre o modelo Profile e acrescenta o método seguinte:
public function posts()
{
return $this->hasMany(Post::class);
//Ou return $this->hasMany(Post::class, 'foreign_key');
}Isto quer dizer que cada perfil tem vários artigos. O inverso também existe:
public function profile()
{
return $this->belongsTo(Profile::class);
//Ou return $this->belongsTo(Profile::class, 'foreign_key');
}A utilização é muito parecida com a que vimos acima, com uma diferença: a relação devolve vários elementos, sob a forma de uma coleção Eloquent.
Agora que a relação está definida como método do modelo, podemos voltar a usá-la como propriedade (->posts), que aqui devolve uma coleção Eloquent em vez de um único modelo. E também como função (->posts()->where('created_at', '>', now()->subDays(14))->get() ).
$posts = Profile::find(1)->posts;
//Ou $posts = Profile::find(1)->posts()->get();
foreach ($posts as post) {
// TODO
}
$lastPost = Profile::find(1)->posts()->latest()->first();Obter o registo mais recente ou mais antigo através de uma relação
Às vezes, ao definir uma relação hasMany, só queres recuperar o registo mais recente ou apenas o registo mais antigo. Para isso, o Laravel oferece dois métodos práticos, ->latestOfMany() e ->oldestOfMany():
public function latestPost()
{
return $this->hasMany(Post::class)->latestOfMany();
}public function oldestPost()
{
return $this->hasMany(Post::class)->oldestOfMany();
}Estes dois métodos devolvem um único modelo em vez de uma coleção.
$latestPost = Profile::find(1)->latestPost;Se isto te serve mas precisas de filtros personalizados ou de cláusulas where() mais avançadas, vê este exemplo na documentação do Laravel:
/**
* Obtém o preço atual do produto
*/
public function currentPricing()
{
return $this->hasOne(Price::class)->ofMany([
'published_at' => 'max',
'id' => 'max',
], function ($query) {
$query->where('published_at', '<', now());
});
}HasOneThrough e HasManyThrough
Vistos estes exemplos, vamos um pouco mais longe: definir uma relação através de outro modelo.
Voltemos ao exemplo acima, em que cada User tem um Profile e cada Profile tem vários Post. Queremos agora obter todos os artigos de um utilizador. Não podemos acrescentar um hasMany(Post::class) diretamente ao modelo User, porque o modelo Post não tem coluna user_id: só tem profile_id. É o exemplo perfeito de uma relação has many through.
Definir esta relação é muito parecido com o que já vimos. No nosso exemplo, acrescenta isto ao modelo User:
public function posts()
{
return $this->hasManyThrough(Post::class, Profile::class);
}O primeiro argumento é o modelo a que queremos aceder e o segundo é o modelo intermédio.
Definir uma relação hasOneThough é quase igual a definir uma hasMany(), com a diferença de que aqui tens de garantir que só existe um elemento no fim:
public function first_login()
{
return $this->hasOneThrough(FirstLogin::class, Profile::class);
}Relações muitos para muitos
Vistas as relações anteriores, passemos a relações mais avançadas. Numa relação muitos para muitos, cada User pode ter vários Profile, por exemplo quando se colabora com outras pessoas, e cada Profile pode ter vários User.
Como resolver isto? Não podemos limitar-nos a acrescentar um profile_id à tabela dos utilizadores, porque há potencialmente vários perfis. E também não podemos acrescentar um user_id ao modelo Profile, porque há potencialmente vários utilizadores.
Para isso precisamos de uma espécie de tabela intermédia. Essa tabela chama-se tabela pivô e, na maioria dos casos, não faz mais nada além de guardar dois id na mesma linha.
Vejamos o cenário seguinte: se a nossa tabela pivô tiver um user_id e um profile_id, podemos ligar um dado User a um dado Profile. E como podemos repetir o mesmo user_id tantas vezes quantas quisermos, cada linha com um profile_id diferente, um utilizador fica ligado a tantos perfis quantos quisermos. Do outro lado passa-se o mesmo: repetimos o mesmo profile_id em várias linhas, cada uma com um user_id diferente, e um perfil fica ligado a tantos utilizadores quantos quisermos.
Como criar uma migração para uma tabela pivô no Laravel
Comecemos por criar a migração de base de dados de que precisamos. Corre o comando seguinte e repara em como o nome da tabela é construído juntando os nomes das duas tabelas (ambos no singular) por ordem alfabética. Podes ignorar esta regra, mas raramente compensa.
php artisan make:migration create_profile_user_table --create=profile_userAbre agora o ficheiro de migração e acrescenta duas linhas ao método up().
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProfileUserTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('profile_user', function (Blueprint $table) {
$table->id();
$table->timestamps();
$table->foreignId('profile_id');
$table->foreignId('user_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('profile_user');
}
}
Como vês, a estrutura de uma tabela pivô é muito simples. Corre agora as migrações.
A seguir, abre os dois modelos e acrescenta as duas funções de relação. Nos dois casos, o método tem a forma belongsToMany().
Acrescenta isto ao teu modelo User:
/**
* Função pública para obter todos os perfis sob os quais este utilizador pode publicar
*/
public function profiles()
{
return $this->belongsToMany(Profile::class);
}E isto ao teu modelo Profile:
/**
* Função pública para obter todos os utilizadores que podem publicar sob este perfil
*/
public function users()
{
return $this->belongsToMany(User::class);
}Se escolheres outra tabela em vez do nome por omissão, passa o teu nome de tabela como parâmetro às funções belongsToMany.
return $this->belongsToMany(Profile::class, 'user_role'); // Aqui a tabela está por ordem alfabética inversaPor omissão, o Eloquent espera as colunasuser_ide profile_id na tabela pivô. Se lhes deres outros nomes, passa-os como terceiro e quarto parâmetros:
return $this->belongsToMany(Profile::class, 'user_role', 'nom_de_la_colonne_qui_represente_l_identifiant_de_ce_modele', 'nom_de_la_colonne_qui_represente_l_identifiant_de_la_cle_etrangere');
// Parece desnecessariamente complicado, por isso aqui vai um exemplo.
// Digamos que acrescentamos isto ao modelo User; por omissão ficaria assim:
return $this->belongsToMany(Profile::class, 'user_role', 'user_id', 'profile_id');Usar uma relação Eloquent Many-To-Many
Usar esta relação é muito fácil e será parecido com o que já vimos. Repara nestes pequenos exemplos:
$user = User::find(1);
$user->profiles->each(function ($item) use ($user) {
$profile->name = $user->firstname . ' ' . $user->lastname;
$profile->save();
});$profile = Profile::find(1);
$profile->users()->orderBy('created_at', 'desc');Guardar dados na tabela pivô
A última parte deste artigo trata de guardar dados na tabela pivô. Sim, também é possível. Não faz falta em todos os casos de uso, mas há situações em que dá muito jeito.
Recapitulando: o que é uma linha de uma tabela pivô? O que representa? Uma linha da tabela pivô representa a relação entre dois registos. Se apagarmos essa linha, a relação continua a existir? Não.
É por isso que isto importa. Se cada linha da tabela pivô representa uma relação, podemos guardar também informação sobre essa relação na tabela pivô. Pode ser algo trivial, como a data de criação da relação ou a da última atualização (com as colunas created_at e updated_at).
Usar a tabela pivô para ler dados da relação
No exemplo acima demos a migração da tabela pivô. Essa migração já criava as colunas created_at e updated_at (através do $table->timestamps();). Então, como aceder a esses dados?
Primeiro temos de o dizer ao Eloquent: o nosso modelo pivô tem outros atributos a que quero aceder, e são estes.
Podes fazê-lo assim:
/**
* Função pública para obter todos os perfis sob os quais este utilizador pode publicar
*/
public function profiles()
{
return $this->belongsToMany(Profile::class)->withPivot('created_at', 'updated_at', 'active');
}Ou usar o prático método auxiliar ->withTimestamps(), que já declara as colunas created_at e updated_at:
/**
* Função pública para obter todos os perfis sob os quais este utilizador pode publicar
*/
public function profiles()
{
return $this->belongsToMany(Profile::class)->withTimestamps()->withPivot('active');
}A partir daqui podes aceder à tabela pivô assim:
$user = User::find(1);
foreach ($user->profiles as $profile) {
echo $profile->pivot->created_at;
}Prático, não é?
Renomear o atributo pivot da tua tabela pivô
Há ainda outro truque simpático e é renomear o atributo pivot. O Eloquent usa uma linguagem descritiva e expressiva. A documentação dá o exemplo de uma relação entre um podcast e um utilizador: essa relação é uma subscrição, por isso soaria estranho falar de «pivot» para referir uma subscrição.
// Mal:
$user->podcasts()->first()->pivot->price;
// Bem:
$user->podcasts()->first()->subscription->price;Para renomear o atributo pivot, usa o método ->as($name) no return $this->belongsToMany():
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();Conclusão
Foi um artigo longo. Se chegaste até aqui, obrigado por me acompanhares. Percorremos os vários tipos de relações entre modelos Eloquent. Espero que te tenha sido útil no teu percurso de aprendizagem das relações Eloquent.
As relações apresentadas neste artigo são as que mais vais usar. Na minha opinião, chegam e sobram para já.
Ainda assim, vais encontrar situações em que estas relações não chegam. Aqui falámos sempre da relação entre um modelo e outro modelo. Mas repara no caso, bastante comum, em que queres etiquetar tanto um Post como uma Page. Nessa altura as relações acima já não bastam: faltam-nos as relações polimórficas. Já escrevi uma introdução às relações polimórficas do Eloquent, lê-a para aprofundares o tema


