Marcio Policarpo
Posted on October 19, 2022
ORM - Object Relational Mapping
ORM é uma técnica que busca aproximar o paradigma da programação orientada a objetos ao paradigma dos bancos de dados relacionais.
E o ORM pode ser adotado de duas formas:
- database first
- code first
Database first
Esta abordagem é mais usual em sistemas legados, quando o schema do banco de dados está definido e bastante evoluído.
Porém, é preciso um cuidado maior ao analisar os relacionamentos entre as tabelas e como resolver dentro do ORM. Um estudo pouco aprofundado pode resultar em perda da performance da aplicação, levando-se à falsa impressão que o problema está no ORM quando na verdade a estratégia adotada não foi a mais apropriada.
Code First
Nesta abordagem as entidades são criadas na aplicação para depois serem criadas no banco de dados, através de um processo chamado de migração, cujo objetivo é guardar o histórico de alterações feitas no schema. Este processo é análogo ao versionamento de código feito, por exemplo, no GitHub.
Entretanto, há situações onde este cenário não é aplicável. Para casos como este, o ORM pode gerar scripts DDL que serão executados no banco de dados, seja por meio de uma sessão aberta no terminal ou através de uma aplicação desenvolvida especificamente para essa finalidade.
Eloquent
O Eloquent é o ORM oficial em aplicações Laravel.
Entretanto a utilização do Eloquent não é obrigatória, sendo possível desenvolver uma aplicação completa, com acesso a bancos de dados utilizando apenas a classe Illuminate\Support\Facades\DB.
Assim como outros ORMs, o Eloquent traz uma classe (abstrata) para representar as entidades do banco de dados. Trata-se do Illuminate\Database\Eloquent\Model.
Ao longo deste artigo veremos algumas das características da classe model
e como resolver problemas que podem aparecer durante o processo de mapeamento de um schema de banco de dados.
⚠️ Apesar deste ser um artigo com cunho técnico, não criaremos projetos ou aplicações e também não haverá interação com bancos de dados.
Todas as informações contidas neste artigo podem ser encontradas na documentação oficial do framework Laravel neste link.
Model
O modelo, dentro da arquitetura MVC, é representado aqui pela letra 'M'. O modelo é o objeto da aplicação que mais se aproxima do schema do banco de dados, sendo usado com frequência para persistência de informações.
Em outras aplicações, o modelo exerce uma função diferente, realizando o transporte de dados entre as camadas de apresentação e negócio.
Neste caso, cria-se uma entidade para fazer a persistência. Este novo objeto (entidade) não se comunica diretamente com o modelo, embora existam projetos com essa característica.
Em aplicações Laravel a maneira mais fácil de criar um modelo é através do Artisan, que pode ser executado a partir do terminal no diretório raiz da aplicação, com o seguinte comando:
php artisan make:model <nome_do_modelo>
Assim que o comando for concluído, obteremos um novo arquivo que estenderá de Illuminate\Database\Eloquent\Model e será salvo no diretório \App\Models.
O código gerado pelo comando será semelhante a este:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Job extends Model
{
use HasFactory;
}
Note que a criação do modelo adicionou a trait HasFactory
, responsável por 'conectar' esta modelo com uma classe factory
, que não será abordada neste artigo.
Tabela
Como dito anteriormente, o modelo representará uma tabela do banco de dados da nossa aplicação. Por convenção, o Laravel assumirá que o nome da tabela representada pelo modelo Job
será Jobs
. Ou seja, o nome da classe no plural.
Porém, haverá casos onde o nome da tabela não se encaixará nessa convenção. A solução para situações como esta é informar o nome da tabela explicitamente. Isto é feito adicionando o atributo privado $table
à nossa classe.
O exemplo a seguir ilustra como implementar esta modificação.
protected $table = 'company_jobs';
Chave primária
Os nomes de chaves primárias são definidos pelo banco de dados. A exceção ocorre quando, no script DDL, o nome da chave primária é atribuído manualmente.
A exemplo do que fizemos para identificar explicitamente o nome da tabela referenciada pelo modelo, vamos 'informar' ao modelo qual o nome da chave primária, através do atributo protegido $primaryKey
.
E podemos ver no trecho de código logo abaixo, como deve ficar a implementação dentro do nosso modelo:
protected $primaryKey = 'job_id';
⚠️ O Eloquent ORM não suporta chaves primárias compostas nativamente. Entretanto, uma pesquisa na internet trará diversas soluções aplicáveis a vários cenários.
Chave estrangeira
Chaves estrangeiras tem como objetivo relacionar tabelas evitando a replicação de todas as informações de uma tabela em outras.
Para facilitar a compreensão do conceito de chave estrangeira, vamos imaginar o cliente de uma instituição financeira, cujo cadastro contém informações sobre endereço, fonte de renda e filiação, por exemplo.
Para este cliente, sempre que ele realizar alguma operação financeira, serão gravadas somente informações relativas à operação, como data, valor, número do documento (se houver), tipo da operação (crédito ou débito) e o ID do cliente.
Ao informamos o ID do cliente na tabela de operações financeiras estamos evitando que todas as informações relativas ao cadastro sejam replicadas em todas as operações, o que aumentaria significativamente o tamanho de cada registro e, por consequência, prejudicar a performance da aplicação.
A configuração de chave estrangeira no modelo pode ser feita através da criação de um método público que retornará o modelo que possui a chave primária.
Um exemplo desse método pode ser visto logo abaixo.
public function user()
{
return $this->belongsTo(User::class);
}
O método belongsTo
recebe como primeiro argumento o modelo que representa a 'tabela pai' do relacionamento e, através de métodos auxiliares, identifica a coluna que corresponde à chave primária da tabela pai.
Este cenário pode ser conveniente, quando a abordagem adotada na aplicação tenha sido code first. Na eventualidade de uma abordagem diferente, como database first, convém não delegar ao framework, a tarefa de encontrar os relacionamentos automaticamente.
Para resolver essa questão, o método belongsTo
pode receber outros argumentos, como nome da chave estrangeira ($foreignKey
), nome da chave primária da tabela pai ($ownerKey
) e nome da constraint no banco de dados ($relation
).
Abaixo a assinatura do método belongsTo
.
/**
* Define an inverse one-to-one or many relationship.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $ownerKey
* @param string|null $relation
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function belongsTo($related, $foreignKey = null, $ownerKey = null, $relation = null)
Relacionamento 'um pra um'
Este tipo de relacionamento entre entidades indica que, para cada instância do modelo 'A' pode existir uma ou nenhuma instância do modelo 'B'. Para citar um exemplo real, uma pessoa física tem apenas um CPF.
A forma de implementar este relacionamento no modelo 'A' é através de um método que retornará a instância do modelo 'B' associado.
Abaixo um exemplo da implementação deste relacionamento.
public function cpf()
{
return $this->hasOne(Cpf::class);
}
O método hasOne
recebe como primeiro argumento o modelo associado. O framework então, cria uma instância do modelo informado para inferir informações sobre o relacionamento entre os modelos.
E para reduzir o 'trabalho' feito pelo framework, o método hasOne
pode receber o nome da chave estrangeira ($foreignKey
) e o nome da chave primária ($localKey
) do modelo associado.
Abaixo temos a assinatura do método hasOne
.
/**
* Define a one-to-one relationship.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasOne
*/
public function hasOne($related, $foreignKey = null, $localKey = null)
Relacionamento 'um pra muitos'
Este é o relacionamento mais comum que vemos na maioria (senão em todas...rs) das aplicações. Utilizando um exemplo do mundo real, significa dizer que uma pessoa pode ter um ou mais filhos, ou nenhum filho.
Este tipo de relacionamento entre modelos também é resolvido através de um método, que ao ser chamado, retornará uma ou mais instâncias do modelo associado.
Abaixo temos um exemplo da implementação deste método em um modelo.
public function kid()
{
return $this->hasMany(Kid::class);
}
O método hasMany
receberá como argumento a classe do modelo 'B', associado ao modelo 'A'.
Logo abaixo, podemos notar que a assinatura do método hasMany
é semelhante à assinatura do método hasOne
, visto anteriormente.
/**
* Define a one-to-many relationship.
*
* @param string $related
* @param string|null $foreignKey
* @param string|null $localKey
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function hasMany($related, $foreignKey = null, $localKey = null)
Ocultar propriedades
Como dito anteriormente, há cenários onde o modelo tem um papel diferente interagindo com a camada de apresentação da aplicação e se comunicando somente com a camada de negócio.
Persistir os dados, nestes casos, ficará a cargo de outro objeto conhecido como entidade. Assim como o modelo, a entidade se comunica somente com a camada de negócio.
Esta abordagem traz isolamento entre as camadas de apresentação e persistência.
Mas existem aplicações onde o modelo é utilizado para fazer a persistência dos dados e mostrar informações na camada de apresentação.
Quando essa situação ocorre, convém ocultar algumas propriedades do modelo para não serem mostradas na camada de apresentação.
E fazemos isso através do atributo protegido $hidden, que recebe um array das propriedades que não devem ser mostradas.
Abaixo um exemplo da declaração deste atributo.
protected $hidden = [
'shop_id',
'created_at',
'updated_at',
];
Inserção em massa
Inserção em massa é uma técnica que visa a redução de código, sendo mais comum em aplicações web.
Nesta técnica, ao invés atribuir os valores explicitamente para cada propriedade do modelo, indicamos ao modelo quais colunas poderão receber os valores diretamente da camada de negócio.
A utilização desta técnica reduz significativamente a manutenção com código. Outro benefício é evitar que um registro seja persistido através da técnica de 'sql injection'.
Para implementar a inserção em massa no modelo, declaramos o atributo protegido $fillable
. Esse atributo receberá um array com os campos que poderão ser tratados durante o processo de inserção em massa.
A seguir um exemplo da implementação deste atributo em um modelo.
protected $fillable = [
'description',
'price',
];
Created_at e Updated_at
Quando a migração é criada, temos a possibilidade de adicionar duas colunas na tabela representada pelo modelo: created_at e update_at.
As duas colunas serão do tipo 'datetime' e o objetivo delas é armazenar o momento que o registro foi criado e a última vez que o registro foi atualizado.
Embora sejam informações úteis, elas nem sempre serão necessárias no modelo. E para evitar que o Eloquent faça esse controle basta adicionar o atributo público $timestamps
, que receberá um valor booleano.
O valor padrão deste atributo é true
e informamos false
para desabilitar esse controle.
Abaixo um exemplo da implementação desse atributo.
public $timestamps = false;
Conclusão
Neste artigo busquei trazer algumas das características do modelo do Eloquent ORM propondo soluções aos problemas mais comuns.
Tudo o que foi mostrado está na documentação oficial do Eloquent.
Mas não fique preso apenas à documentação pois ela pode não apresentar solução para problemas mais específicos.
Até breve.
😎
Posted on October 19, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 27, 2024
October 12, 2024