Eloquent ORM: relacionamentos e queries
1. Introdução ao Eloquent ORM e Configuração Inicial
1.1. O que é Eloquent ORM e sua integração com Laravel
Eloquent é o ORM (Object-Relational Mapping) padrão do Laravel, que implementa o padrão Active Record. Ele permite interagir com bancos de dados relacionais utilizando objetos PHP, abstraindo a complexidade das queries SQL. Cada tabela do banco corresponde a um Model, e cada instância desse Model representa uma linha na tabela.
1.2. Definição de Models e convenções de nomenclatura
Por convenção, o nome da tabela é o plural snake_case do nome da classe Model. Por exemplo, User mapeia para users, Post para posts. A chave primária esperada é id (auto-increment), e as timestamps created_at e updated_at são gerenciadas automaticamente.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// Se a tabela não seguir a convenção, defina explicitamente:
protected $table = 'usuarios';
// Se não usar timestamps:
public $timestamps = false;
}
1.3. Configuração de conexão com banco de dados e migrations
A conexão é configurada no arquivo .env:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=meu_banco
DB_USERNAME=root
DB_PASSWORD=
Migrations são usadas para criar e modificar tabelas:
<?php
// database/migrations/xxxx_create_users_table.php
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});
}
2. Relacionamentos Básicos: Um para Um e Um para Muitos
2.1. Relacionamento hasOne / belongsTo: exemplo de perfil de usuário
Um usuário tem um perfil. No Model User:
public function profile()
{
return $this->hasOne(Profile::class);
}
No Model Profile:
public function user()
{
return $this->belongsTo(User::class);
}
Uso:
$user = User::find(1);
echo $user->profile->bio;
2.2. Relacionamento hasMany / belongsTo: posts de um usuário
// Model User
public function posts()
{
return $this->hasMany(Post::class);
}
// Model Post
public function user()
{
return $this->belongsTo(User::class);
}
2.3. Consultas encadeadas com relacionamentos
// Lazy loading (dispara queries separadas)
$user = User::find(1);
foreach ($user->posts as $post) {
echo $post->title;
}
// Eager loading (uma única query com JOIN)
$users = User::with('posts')->get();
3. Relacionamentos Muitos para Muitos e Polimórficos
3.1. Relacionamento belongsToMany: papéis de usuários
Tabela pivô role_user com user_id e role_id:
// Model User
public function roles()
{
return $this->belongsToMany(Role::class);
}
// Model Role
public function users()
{
return $this->belongsToMany(User::class);
}
3.2. Relacionamentos polimórficos
Comentários que podem pertencer a posts ou vídeos:
// Model Comment
public function commentable()
{
return $this->morphTo();
}
// Model Post
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
// Model Video
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
Estrutura da tabela comments:
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->morphs('commentable'); // cria commentable_id e commentable_type
$table->timestamps();
});
3.3. Manipulação de dados na tabela pivô
// Atributos extras na tabela pivô
public function roles()
{
return $this->belongsToMany(Role::class)
->withPivot('level', 'assigned_at')
->withTimestamps();
}
// Sincronizar papéis
$user->roles()->sync([1 => ['level' => 5], 2 => ['level' => 3]]);
// Anexar sem remover existentes
$user->roles()->attach(3, ['level' => 2]);
// Desanexar
$user->roles()->detach(1);
4. Consultas Avançadas com Query Builder do Eloquent
4.1. Cláusulas where, orWhere, whereIn
$posts = Post::where('active', true)
->where(function ($query) {
$query->where('title', 'like', '%PHP%')
->orWhere('body', 'like', '%PHP%');
})
->whereIn('category_id', [1, 3, 5])
->get();
4.2. Ordenação, paginação e agrupamento
// Paginação
$users = User::orderBy('name')->paginate(15);
// Paginação simples (apenas next/previous)
$users = User::orderBy('name')->simplePaginate(15);
// Agrupamento com having
$categories = Post::select('category_id', DB::raw('count(*) as total'))
->groupBy('category_id')
->having('total', '>', 10)
->get();
4.3. Subconsultas e joins manuais
// Subconsulta na seleção
$users = User::select('users.*')
->addSelect(['last_post_date' => Post::select('created_at')
->whereColumn('user_id', 'users.id')
->latest()
->limit(1)
])->get();
// Join manual
$posts = Post::join('users', 'posts.user_id', '=', 'users.id')
->where('users.active', true)
->select('posts.*', 'users.name as author')
->get();
5. Eager Loading, Lazy Loading e Otimização de Consultas
5.1. Diferença entre lazy loading e eager loading
// Lazy loading (N+1 problem)
$users = User::all();
foreach ($users as $user) {
echo $user->posts->count(); // Executa 1 query para users + N queries para posts
}
// Eager loading (2 queries)
$users = User::with('posts')->get();
5.2. Eager loading condicional e aninhado
// Carregar apenas posts publicados
$users = User::with(['posts' => function ($query) {
$query->where('published', true)->orderBy('created_at', 'desc');
}])->get();
// Relacionamentos aninhados
$users = User::with('posts.comments.author')->get();
5.3. Uso de load() e loadMissing()
$user = User::find(1);
// Carregar relacionamento após a consulta
$user->load('posts');
// Carregar apenas se ainda não foi carregado
$user->loadMissing('profile');
6. Agregações, Scopes e Mutators
6.1. Funções de agregação em relacionamentos
// Contar posts de cada usuário
$users = User::withCount('posts')->get();
foreach ($users as $user) {
echo $user->posts_count;
}
// Agregações em relacionamentos
$users = User::withSum('posts', 'views')->get();
$users = User::withAvg('ratings', 'score')->get();
6.2. Escopos globais e locais
// Escopo local
public function scopeActive($query)
{
return $query->where('active', true);
}
public function scopePopular($query, $minViews = 100)
{
return $query->where('views', '>=', $minViews);
}
// Uso
$posts = Post::active()->popular(500)->get();
6.3. Mutators e Accessors
// Accessor (getter)
public function getFullNameAttribute()
{
return "{$this->first_name} {$this->last_name}";
}
// Mutator (setter)
public function setPasswordAttribute($value)
{
$this->attributes['password'] = bcrypt($value);
}
// Uso
echo $user->full_name; // Accessor
$user->password = 'secret'; // Mutator
7. Manipulação de Dados: Inserção, Atualização e Exclusão com Relacionamentos
7.1. Criação de registros relacionados
// Usando save()
$user = User::find(1);
$post = new Post(['title' => 'Novo Post', 'body' => 'Conteúdo']);
$user->posts()->save($post);
// Usando create()
$user->posts()->create(['title' => 'Outro Post', 'body' => 'Mais conteúdo']);
// Usando associate() para belongsTo
$post = Post::find(1);
$user = User::find(2);
$post->user()->associate($user);
$post->save();
7.2. Atualização em massa e upsert
// Atualização em massa
Post::where('category_id', 3)->update(['views' => 0]);
// Upsert (insert or update)
User::upsert([
['email' => 'user1@example.com', 'name' => 'User 1'],
['email' => 'user2@example.com', 'name' => 'User 2'],
], ['email'], ['name']);
7.3. Exclusão em cascata e soft deletes
// Migration com cascade
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')
->constrained()
->onDelete('cascade');
});
// Soft deletes
use Illuminate\Database\Eloquent\SoftDeletes;
class Post extends Model
{
use SoftDeletes;
protected $dates = ['deleted_at'];
}
// Consultar com soft deletes
$posts = Post::withTrashed()->get();
$posts = Post::onlyTrashed()->get();
8. Boas Práticas, Performance e Debugging
8.1. Índices e profiling
// Migration com índices
Schema::table('posts', function (Blueprint $table) {
$table->index('user_id');
$table->index(['user_id', 'published']);
});
// Profiling com explain
$query = Post::where('user_id', 1)->toSql();
$explain = DB::select("EXPLAIN $query", [1]);
8.2. Cache de consultas
// Cache de queries
$users = Cache::remember('active_users', 3600, function () {
return User::where('active', true)->get();
});
// Cache de relacionamentos
$user = User::find(1);
$posts = Cache::remember("user_{$user->id}_posts", 3600, function () use ($user) {
return $user->posts()->with('comments')->get();
});
8.3. Ferramentas de debug
// Ver SQL gerado
$query = User::where('active', true);
dd($query->toSql(), $query->getBindings());
// Log de queries no AppServiceProvider
public function boot()
{
DB::listen(function ($query) {
logger($query->sql, $query->bindings);
});
}
// Usando Laravel Debugbar (instalar com composer)
// composer require barryvdh/laravel-debugbar
Referências
- Documentação Oficial do Eloquent ORM — Documentação completa sobre relacionamentos, queries e boas práticas do Eloquent.
- Eloquent: Getting Started - Laravel News — Guia introdutório com exemplos práticos de configuração e uso do Eloquent.
- Eloquent Relationships Cheat Sheet - Laravel Daily — Resumo visual e prático de todos os tipos de relacionamentos do Eloquent.
- Performance Optimization in Eloquent - Stack Overflow Blog — Dicas de otimização de performance com eager loading, índices e caching.
- Laravel Debugbar - GitHub — Pacote essencial para debugging de queries Eloquent e profiling de performance.
- Eloquent: API Resources - Laravel Docs — Documentação sobre transformação de dados e serialização de relacionamentos com API Resources.