ORM Eloquent: Model

marciopolicarpo

Marcio Policarpo

Posted on October 19, 2022

ORM Eloquent: Model

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;
}

Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

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';
Enter fullscreen mode Exit fullscreen mode

⚠️ 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);
}
Enter fullscreen mode Exit fullscreen mode

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)

Enter fullscreen mode Exit fullscreen mode

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);    
}

Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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',
];

Enter fullscreen mode Exit fullscreen mode

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',
];
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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.

😎

💖 💪 🙅 🚩
marciopolicarpo
Marcio Policarpo

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