Eloquent: Começando
Introdução
Laravel inclui o Eloquent, um mapeador relacional de objeto (ORM) que torna a interação com sua base de dados agradável. Ao usar o Eloquent, cada tabela do banco de dados tem um "Modelo" correspondente usado para interagir com essa tabela. Além de obter registros da tabela do banco de dados, os modelos Eloquent permitem inserir, atualizar e excluir registros dessa tabela também.
Nota
Antes de começar, certifique-se de configurar uma conexão de banco de dados no seu arquivo de configuração do config/database.php
. Para mais informações sobre a configuração do seu banco de dados, consulte a documentação da configuração do banco de dados.
Laravel Bootcamp
Se você é novo no Laravel, sinta-se à vontade para mergulhar no bootcamp do Laravel. O bootcamp do Laravel o guiará para construir seu primeiro aplicativo Laravel usando Eloquent. É uma ótima maneira de ter um passeio em tudo que o Laravel e o Eloquent têm a oferecer.
Geração de Classes de Modelo
Para começar, vamos criar um modelo Eloquent. Os modelos geralmente estão na pasta app/Models
e estendem a classe Illuminate\Database\Eloquent\Model
. Você pode usar o comando make:model
do Artisan para gerar um novo modelo:
php artisan make:model Flight
Se você desejar gerar uma migração de banco de dados quando gerar o modelo, você pode usar a opção --migration
ou -m
:
php artisan make:model Flight --migration
Você pode gerar outros tipos de classe quando estiver criando um modelo como fábricas, seeds, políticas, controladores e formulários de solicitação. Além disso, essas opções podem ser combinadas para criar várias classes de uma só vez:
# Gere um modelo e uma classe FlightFactory...
php artisan make:model Flight --factory
php artisan make:model Flight -f
# Gere um modelo e uma classe FlightSeeder...
php artisan make:model Flight --seed
php artisan make:model Flight -s
# Gerar um modelo e uma classe FlightController...
php artisan make:model Flight --controller
php artisan make:model Flight -c
# Gerar um modelo, classe de recurso FlightController e classes de solicitação de formulário...
php artisan make:model Flight --controller --resource --requests
php artisan make:model Flight -crR
# Gerar um modelo e uma classe FlightPolicy...
php artisan make:model Flight --policy
# Gerar um modelo e uma migração, fábrica, semeador e controlador...
php artisan make:model Flight -mfsc
# Atalho para gerar um modelo, migração, fábrica, semeador, política, controlador e solicitações de formulário...
php artisan make:model Flight --all
php artisan make:model Flight -a
# Gerar um modelo de pivô...
php artisan make:model Member --pivot
php artisan make:model Member -p
Inspecionando modelos
Às vezes pode ser difícil determinar todos os atributos e as relações de um modelo apenas olhando para o código. Em vez disso, tente usar o comando Artisan model:show
, que fornece uma visão geral conveniente dos atributos e das relações do seu modelo:
php artisan model:show Flight
Convenções em Modelos Eloquent
Modelos gerados pelo comando make:model
serão colocados no diretório app/Models
. Vamos analisar uma classe básica de modelo e discutir algumas das convenções-chave do Eloquent:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
// ...
}
Nomes das tabelas
Depois de olhar o exemplo acima você pode ter notado que nós não dissemos ao Eloquent qual tabela do banco de dados corresponde a nosso modelo Flight
. Por convenção, o "snake case", nome plural da classe será usado como o nome da tabela, a menos que outro nome seja explicitamente especificado. Então, neste caso, o Eloquent irá assumir que o modelo Flight
armazena registros na tabela flights
, enquanto um modelo AirTrafficController
armazenaria registros em uma tabela air_traffic_controllers
.
Se a tabela do banco de dados correspondente ao seu modelo não segue essa convenção, você pode especificar manualmente o nome da tabela do modelo definindo uma propriedade table
no modelo.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* A tabela associada ao modelo.
*
* @var string
*/
protected $table = 'my_flights';
}
Chaves Primárias
O Eloquent também assumirá que cada tabela do banco de dados correspondente a um modelo tem uma coluna de chave primária chamada id
. Se necessário, você pode definir uma propriedade $primaryKey
protegida em seu modelo para especificar uma coluna diferente que serve como sua chave primária:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* A chave primária associada à tabela.
*
* @var string
*/
protected $primaryKey = 'flight_id';
}
Além disso, o Eloquent assume que a chave primária é um valor inteiro incrementado, o que significa que o Eloquent irá automaticamente converter a chave primária em um inteiro. Se você quiser usar uma chave primária não incrementada ou não numérica você deve definir uma propriedade pública chamada $incrementing
em seu modelo que está definido como false
:
<?php
class Flight extends Model
{
/**
* Indica se o ID do modelo é de incremento automático.
*
* @var bool
*/
public $incrementing = false;
}
Se o modelo não for inteiro, você deve definir uma propriedade $keyType
protegida no seu modelo. Essa propriedade deve ter um valor de string
:
<?php
class Flight extends Model
{
/**
* O tipo de dados do ID da chave primária.
*
* @var string
*/
protected $keyType = 'string';
}
Chave composta
O Eloquent exige que cada modelo tenha pelo menos um "ID" que o identifique de forma exclusiva, que pode servir como sua chave primária. As chaves primárias "compostas" não são suportadas pelos modelos Eloquent. Contudo, você é livre para adicionar índices exclusivos com múltiplas colunas às suas tabelas de banco, além da chave primária de identificação exclusiva da tabela.
Chaves UUID e ULID
Em vez de usar números incrementando automaticamente como suas chaves primárias de um modelo Eloquent, você pode optar por usar UUIDs em vez disso. Os UUID são identificadores alfanuméricos universalmente exclusivos que tem 36 caracteres de comprimento.
Se você gostaria que um modelo use uma chave UUID em vez de um inteiro incrementado automaticamente, você pode usar a trait Illuminate\Database\Eloquent\Concerns\HasUuids
no modelo. Claro, você deve garantir que o modelo tenha uma coluna de chave primária equivalente a UUID:
use Illuminate\Database\Eloquent\Concerns\HasUuids;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use HasUuids;
// ...
}
$article = Article::create(['title' => 'Traveling to Europe']);
$article->id; // "8f8e8478-9035-4d23-b9a7-62f4d2612ce5"
Por padrão, a trait HasUuids
irá gerar UUIDs "ordenados" para seus modelos. Estes UUIDs são mais eficientes para armazenamento de banco de dados indexado, porque podem ser ordenados lexicograficamente.
Você pode substituir o processo de geração do UUID para um determinado modelo definindo o método newUniqueId
no modelo. Além disso, você pode especificar quais colunas devem receber UUIDs, definindo um método uniqueIds
no modelo:
use Ramsey\Uuid\Uuid;
/**
* Gere um novo UUID para o modelo.
*/
public function newUniqueId(): string
{
return (string) Uuid::uuid4();
}
/**
* Obtenha as colunas que devem receber um identificador exclusivo.
*
* @return array<int, string>
*/
public function uniqueIds(): array
{
return ['id', 'discount_code'];
}
Se você quiser, você pode optar por utilizar "ULIDs" em vez de UUIDs. ULIDs são parecidos com UUIDs; no entanto, eles têm apenas 26 caracteres de comprimento. Tal como os UUIDs ordenados, ULIDs podem ser ordenados lexicograficamente para indexação eficiente do banco de dados. Para usar ULIDs, você deve utilizar a trait Illuminate\Database\Eloquent\Concerns\HasUlids
em seu modelo. Você também deve garantir que o modelo tenha uma coluna primária equivalente a ULID:
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
use HasUlids;
// ...
}
$article = Article::create(['title' => 'Traveling to Asia']);
$article->id; // "01gd4d3tgrrfqeda94gdbtdk5c"
Timestamps
Por padrão, o Eloquent espera que existam as colunas created_at
e updated_at
na tabela correspondente do banco de dados do seu modelo. O Eloquent irá definir automaticamente os valores das colunas quando os modelos são criados ou atualizados. Se você não deseja que essas colunas sejam gerenciadas automaticamente pelo Eloquent, você deve definir uma propriedade $timestamps
no seu modelo com um valor de false
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Indica se o modelo deve ter registro de data e hora.
*
* @var bool
*/
public $timestamps = false;
}
Se você precisar personalizar o formato de seus timestamps no modelo, defina a propriedade $dateFormat
do seu modelo. Esta propriedade determina como os atributos de data são armazenados no banco de dados, bem como seu formato quando o modelo é serializado em um array ou JSON:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* O formato de armazenamento das colunas de data do modelo.
*
* @var string
*/
protected $dateFormat = 'U';
}
Se você precisa personalizar os nomes das colunas utilizadas para armazenar os timestamps, você pode definir as constantes CREATED_AT
e UPDATED_AT
na sua classe modelo.
<?php
class Flight extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
}
Se você gostaria de realizar operações de modelo sem alterar o carimbo de data/hora, você pode realizar as operações dentro de um método closure dado ao método withoutTimestamps
:
Model::withoutTimestamps(fn () => $post->increment('reads'));
Conexões de Banco de Dados
Por padrão, todos os modelos Eloquent usarão a conexão padrão configurada para seu aplicativo. Se você gostaria de especificar uma conexão diferente que deve ser usada quando estiver interagindo com um modelo específico, você deve definir a propriedade $connection
no modelo:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* A conexão de banco de dados que deve ser usada pelo modelo.
*
* @var string
*/
protected $connection = 'mysql';
}
Valores padrão de atributos
Por padrão, uma instância de modelo recém-instanciada não conterá nenhum valor de atributo. Se você gostaria de definir os valores padrão para alguns dos atributos do seu modelo, você pode definir uma propriedade $attributes
no seu modelo. Os valores dos atributos colocados na matriz $attributes
devem estar em seu formato bruto, "armazenável" como se eles foram lidos apenas a partir do banco de dados:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Valores padrão do modelo para atributos.
*
* @var array
*/
protected $attributes = [
'options' => '[]',
'delayed' => false,
];
}
Configurando Eloquent estrito
Laravel oferece vários métodos que permitem configurar o comportamento e "restrições" do Eloquent em uma variedade de situações.
Em primeiro lugar, o método preventLazyLoading
aceita um argumento booleano opcional que indica se deve-se evitar a carga preguiçosa. Por exemplo, você pode querer apenas desativar a carga preguiçosa em ambientes não-produção para que seu ambiente de produção continue funcionando normalmente mesmo se um relacionamento carregado preguiçosamente estiver presente no código de produção por acidente. Tipicamente, este método deve ser invocado no método boot
do seu provedor de serviços de aplicação:
use Illuminate\Database\Eloquent\Model;
/**
* Inicialize qualquer serviço de aplicativo.
*/
public function boot(): void
{
Model::preventLazyLoading(! $this->app->isProduction());
}
Além disso, você pode instruir o Laravel a lançar uma exceção ao tentar preencher um atributo não preenchível invocando o método preventSilentlyDiscardingAttributes
. Isso pode ajudar a evitar erros inesperados durante o desenvolvimento local quando estiver tentando definir um atributo que não foi adicionado na matriz fillable
do modelo:
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
Recuperando Modelos
Uma vez que você tenha criado um modelo e sua tabela de banco de dados associada, você está pronto para começar a obter dados do seu banco de dados. Você pode pensar em cada modelo Eloquent como um poderoso construtor de consultas permitindo-lhe consultar o banco de dados fluentemente na tabela de banco de dados associada ao modelo. O método all
do modelo irá recuperar todos os registros da tabela de banco de dados associada ao modelo:
use App\Models\Flight;
foreach (Flight::all() as $flight) {
echo $flight->name;
}
Construção de consultas
O método all
do Eloquent retornará todos os resultados da tabela do modelo. No entanto, como cada modelo Eloquent serve como um construidor de consultas, você pode adicionar restrições adicionais às consultas e depois invocar o método 'get' para obter os resultados:
$flights = Flight::where('active', 1)
->orderBy('name')
->take(10)
->get();
NOTA
Como os modelos Eloquent são construtores de consulta, você deve revisar todos os métodos fornecidos pelo construtor de consulta do Laravel. Você pode usar qualquer um desses métodos ao escrever suas consultas Eloquent.
Atualizando Modelos
Se já tiver uma instância de um modelo Eloquent que foi retirado do banco de dados, você pode "atualizar" o modelo usando os métodos fresh
e refresh
. O método fresh
irá re-recuperar o modelo do banco de dados. A instância existente do modelo não será afetada:
$flight = Flight::where('number', 'FR 900')->first();
$freshFlight = $flight->fresh();
O método refresh
irá reidratar o modelo existente utilizando dados novos do banco de dados. Além disso, todas as suas relações carregadas também serão atualizadas:
$flight = Flight::where('number', 'FR 900')->first();
$flight->number = 'FR 456';
$flight->refresh();
$flight->number; // "FR 900"
Coleções
Como vimos, métodos eloquentes como all
e get
recuperam vários registros do banco de dados. No entanto, esses métodos não retornam um array PHP simples. Em vez disso, é retornada uma instância de Illuminate\Database\Eloquent\Collection
.
A classe Eloquent Collection
estende a base Illuminate\Support\Collection
do Laravel, que fornece uma variedade de métodos úteis para interagir com coleções de dados. Por exemplo, o método reject
pode ser usado para remover modelos de uma coleção com base nos resultados de um closure invocado:
$flights = Flight::where('destination', 'Paris')->get();
$flights = $flights->reject(function (Flight $flight) {
return $flight->cancelled;
});
Além dos métodos fornecidos pela classe de coleção básica do Laravel, a classe de coleções Eloquent fornece alguns métodos extras especificamente destinados para interagir com coleções de modelos Eloquent.
Como todas as coleções do Laravel implementam as interfaces iteráveis do PHP, você pode percorrer coleções como se elas fossem um array:
foreach ($flights as $flight) {
echo $flight->name;
}
Resultados em Chunks
O seu aplicativo pode ficar sem memória se você tentar carregar dezenas de milhares de registros Eloquent através dos métodos all
ou get
. Em vez de usar esses métodos, o método chunk
pode ser usado para processar grandes números de modelos de maneira mais eficiente.
O método chunk
irá obter um subconjunto de modelos Eloquent, passando-os para uma função closure para processamento. Como só é obtido o atual pedaço de modelos Eloquent por vez, o método chunk
irá fornecer significativamente uso reduzido de memória quando se trabalha com um grande número de modelos:
use App\Models\Flight;
use Illuminate\Database\Eloquent\Collection;
Flight::chunk(200, function (Collection $flights) {
foreach ($flights as $flight) {
// ...
}
});
O primeiro argumento passado para o método chunk
é o número de registros que você deseja receber por "chunk". O closure passado como segundo argumento será invocado para cada "chunk" que for obtido do banco de dados. Uma consulta ao banco de dados será executada para obter cada "chunk" de registros passados ao closure.
Se você está filtrando os resultados do método chunk
com base em uma coluna que também será atualizada enquanto itera sobre os resultados, você deve usar o método chunkById
. O uso do método chunk
nesses cenários poderia levar a resultados inesperados e inconsistentes. Internamente, o método chunkById
sempre irá buscar modelos com uma coluna id
maior que o último modelo no último chunk:
Flight::where('departed', true)
->chunkById(200, function (Collection $flights) {
$flights->each->update(['departed' => false]);
}, $column = 'id');
Chunking usando coleções preguiçosas
O método lazy
funciona de maneira similar à o método chunk
, no sentido que, nos bastidores, ele executa a consulta em partes. Porém, em vez de passar cada parte diretamente para um callback como é, o método lazy
retorna uma coleção de modelos Eloquent, que permite interagir com os resultados como um único fluxo:
use App\Models\Flight;
foreach (Flight::lazy() as $flight) {
// ...
}
Se você está filtrando os resultados do método lazy
com base em uma coluna que também será atualizada enquanto itera sobre os resultados, você deve usar o método lazyById
. Internamente, o método lazyById
sempre recupera modelos com a coluna id
maior que o último modelo no último pedaço:
Flight::where('departed', true)
->lazyById(200, $column = 'id')
->each->update(['departed' => false]);
Você pode filtrar os resultados baseados na ordem decrescente do id
usando o método lazyByIdDesc
.
Cursores
Semelhante ao método lazy
, o método cursor
pode ser usado para reduzir significativamente o consumo de memória do seu aplicativo quando você itera através de dezenas de milhares de registros do modelo Eloquent.
O método cursor
irá executar apenas uma consulta ao banco de dados; contudo, os modelos individuais do Eloquent não serão hidratados até que eles sejam realmente iterados. Portanto, apenas um modelo Eloquent é mantido na memória em qualquer momento enquanto se itera sobre o cursor.
ATENÇÃO
Como o método cursor
só pode manter um único modelo Eloquent na memória de cada vez, ele não pode carregar em massa relacionamentos. Se você precisar carregar em massa relacionamentos, considere usar o método 'lazy' em vez disso.
Internamente, o método cursor
utiliza geradores PHP para implementar essa funcionalidade:
use App\Models\Flight;
foreach (Flight::where('destination', 'Zurich')->cursor() as $flight) {
// ...
}
O cursor retorna uma instância de Illuminate\Support\LazyCollection
. Coleções preguiçosas permitem que você use muitos dos métodos de coleção disponíveis em coleções típicas do Laravel, enquanto carrega apenas um modelo na memória por vez:
use App\Models\User;
$users = User::cursor()->filter(function (User $user) {
return $user->id > 500;
});
foreach ($users as $user) {
echo $user->id;
}
Embora o método cursor use muito menos memória do que uma consulta regular (apenas mantendo um único modelo Eloquent na memória de cada vez), ele ainda acabará por esgotar a memória. Isso é devido ao fato de que o driver PDO do PHP armazena em cache todos os resultados da consulta bruta em seu buffer internamente. Se você estiver lidando com um grande número de registros Eloquent, considere usar o método lazy
em vez disso.
Subconsultas Avançadas
Select
em Subconsultas
O Eloquent também oferece suporte avançado a subconsultas, o que permite que você extraia informações de tabelas relacionadas em uma única consulta. Por exemplo, vamos imaginar que temos uma tabela de destinations
de voos e uma tabela de flights
para destinos. A tabela flights
contém uma coluna arrived_at
que indica quando o voo chegou ao destino.
Usando a funcionalidade de subconsulta disponível nos métodos select
e addSelect
do construtor de consultas, podemos selecionar todas os destinations
e o nome do voo que chegou mais recentemente em cada destino usando uma única consulta:
use App\Models\Destination;
use App\Models\Flight;
return Destination::addSelect(['last_flight' => Flight::select('name')
->whereColumn('destination_id', 'destinations.id')
->orderByDesc('arrived_at')
->limit(1)
])->get();
Subconsulta de ordenação
Além disso, a função orderBy
do construtor de consultas suporta subconsultas. Continuando com o exemplo dos voos, podemos usar essa funcionalidade para classificar todos os destinos com base na hora da última chegada de um voo a esse destino. Novamente, isso pode ser feito enquanto executa uma única consulta ao banco de dados:
return Destination::orderByDesc(
Flight::select('arrived_at')
->whereColumn('destination_id', 'destinations.id')
->orderByDesc('arrived_at')
->limit(1)
)->get();
Recuperando Modelos/Agregados
Além de buscar todos os registros que correspondem a uma consulta específica, você também pode buscar um único registro usando os métodos find
, first
ou firstWhere
. Em vez de retornar uma coleção de modelos, esses métodos retornam uma única instância do modelo:
use App\Models\Flight;
// Recuperar um modelo pela sua chave primária...
$flight = Flight::find(1);
// Recupere o primeiro modelo que corresponde às restrições da consulta...
$flight = Flight::where('active', 1)->first();
// Alternativa para recuperar o primeiro modelo que corresponde às restrições da consulta...
$flight = Flight::firstWhere('active', 1);
Às vezes você pode querer executar alguma outra ação se não houver resultados encontrados. Os métodos findOr
e firstOr
retornarão uma única instância de modelo ou, se não houver resultados encontrados, executarão o closure dado. O valor retornado pelo closure será considerado o resultado do método:
$flight = Flight::findOr(1, function () {
// ...
});
$flight = Flight::where('legs', '>', 3)->firstOr(function () {
// ...
});
Exceções de Não Encontrado
Às vezes você pode querer lançar uma exceção se um modelo não for encontrado. Isso é particularmente útil em rotas ou controladores. Os métodos findOrFail
e firstOrFail
recuperarão o primeiro resultado da consulta; no entanto, se nenhum resultado for encontrado, uma exceção Illuminate\Database\Eloquent\ModelNotFoundException
será lançada:
$flight = Flight::findOrFail(1);
$flight = Flight::where('legs', '>', 3)->firstOrFail();
Se o ModelNotFoundException
não for capturado, uma resposta HTTP 404 é automaticamente enviada de volta ao cliente:
use App\Models\Flight;
Route::get('/api/flights/{id}', function (string $id) {
return Flight::findOrFail($id);
});
Recuperando ou Criando Modelos
O método firstOrCreate
tentará localizar um registro de banco de dados usando as pares coluna/valor fornecidos. Se o modelo não puder ser encontrado no banco de dados, um registro será inserido com os atributos resultantes da fusão do primeiro argumento do array com o segundo argumento opcional do array:
O método firstOrNew
, assim como o firstOrCreate
, tentará localizar um registro no banco de dados que corresponda aos atributos fornecidos. No entanto, se um modelo não for encontrado, uma nova instância do modelo será retornada. Observe que o modelo retornado por firstOrNew
ainda não foi persistido no banco de dados. Você precisará chamar manualmente o método save
para persistir:
use App\Models\Flight;
// Recuperar voo pelo nome ou criá-lo caso ele não exista...
$flight = Flight::firstOrCreate([
'name' => 'London to Paris'
]);
// Recupere o voo pelo nome ou crie-o com os atributos name, delayed e arrival_time...
$flight = Flight::firstOrCreate(
['name' => 'London to Paris'],
['delayed' => 1, 'arrival_time' => '11:30']
);
// Recuperar voo por nome ou instanciar uma nova instância de voo...
$flight = Flight::firstOrNew([
'name' => 'London to Paris'
]);
// Recupere o voo pelo nome ou instancie com os atributos name, delayed e arrival_time...
$flight = Flight::firstOrNew(
['name' => 'Tokyo to Sydney'],
['delayed' => 1, 'arrival_time' => '11:30']
);
Recuperando Agregados
Ao interagir com modelos Eloquent, você também pode usar o count
, sum
, max
e outros métodos agregados fornecidos pelo construtor de consultas do Laravel. Como se poderia esperar, esses métodos retornam um valor escalar em vez de uma instância do modelo Eloquent:
$count = Flight::where('active', 1)->count();
$max = Flight::where('active', 1)->max('price');
Inserindo e Atualizando Modelos
Inserindo
Claro, quando usamos Eloquent não precisamos apenas buscar modelos do banco de dados, também precisamos inserir novos registros. Felizmente, o Eloquent torna isso simples. Para inserir um novo registro no banco de dados, você deve instanciar uma nova instância do modelo e definir atributos no modelo. Em seguida, chame o método save
na instância do modelo:
<?php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Flight;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class FlightController extends Controller
{
/**
* Armazene um novo voo no banco de dados.
*/
public function store(Request $request): RedirectResponse
{
// Validar a solicitação...
$flight = new Flight;
$flight->name = $request->name;
$flight->save();
return redirect('/flights');
}
}
Neste exemplo, atribuímos o campo name
da requisição HTTP recebida ao atributo name
da instância do modelo App\Models\Flight
. Quando chamamos o método save
, um registro será inserido no banco de dados. Os timestamps created_at
e updated_at
do modelo serão automaticamente definidos quando o método save
for chamado, então não é necessário defini-los manualmente.
Alternativamente, você pode usar o método create
para salvar um novo modelo usando uma única instrução PHP. A instância do modelo inserida será retornada para você pelo método create
:
use App\Models\Flight;
$flight = Flight::create([
'name' => 'London to Paris',
]);
Porém, antes de usar o método create
, você precisará especificar uma propriedade fillable
ou guarded
na sua classe de modelo. Essas propriedades são necessárias porque todos os modelos Eloquent são protegidos contra vulnerabilidades de atribuição em massa por padrão. Para saber mais sobre a atribuição em massa, consulte a documentação de atribuição em massa.
Atualizações
O método save
também pode ser usado para atualizar modelos que já existem no banco de dados. Para atualizar um modelo, você deve recuperá-lo e definir quaisquer atributos que deseja atualizar. Em seguida, você deve chamar o método save
do modelo. Novamente, o timestamp updated_at
será automaticamente atualizado, então não há necessidade de definir manualmente seu valor:
use App\Models\Flight;
$flight = Flight::find(1);
$flight->name = 'Paris to London';
$flight->save();
Às vezes, você pode precisar atualizar um modelo existente ou criar um novo se não houver um modelo correspondente. Como o método firstOrCreate
, o método updateOrCreate
salva o modelo, então não há necessidade de chamar manualmente o método save
.
No exemplo abaixo, se um voo existir com uma localização de departure
de Oakland
e uma localização de destination
de San Diego
, suas colunas price
e discounted
serão atualizadas. Se nenhum voo existir, um novo voo será criado que tenha os atributos resultantes da fusão do primeiro array de argumentos com o segundo array de argumentos:
$flight = Flight::updateOrCreate(
['departure' => 'Oakland', 'destination' => 'San Diego'],
['price' => 99, 'discounted' => 1]
);
Atualização em Massa
Atualizações também podem ser feitas em modelos que correspondam a uma determinada consulta. Neste exemplo, todos os voos que são active
e têm um destination
de San Diego
serão marcados como atrasados:
Flight::where('active', 1)
->where('destination', 'San Diego')
->update(['delayed' => 1]);
O método update
espera uma matriz de pares coluna-valor representando as colunas que devem ser atualizadas. O método update
retorna o número de linhas afetadas.
ATENÇÃO
Ao emitir uma atualização em massa via Eloquent, os eventos do modelo saving
, saved
, updating
e updated
não serão acionados para os modelos atualizados. Isso ocorre porque os modelos nunca são realmente recuperados ao emitir uma atualização em massa.
Examinando alterações de atributo
O Eloquent fornece os métodos isDirty
, isClean
e wasChanged
para examinar o estado interno do seu modelo e determinar como seus atributos mudaram desde que o modelo foi originalmente recuperado.
O método isDirty
determina se algum dos atributos do modelo foi alterado desde que o modelo foi recuperado. Você pode passar um nome específico de atributo ou uma matriz de atributos para o método isDirty
para determinar se algum dos atributos está "dirty". O método isClean
determinará se um atributo permaneceu inalterado desde que o modelo foi recuperado. Este método também aceita um argumento opcional de atributo:
use App\Models\User;
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);
$user->title = 'Painter';
$user->isDirty(); // true
$user->isDirty('title'); // true
$user->isDirty('first_name'); // false
$user->isDirty(['first_name', 'title']); // true
$user->isClean(); // false
$user->isClean('title'); // false
$user->isClean('first_name'); // true
$user->isClean(['first_name', 'title']); // false
$user->save();
$user->isDirty(); // false
$user->isClean(); // true
O método wasChanged
determina se algum atributo foi alterado quando o modelo foi salvo pela última vez dentro do ciclo de solicitação atual. Se necessário, você pode passar um nome de atributo para ver se um atributo específico foi alterado:
$user = User::create([
'first_name' => 'Taylor',
'last_name' => 'Otwell',
'title' => 'Developer',
]);
$user->title = 'Painter';
$user->save();
$user->wasChanged(); // true
$user->wasChanged('title'); // true
$user->wasChanged(['title', 'slug']); // true
$user->wasChanged('first_name'); // false
$user->wasChanged(['first_name', 'title']); // true
O método getOriginal
retorna um array contendo os atributos originais do modelo independentemente de quaisquer alterações feitas no modelo desde que ele foi recuperado. Se necessário, você pode passar um nome específico de atributo para obter o valor original de um atributo específico:
$user = User::find(1);
$user->name; // John
$user->email; // john@example.com
$user->name = "Jack";
$user->name; // Jack
$user->getOriginal('name'); // John
$user->getOriginal(); // Matriz de atributos originais...
Atribuição em massa
Você pode usar o método create
para salvar um novo modelo usando uma única instrução PHP. A instância do modelo inserida será retornada para você pelo método:
use App\Models\Flight;
$flight = Flight::create([
'name' => 'London to Paris',
]);
Porém, antes de usar o método create
, você precisará especificar uma propriedade fillable
ou guarded
na sua classe de modelo. Essas propriedades são necessárias porque todos os modelos Eloquent são protegidos contra vulnerabilidades de atribuição em massa por padrão.
Uma vulnerabilidade de atribuição em massa ocorre quando um usuário passa um campo inesperado de uma solicitação HTTP e esse campo altera uma coluna no seu banco de dados que você não esperava. Por exemplo, um usuário malicioso pode enviar um parâmetro is_admin
através de uma solicitação HTTP, que é então passado para o método create
do seu modelo, permitindo que o usuário aumente seu nível de administrador.
Então, para começar, você deve definir quais atributos do modelo deseja tornar atribuíveis em massa. Você pode fazer isso usando a propriedade $fillable
no modelo. Por exemplo, vamos tornar o atributo name
do nosso modelo Flight
atribuível em massa:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Os atributos que são atribuíveis em massa.
*
* @var array
*/
protected $fillable = ['name'];
}
Uma vez que você tenha especificado quais atributos são atribuíveis em massa, você pode usar o método create
para inserir um novo registro no banco de dados. O método create
retorna a instância do modelo recém-criado:
$flight = Flight::create(['name' => 'London to Paris']);
Se você já tiver uma instância de modelo, poderá usar o método fill
para preenchê-la com um array de atributos:
$flight->fill(['name' => 'Amsterdam to Frankfurt']);
Atribuição em massa e colunas JSON
Ao atribuir colunas JSON, cada chave da coluna deve ser especificada na matriz $fillable
do seu modelo. Para segurança, o Laravel não suporta a atualização de atributos aninhados JSON ao usar a propriedade guarded
:
/**
* Os atributos que são atribuíveis em massa.
*
* @var array
*/
protected $fillable = [
'options->enabled',
];
Permitindo Atribuição em Massa
Se você quiser tornar todos os seus atributos atribuíveis em massa, você pode definir a propriedade $guarded
do seu modelo como um array vazio. Se você escolher desproteger seu modelo, você deve tomar cuidado especial para sempre criar manualmente os arrays passados para os métodos fill
, create
e update
do Eloquent:
/**
* Os atributos que não são atribuíveis em massa.
*
* @var array
*/
protected $guarded = [];
Exceções de Atribuição em Massa
Por padrão, atributos que não estão incluídos na matriz $fillable
são descartados silenciosamente quando operações de atribuição em massa são executadas. Em produção, esse é o comportamento esperado; no entanto, durante o desenvolvimento local, pode levar a confusão sobre por que as alterações no modelo não estão tendo efeito.
Se desejar, você pode instruir o Laravel a lançar uma exceção ao tentar preencher um atributo não preenchível invocando o método preventSilentlyDiscardingAttributes
. Normalmente, este método deve ser invocado no método boot
da classe AppServiceProvider
do seu aplicativo:
use Illuminate\Database\Eloquent\Model;
/**
* Inicialize qualquer serviço de aplicativo.
*/
public function boot(): void
{
Model::preventSilentlyDiscardingAttributes($this->app->isLocal());
}
Upserts
O método upsert
do Eloquent pode ser usado para atualizar ou criar registros em uma única operação atômica. O primeiro argumento do método consiste nos valores a serem inseridos ou atualizados, enquanto o segundo argumento lista a(s) coluna(s) que identifica exclusivamente os registros na tabela associada. O terceiro e último argumento é um array das colunas que devem ser atualizadas se um registro correspondente já existir no banco de dados. O método upsert
irá definir automaticamente as colunas created_at
e updated_at
se os timestamps estiverem habilitados no modelo:
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], uniqueBy: ['departure', 'destination'], update: ['price']);
ATENÇÃO
Todos os bancos de dados, exceto o SQL Server, exigem que as colunas no segundo argumento do método upsert
tenham um índice "primary" ou "unique". Além disso, o driver MySQL ignora o segundo argumento do método upsert
e sempre usa os índices "primary" e "unique" da tabela para detectar registros existentes.
Deletando Modelos
Para excluir um modelo, você pode chamar o método delete
na instância do modelo:
use App\Models\Flight;
$flight = Flight::find(1);
$flight->delete();
Você pode chamar o método truncate
para excluir todos os registros associados ao modelo no banco de dados. A operação truncate
também irá redefinir quaisquer IDs incrementados automaticamente na tabela associada ao modelo:
Flight::truncate();
Deletando um Modelo Existente pela sua Chave Primária
No exemplo acima, estamos recuperando o modelo do banco de dados antes de chamar o método delete
. No entanto, se você souber a chave primária do modelo, poderá excluí-lo sem recuperá-lo explicitamente chamando o método destroy
. Além de aceitar uma única chave primária, o método destroy
também aceita múltiplas chaves primárias, um array de chaves primárias ou uma coleção de chaves primárias:
Flight::destroy(1);
Flight::destroy(1, 2, 3);
Flight::destroy([1, 2, 3]);
Flight::destroy(collect([1, 2, 3]));
ATENÇÃO
O método destroy
carrega cada modelo individualmente e chama o método delete
, de modo que os eventos deleting
e deleted
sejam devidamente enviados para cada modelo.
Deletando Modelos Usando Consultas
Claro, você pode construir uma consulta Eloquent para excluir todos os modelos que correspondem aos critérios da sua consulta. Neste exemplo, vamos excluir todos os voos que estão marcados como inativos. Assim como as atualizações em massa, as exclusões em massa não despacharão eventos de modelo para os modelos que estão sendo excluídos:
$deleted = Flight::where('active', 0)->delete();
ATENÇÃO
Ao executar uma instrução de exclusão em massa via Eloquent, os eventos deleting
e deleted
do modelo não serão enviados para os modelos excluídos. Isso ocorre porque os modelos nunca são realmente recuperados ao executar a instrução de exclusão.
Soft Delete
Além de realmente remover registros do seu banco de dados, o Eloquent também pode "excluir suavemente" os modelos. Quando os modelos são excluídos suavemente, eles não são realmente removidos do seu banco de dados. Em vez disso, um atributo deleted_at
é definido no modelo indicando a data e hora em que o modelo foi "excluído". Para habilitar exclusões suaves para um modelo, adicione a trait Illuminate\Database\Eloquent\SoftDeletes
ao modelo:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Flight extends Model
{
use SoftDeletes;
}
NOTA
A trait SoftDeletes
irá automaticamente converter o atributo deleted_at
em uma instância de DateTime
/ Carbon
para você.
Você também deve adicionar a coluna deleted_at
à sua tabela de banco de dados. O construtor de esquema (schema builder) do Laravel contém um método auxiliar para criar esta coluna:
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('flights', function (Blueprint $table) {
$table->softDeletes();
});
Schema::table('flights', function (Blueprint $table) {
$table->dropSoftDeletes();
});
Agora, quando você chama o método delete
no modelo, a coluna deleted_at
será definida para a data e hora atuais. No entanto, o registro do banco de dados do modelo permanecerá na tabela. Quando consultar um modelo que usa exclusão suave, os modelos excluídos serão automaticamente excluídos de todos os resultados da consulta.
Para determinar se uma instância de modelo foi excluída suavemente, você pode usar o método trashed
:
if ($flight->trashed()) {
// ...
}
Restaurando Modelos do Soft Delete
Às vezes você pode querer "desfazer" uma exclusão suave de um modelo. Para restaurar um modelo excluído suavemente, você pode chamar o método restore
em uma instância do modelo. O método restore
irá definir a coluna deleted_at
do modelo para null
:
$flight->restore();
Você também pode usar o método restore
em uma consulta para restaurar vários modelos. Novamente, como outras operações "em massa", isso não irá disparar nenhum evento do modelo para os modelos que são restaurados:
Flight::withTrashed()
->where('airline_id', 1)
->restore();
O método restore
também pode ser usado ao construir consultas de relacionamento:
$flight->history()->restore();
Deletando Modelos Permanentemente
Às vezes você pode precisar realmente remover um modelo do seu banco de dados. Você pode usar o método forceDelete
para remover permanentemente um modelo excluído com suavidade da tabela do banco de dados:
$flight->forceDelete();
Você também pode usar o método forceDelete
ao construir consultas de relacionamento Eloquent:
$flight->history()->forceDelete();
Consultando Modelos Excluídos
Incluindo Modelos Excluídos
Como mencionado acima, modelos excluídos suavemente serão automaticamente excluídos dos resultados da consulta. No entanto, você pode forçar modelos excluídos suavemente a serem incluídos nos resultados de uma consulta chamando o método withTrashed
na consulta:
use App\Models\Flight;
$flights = Flight::withTrashed()
->where('account_id', 1)
->get();
O método withTrashed
também pode ser chamado quando construindo uma relação de consulta:
$flight->history()->withTrashed()->get();
Recuperando Apenas Modelos Excluídos
O método onlyTrashed
irá recuperar apenas os modelos excluídos suavemente:
$flights = Flight::onlyTrashed()
->where('airline_id', 1)
->get();
Modelos de poda
Às vezes você pode querer excluir periodicamente modelos que não são mais necessários. Para fazer isso, você pode adicionar a traitIlluminate\Database\Eloquent\Prunable
ou Illuminate\Database\Eloquent\MassPrunable
para os modelos que gostaria de podar periodicamente. Depois de adicionar uma das traits ao modelo, implemente um método pruner
que retorne um construtor de consultas Eloquent que resolva os modelos que não são mais necessários:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
class Flight extends Model
{
use Prunable;
/**
* Obtenha a consulta do modelo podável.
*/
public function prunable(): Builder
{
return static::where('created_at', '<=', now()->subMonth());
}
}
Ao marcar modelos como Prunable
, você também pode definir um método de pruning
no modelo. Este método será chamado antes do modelo ser excluído. Este método pode ser útil para excluir quaisquer recursos adicionais associados ao modelo, como arquivos armazenados, antes que o modelo seja removido permanentemente do banco de dados:
/**
* Prepare o modelo para poda.
*/
protected function pruning(): void
{
// ...
}
Depois de configurar seu modelo "prunable", você deve agendar o comando Artisan model:prune
no arquivo routes/console.php
da sua aplicação. Você é livre para escolher o intervalo apropriado em que esse comando deve ser executado:
use Illuminate\Support\Facades\Schedule;
Schedule::command('model:prune')->daily();
Por trás das cenas, o comando model:prune
irá detectar automaticamente modelos "Prunable" dentro do diretório app/Models
da sua aplicação. Se os seus modelos estiverem em um local diferente, você pode usar a opção --model
para especificar os nomes das classes de modelo:
Schedule::command('model:prune', [
'--model' => [Address::class, Flight::class],
])->daily();
Se você quiser excluir certos modelos de serem podados enquanto todos os outros modelos detectados são podados, você pode usar a opção --except
:
Schedule::command('model:prune', [
'--except' => [Address::class, Flight::class],
])->daily();
Você pode testar sua consulta prunable
executando o comando model:prune
com a opção --pretend
. Ao fingir (--pretend), o comando model:prune
simplesmente relatará quantos registros seriam podados se o comando fosse realmente executado:
php artisan model:prune --pretend
ATENÇÃO
Os modelos de soft delete serão permanentemente excluídos (forceDelete
) se corresponderem à consulta de exclusão.
Poda em massa
Quando os modelos são marcados com a trait Illuminate\Database\Eloquent\MassPrunable
, os modelos são excluídos do banco de dados usando consultas de exclusão em massa. Portanto, o método pruning
não será invocado, nem os eventos deleting
e deleted
do modelo serão enviados. Isso ocorre porque os modelos nunca são realmente recuperados antes da exclusão, tornando o processo de poda muito mais eficiente:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\MassPrunable;
class Flight extends Model
{
use MassPrunable;
/**
* Obtenha a consulta do modelo podável.
*/
public function prunable(): Builder
{
return static::where('created_at', '<=', now()->subMonth());
}
}
Replicando Modelos
Você pode criar uma cópia não salva de uma instância de modelo existente usando o método replicate
. Este método é particularmente útil quando você tem instâncias de modelo que compartilham muitos dos mesmos atributos:
use App\Models\Address;
$shipping = Address::create([
'type' => 'shipping',
'line_1' => '123 Example Street',
'city' => 'Victorville',
'state' => 'CA',
'postcode' => '90001',
]);
$billing = $shipping->replicate()->fill([
'type' => 'billing'
]);
$billing->save();
Para excluir um ou mais atributos de serem replicados para o novo modelo, você pode passar uma matriz para o método replicate
:
$flight = Flight::create([
'destination' => 'LAX',
'origin' => 'LHR',
'last_flown' => '2020-03-04 11:00:00',
'last_pilot_id' => 747,
]);
$flight = $flight->replicate([
'last_flown',
'last_pilot_id'
]);
Escopos de consulta
Escopos Globais
Os escopos globais permitem adicionar restrições a todas as consultas para um determinado modelo. A funcionalidade de exclusão suave do próprio Laravel utiliza escopos globais para recuperar apenas os "modelos não excluídos" do banco de dados. Escrever seus próprios escopos globais pode fornecer uma maneira conveniente e fácil de garantir que todas as consultas para um determinado modelo recebam certas restrições.
Geração de escopos
Para gerar um novo escopo global, você pode invocar o comando Artisan make:scope
, que colocará o escopo gerado no diretório app/Models/Scopes
da sua aplicação:
php artisan make:scope AncientScope
Escrevendo Escopos Globais
Escrever um escopo global é simples. Primeiro, use o comando make:scope
para gerar uma classe que implementa a interface Illuminate\Database\Eloquent\Scope
. A interface Scope
exige que você implemente um método: apply
. O método apply
pode adicionar restrições where
ou outros tipos de cláusulas à consulta conforme necessário:
<?php
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
class AncientScope implements Scope
{
/**
* Aplique o escopo a um determinado construtor de consultas Eloquent.
*/
public function apply(Builder $builder, Model $model): void
{
$builder->where('created_at', '<', now()->subYears(2000));
}
}
NOTA
Se o seu escopo global é adicionar colunas à cláusula SELECT da consulta, você deve usar o método addSelect
em vez de select
. Isso evitará a substituição involuntária da cláusula SELECT
existente da consulta.
Aplicando Escopos Globais
Para atribuir um escopo global a um modelo, você pode simplesmente colocar o atributo ScopedBy
no modelo:
<?php
namespace App\Models;
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Attributes\ScopedBy;
#[ScopedBy([AncientScope::class])]
class User extends Model
{
//
}
Ou você pode registrar manualmente o escopo global sobrescrevendo o método booted
do modelo e invocando o método addGlobalScope
do modelo. O método addGlobalScope
aceita uma instância de seu escopo como único argumento:
<?php
namespace App\Models;
use App\Models\Scopes\AncientScope;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* O método "inicializado" do modelo.
*/
protected static function booted(): void
{
static::addGlobalScope(new AncientScope);
}
}
Depois de adicionar o escopo no exemplo acima para o modelo App\Models\User
, uma chamada para o método User::all()
executará a seguinte consulta SQL:
select * from `users` where `created_at` < 0021-02-18 00:00:00
Escopos Globais Anônimos
O Eloquent também permite que você defina escopos globais usando closures, o que é particularmente útil para escopos simples que não exigem uma classe própria. Ao definir um escopo global usando um closure, você deve fornecer um nome de escopo de sua escolha como o primeiro argumento do método addGlobalScope
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* O método "inicializado" do modelo.
*/
protected static function booted(): void
{
static::addGlobalScope('ancient', function (Builder $builder) {
$builder->where('created_at', '<', now()->subYears(2000));
});
}
}
Removendo Escopos Globais
Se você quiser remover um escopo global para uma consulta específica, você pode usar o método withoutGlobalScope
. Este método aceita o nome da classe do escopo global como seu único argumento:
User::withoutGlobalScope(AncientScope::class)->get();
Ou, se você definiu o escopo global usando um closure, você deve passar a string que você atribuiu ao escopo global:
User::withoutGlobalScope('ancient')->get();
Se você gostaria de remover vários ou até mesmo todos os escopos globais da consulta, você pode usar o método withoutGlobalScopes
:
// Remova todos os escopos globais...
User::withoutGlobalScopes()->get();
// Remova alguns dos escopos globais...
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
Escopos Locais
Os escopos locais permitem que você defina conjuntos comuns de restrições de consulta que podem ser facilmente reutilizados em sua aplicação. Por exemplo, você pode precisar frequentemente recuperar todos os usuários considerados "populares". Para definir um escopo, prefira um método do modelo Eloquent com scope
.
Os escopos devem sempre retornar a mesma instância do construtor de consulta ou void
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Defina o escopo de uma consulta para incluir apenas usuários populares.
*/
public function scopePopular(Builder $query): void
{
$query->where('votes', '>', 100);
}
/**
* Defina o escopo de uma consulta para incluir apenas usuários ativos.
*/
public function scopeActive(Builder $query): void
{
$query->where('active', 1);
}
}
Utilizando o escopo local
Uma vez que o escopo tenha sido definido, você pode chamar os métodos de escopo ao consultar o modelo. No entanto, não deve incluir o prefixo scope
ao chamar o método. Você também pode encadear chamadas para vários escopos:
use App\Models\User;
$users = User::popular()->active()->orderBy('created_at')->get();
Combinando múltiplas escopos de modelo Eloquent através do operador de consulta or
pode exigir o uso de closures para alcançar a agrupação lógica:
$users = User::popular()->orWhere(function (Builder $query) {
$query->active();
})->get();
No entanto, como isso pode ser um pouco complicado, o Laravel fornece um método "de ordem superior" chamado orWhere
que permite encadear escopos de forma fluente sem o uso de closures:
$users = User::popular()->orWhere->active()->get();
Escopos Dinâmicos
Às vezes você pode querer definir um escopo que aceite parâmetros. Para começar, basta adicionar seus parâmetros adicionais à assinatura do seu método de escopo. Os parâmetros do escopo devem ser definidos após o parâmetro $query
:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* Defina o escopo de uma consulta para incluir apenas usuários de um determinado tipo.
*/
public function scopeOfType(Builder $query, string $type): void
{
$query->where('type', $type);
}
}
Uma vez que os argumentos esperados tenham sido adicionados à assinatura do seu método de escopo, você pode passar os argumentos ao chamar o escopo:
$users = User::ofType('admin')->get();
Comparando Modelos
Às vezes você pode precisar determinar se dois modelos são "iguais" ou não. Os métodos is
e isNot
podem ser usados para verificar rapidamente se dois modelos têm a mesma chave primária, tabela e conexão com o banco de dados ou não:
if ($post->is($anotherPost)) {
// ...
}
if ($post->isNot($anotherPost)) {
// ...
}
Os métodos is
e isNot
também estão disponíveis ao usar os relacionamentos belongsTo
, hasOne
, morphTo
e morphOne
. Este método é particularmente útil quando você gostaria de comparar um modelo relacionado sem emitir uma consulta para recuperar esse modelo:
if ($post->author()->is($user)) {
// ...
}
Eventos
NOTA
Quer transmitir seus eventos Eloquent diretamente para seu aplicativo de lado do cliente? Confira o transmissão de eventos de modelo.
Modelos eloquentes disparam vários eventos, permitindo que você se conecte aos seguintes momentos no ciclo de vida de um modelo: retrieved
, creating
, created
, updating
, updated
, saving
, saved
, deleting
, deleted
, trashed
, forceDeleting
, forceDeleted
, restoring
, restored
e replicating
.
O evento retrieved
será acionado quando um modelo existente for recuperado do banco de dados. Quando um novo modelo é salvo pela primeira vez, os eventos creating
e created
serão acionados. Os eventos updating
e updated
serão acionados quando um modelo existente for modificado e o método save
for chamado. Os eventos saving
/ saved
serão acionados quando um modelo for criado ou atualizado - mesmo que os atributos do modelo não tenham sido alterados. Os nomes dos eventos que terminam com -ing
são acionados antes de qualquer alteração no modelo ser persistida, enquanto os eventos que terminam com -ed
são acionados depois que as alterações no modelo foram persistidas.
Para começar a escutar eventos de modelo, defina uma propriedade $dispatchesEvents
no seu modelo Eloquent. Esta propriedade mapeia vários pontos do ciclo de vida do modelo Eloquent para suas próprias classes de evento. Cada classe de evento deve esperar receber uma instância do modelo afetado através de seu construtor:
<?php
namespace App\Models;
use App\Events\UserDeleted;
use App\Events\UserSaved;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
/**
* O mapa de eventos para o modelo.
*
* @var array<string, string>
*/
protected $dispatchesEvents = [
'saved' => UserSaved::class,
'deleted' => UserDeleted::class,
];
}
Depois de definir e mapear seus eventos Eloquent, você pode usar ouvintes de eventos para lidar com eles.
ATENÇÃO
Ao emitir uma consulta de atualização ou exclusão em massa via Eloquent, os eventos do modelo saved
, updated
, deleting
e deleted
não serão enviados para os modelos afetados. Isso ocorre porque os modelos nunca são realmente recuperados ao executar atualizações ou exclusões em massa.
Usando Closures
Em vez de usar classes de eventos personalizadas, você pode registrar closures que executam quando vários eventos do modelo são enviados. Normalmente, você deve registrar esses closures no método booted
do seu modelo:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* O método "inicializado" do modelo.
*/
protected static function booted(): void
{
static::created(function (User $user) {
// ...
});
}
}
Se necessário, você pode utilizar ouvintes de eventos anônimos que podem ser enfileirados ao registrar o evento do modelo. Isso instruirá o Laravel a executar o ouvinte de eventos do modelo em segundo plano usando a fila da sua aplicação:
use function Illuminate\Events\queueable;
static::created(queueable(function (User $user) {
// ...
}));
Observadores
Definindo Observadores
Se você estiver escutando muitos eventos em um modelo específico, você pode usar observadores para agrupar todos os seus ouvintes em uma única classe. As classes de observador têm nomes de métodos que refletem os eventos Eloquent que você deseja escutar. Cada um desses métodos recebe o modelo afetado como seu único argumento. O comando Artisan make:observer
é a maneira mais fácil de criar uma nova classe de observador:
php artisan make:observer UserObserver --model=User
Este comando colocará o novo observador em seu diretório app/Observers
. Se este diretório não existir, o Artisan o criará para você. Seu novo observador ficará assim:
<?php
namespace App\Observers;
use App\Models\User;
class UserObserver
{
/**
* Manipule o evento "criado" do usuário.
*/
public function created(User $user): void
{
// ...
}
/**
* Manipule o evento "atualizado" do usuário.
*/
public function updated(User $user): void
{
// ...
}
/**
* Manipule o evento "usuário excluído".
*/
public function deleted(User $user): void
{
// ...
}
/**
* Manipule o evento "restaurado" do usuário.
*/
public function restored(User $user): void
{
// ...
}
/**
* Manipule o evento "forceDeleted" do usuário.
*/
public function forceDeleted(User $user): void
{
// ...
}
}
Para registrar um observador, você pode colocar o atributo ObservedBy
no modelo correspondente:
use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
#[ObservedBy([UserObserver::class])]
class User extends Authenticatable
{
//
}
Ou você pode registrar manualmente um observador invocando o método observe
no modelo que deseja observar. Você pode registrar observadores no método boot
da classe AppServiceProvider
da sua aplicação:
use App\Models\User;
use App\Observers\UserObserver;
/**
* Inicialize qualquer serviço de aplicativo.
*/
public function boot(): void
{
User::observe(UserObserver::class);
}
NOTA
Existem eventos adicionais que um observador pode escutar, como saving
e retrieved
. Esses eventos são descritos na documentação eventos.
Observadores e Transações de Banco de Dados
Ao criar modelos dentro de uma transação de banco de dados, você pode instruir um observador para executar apenas seus manipuladores de eventos após o compromisso da transação de banco de dados. Você pode alcançar isso implementando a interface ShouldHandleEventsAfterCommit
em seu observador. Se não houver uma transação de banco de dados em andamento, os manipuladores de eventos serão executados imediatamente:
<?php
namespace App\Observers;
use App\Models\User;
use Illuminate\Contracts\Events\ShouldHandleEventsAfterCommit;
class UserObserver implements ShouldHandleEventsAfterCommit
{
/**
* Manipule o evento "criado" do usuário.
*/
public function created(User $user): void
{
// ...
}
}
Silenciar Eventos
Você pode ocasionalmente precisar "silenciar" temporariamente todos os eventos disparados por um modelo. Você pode conseguir isso usando o método withoutEvents
. O método withoutEvents
aceita uma função como seu único argumento. Qualquer código executado dentro desta função não irá disparar eventos do modelo, e qualquer valor retornado pela função será retornado pelo método withoutEvents
:
use App\Models\User;
$user = User::withoutEvents(function () {
User::findOrFail(1)->delete();
return User::find(2);
});
Salvando um único modelo sem eventos
Às vezes você pode querer "salvar" um modelo específico sem disparar nenhum evento. Você pode fazer isso usando o método saveQuietly
:
$user = User::findOrFail(1);
$user->name = 'Victoria Faith';
$user->saveQuietly();
Você também pode "atualizar", "excluir", "excluir suave", "restaurar" e "replicar" um modelo sem disparar nenhum evento.
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();