ORMs (visão geral)
LidoNesta aula, exploramos o conceito de ORM (Object-Relational Mapping), comparamos os dois principais ORMs do PHP — Eloquent e Doctrine — e discutimos os padrões Active Record e Data Mapper, além dos trade-offs envolvidos na escolha de um ORM. Ao final, você terá uma base sólida para decidir qual abordagem usar em seus projetos.
Bem-vindo à aula 89 do nosso curso de PHP! Hoje vamos mergulhar em um dos tópicos mais importantes no desenvolvimento web moderno: ORMs (Object-Relational Mapping). Se você já trabalhou com bancos de dados relacionais e PHP, provavelmente já ouviu falar de Eloquent ou Doctrine. Mas o que exatamente é um ORM? Por que ele é tão usado? E quais são as diferenças entre as principais abordagens?
Nesta aula, vamos entender o que é ORM, comparar os dois gigantes do ecossistema PHP — Eloquent e Doctrine —, explorar os padrões Active Record e Data Mapper, e analisar os prós e contras de cada escolha. Ao final, você terá uma visão clara para decidir quando e como usar um ORM em seus projetos.
O que é ORM
ORM significa Object-Relational Mapping, ou Mapeamento Objeto-Relacional. Em termos simples, é uma técnica que permite interagir com um banco de dados relacional (como MySQL, PostgreSQL ou SQLite) usando objetos da sua linguagem de programação, em vez de escrever SQL puro. O ORM atua como uma ponte entre o mundo orientado a objetos do seu código e o mundo relacional do banco de dados.
Imagine que você tem uma tabela usuarios com colunas id, nome e email. Sem ORM, você escreveria algo como:
$pdo = new PDO('mysql:host=localhost;dbname=meubanco', 'usuario', 'senha');
$stmt = $pdo->query('SELECT * FROM usuarios WHERE id = 1');
$usuario = $stmt->fetch(PDO::FETCH_ASSOC);
echo $usuario['nome'];Com um ORM, você faria algo assim (exemplo com Eloquent):
$usuario = Usuario::find(1);
echo $usuario->nome;Perceba como o ORM abstrai a complexidade do SQL e fornece uma API orientada a objetos. Além disso, o ORM cuida da conversão entre tipos (por exemplo, uma string do banco vira um objeto DateTime no PHP), do gerenciamento de relacionamentos (chaves estrangeiras, tabelas pivô) e, muitas vezes, da criação de consultas complexas de forma programática.
Um ORM geralmente inclui recursos como:
- Mapeamento de tabelas para classes: cada tabela vira uma classe, e cada registro vira uma instância dessa classe.
- Relacionamentos: permite definir associações como
hasOne,hasMany,belongsTo,belongsToMany. - Consultas fluentes: um construtor de consultas que permite montar SQL de forma encadeada.
- Migrations: ferramentas para versionar o esquema do banco de dados.
- Seeds e factories: para popular o banco com dados de teste.
É importante notar que ORMs não são apenas para PHP; existem em várias linguagens, como Hibernate (Java), Entity Framework (.NET) e SQLAlchemy (Python). No PHP, os dois principais são Eloquent e Doctrine, que veremos a seguir.
Eloquent e Doctrine (panorama)
No ecossistema PHP, dois ORMs dominam o cenário: Eloquent, que é o ORM padrão do Laravel, e Doctrine, que é amplamente utilizado em frameworks como Symfony e Zend (agora Laminas). Cada um tem suas características, filosofia e casos de uso ideais.
Eloquent
Eloquent é o ORM do Laravel, um dos frameworks PHP mais populares. Ele adota o padrão Active Record (que veremos adiante), onde cada classe modelo corresponde a uma tabela e cada instância representa uma linha. Eloquent é conhecido por sua simplicidade e produtividade.
Principais características:
- Sintaxe simples e expressiva: por exemplo,
User::where('active', true)->get(). - Integração profunda com Laravel: funciona perfeitamente com o Query Builder, migrations, factories e outros componentes.
- Relacionamentos fáceis: definir um relacionamento é tão simples quanto criar um método na classe.
- Mass assignment protection: evita atribuição em massa insegura.
- Eloquent ORM inclui timestamps automáticos, soft deletes, e muito mais.
Exemplo de definição de modelo e uso:
// app/Models/User.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $fillable = ['name', 'email', 'password'];
public function posts()
{
return $this->hasMany(Post::class);
}
}
// Uso
$user = User::find(1);
foreach ($user->posts as $post) {
echo $post->title;
}Doctrine
Doctrine é um projeto mais antigo e maduro, composto por vários componentes, sendo o Doctrine ORM o mais famoso. Ele adota o padrão Data Mapper, onde as entidades são classes simples (POJO-like) e o mapeamento é feito separadamente, geralmente via anotações, XML ou YAML. Doctrine é altamente configurável e pode ser usado fora de frameworks, embora seja comum em Symfony.
Principais características:
- Entidades persistentes: suas classes de domínio não precisam herdar de uma classe base (embora possam).
- Mapeamento flexível: você define o mapeamento via atributos (PHP 8), anotações, XML ou YAML.
- Unidade de trabalho (Unit of Work): ele rastreia mudanças e sincroniza com o banco apenas quando você chama
flush(). - DQL (Doctrine Query Language): uma linguagem de consulta orientada a objetos.
- Suporte a eventos e hooks.
Exemplo de entidade e uso:
// src/Entity/User.php
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
private int $id;
#[ORM\Column(type: 'string', length: 100)]
private string $name;
#[ORM\Column(type: 'string')]
private string $email;
// getters e setters...
}
// Uso
$user = new User();
$user->setName('João');
$user->setEmail('joao@example.com');
$entityManager->persist($user);
$entityManager->flush();Enquanto Eloquent é mais rápido para desenvolvimento e integrado ao Laravel, Doctrine oferece mais controle e separação de preocupações, sendo preferido em aplicações complexas onde o domínio é rico.
Active Record vs Data Mapper
Esses dois padrões são fundamentais para entender como um ORM funciona. Eles diferem na forma como as classes de modelo se relacionam com o banco de dados.
Active Record
No padrão Active Record, cada classe modelo contém tanto os dados (atributos) quanto o comportamento de acesso ao banco (métodos como save(), delete(), find()). Ou seja, a classe é responsável por sua própria persistência. Isso torna o código simples e direto, mas acopla a lógica de negócio com a persistência.
Exemplo com Eloquent:
$user = new User();
$user->name = 'Maria';
$user->email = 'maria@example.com';
$user->save(); // persiste no banco
$user->delete(); // removeVantagens:
- Simplicidade: fácil de entender e usar.
- Produtividade: menos código para escrever.
- Integração com convenções (por exemplo, nome da tabela é o plural do nome da classe).
Desvantagens:
- Acoplamento: a lógica de negócio fica misturada com a persistência.
- Testabilidade: mais difícil de testar isoladamente, pois depende do banco.
- Escalabilidade: em domínios complexos, pode se tornar confuso.
Data Mapper
No padrão Data Mapper, as entidades são classes comuns (sem herança de framework) e a persistência é tratada por um mapper separado (no caso do Doctrine, o EntityManager). As entidades não conhecem o banco; elas são objetos de domínio puros. O mapper é responsável por sincronizar o estado entre o objeto e o banco.
Exemplo com Doctrine:
$user = new User();
$user->setName('Carlos');
$user->setEmail('carlos@example.com');
$entityManager->persist($user);
$entityManager->flush(); // só agora gravaVantagens:
- Separação de responsabilidades: o domínio fica livre de infraestrutura.
- Flexibilidade: você pode persistir objetos que não seguem convenções, e mapear relacionamentos complexos.
- Testabilidade: entidades podem ser testadas sem banco de dados.
Desvantagens:
- Mais complexo: exige mais configuração e entendimento do ciclo de vida.
- Curva de aprendizado maior.
- Pode ser excessivo para projetos simples.
Em resumo, Active Record é ótimo para CRUD simples e prototipagem, enquanto Data Mapper é melhor para sistemas com regras de negócio complexas e arquitetura limpa.
Trade-offs
Ao decidir entre usar um ORM ou não, e qual escolher, você precisa considerar vários fatores. Vamos analisar os principais trade-offs.
Produtividade vs Controle
ORMs aumentam a produtividade ao eliminar SQL repetitivo e fornecer helpers para operações comuns. No entanto, você perde controle fino sobre as consultas. Por exemplo, consultas com junções complexas ou otimizações específicas do banco podem ser difíceis de expressar no ORM. Em muitos casos, você ainda pode escrever SQL puro, mas isso quebra a abstração.
Performance
ORMs podem introduzir overhead, como o problema N+1 (quando você consulta um registro e depois consulta seus relacionamentos em loops, gerando muitas queries). Boas práticas como eager loading (com with() no Eloquent ou join no Doctrine) ajudam, mas exigem conhecimento. Em cenários de alta performance, pode ser necessário recorrer a SQL puro ou a micro-otimizações.
Complexidade de Mapeamento
Mapear tabelas simples é trivial, mas relacionamentos muitos-para-muitos, herança, ou colunas polimórficas podem complicar. ORMs oferecem soluções, mas elas podem ser difíceis de configurar e entender. Por exemplo, no Doctrine, herança pode ser mapeada com single table ou joined table, cada um com prós e contras.
Dependência de Framework
Eloquent é fortemente acoplado ao Laravel, então se você usar em outro framework, pode ser complicado. Doctrine é mais independente, mas ainda tem integrações com Symfony. Escolher um ORM pode amarrar você a um ecossistema.
Curva de Aprendizado
Eloquent é mais fácil de aprender, mas Doctrine exige mais tempo para dominar, especialmente seus conceitos de Unit of Work, proxies e ciclo de vida. Para equipes iniciantes, Eloquent pode ser mais produtivo.
Manutenção e Evolução
ORM pode facilitar a manutenção, pois você muda a estrutura de objetos em vez de SQL. Porém, se o esquema do banco for muito complexo, o mapeamento pode se tornar um fardo. Além disso, migrations gerenciadas pelo ORM podem ser úteis, mas exigem disciplina.
Em resumo, não há resposta certa. Avalie o tamanho do projeto, a complexidade do domínio, a experiência da equipe e os requisitos de performance. Muitas vezes, uma abordagem híbrida (usar ORM para CRUD e SQL puro para relatórios) é a melhor.
Boas Práticas e Observações Finais
Ao usar ORMs, siga estas boas práticas:
- Use eager loading para evitar o problema N+1.
- Mantenha as entidades/classes de modelo simples e focadas no domínio.
- Evite consultas complexas no ORM; use Query Builder ou SQL quando necessário.
- Configure índices e chaves estrangeiras corretamente no banco, mesmo que o ORM gerencie.
- Escreva testes para as consultas e relacionamentos, especialmente se usar Data Mapper.
Lembre-se: ORM é uma ferramenta, não uma bala de prata. Entenda seus limites e use quando fizer sentido.
Referências
- PHP Manual - PDO
- Laravel Eloquent Documentation
- Doctrine ORM Documentation
- Martin Fowler - Active Record
- Martin Fowler - Data Mapper
- Symfony & Doctrine
Exercícios
- Explique, com suas palavras, o que é ORM e qual problema ele resolve. Dê um exemplo de como seria uma consulta sem ORM e com ORM.
- Cite duas diferenças principais entre Eloquent e Doctrine.
- Explique o padrão Active Record e dê um exemplo de código em PHP (pode ser com Eloquent).
- Quais são os trade-offs entre usar um ORM e escrever SQL puro? Cite pelo menos três.
- Considerando um projeto com regras de negócio complexas, qual padrão (Active Record ou Data Mapper) você escolheria e por quê?
$stmt = $pdo->query('SELECT * FROM usuarios WHERE id = 1'); $usuario = $stmt->fetch();. Com ORM (Eloquent): $usuario = Usuario::find(1);.$user = new User();
$user->name = 'Ana';
$user->save(); // persiste A classe User herda de Model do Eloquent e cada instância representa uma linha da tabela.