Como o OP afirmou em seus comentários: O design do banco de dados já está definido e, portanto, o relacionamento polimórfico do Laravel parece não ser uma opção aqui.
Gosto da resposta de Chris Neal porque tive que fazer algo semelhante recentemente (escrever meu próprio driver de banco de dados para dar suporte ao Eloquent para arquivos dbase / DBF) e adquiri muita experiência com os elementos internos do Eloquent ORM do Laravel.
Eu adicionei meu sabor pessoal a ele para tornar o código mais dinâmico, mantendo um mapeamento explícito por modelo.
Recursos suportados que testei rapidamente:
Animal::find(1)
funciona como solicitado em sua pergunta
Animal::all()
funciona bem
Animal::where(['type' => 'dog'])->get()
retornará AnimalDog
-objects como uma coleção
- Mapeamento dinâmico de objetos por classe eloquente que usa essa característica
- Fallback para
Animal
-model no caso de não haver mapeamento configurado (ou um novo mapeamento aparecer no DB)
Desvantagens:
- Está reescrevendo o modelo interno
newInstance()
e newFromBuilder()
inteiramente (copiar e colar). Isso significa que, se houver alguma atualização da estrutura para essas funções de membro, você precisará adotar o código manualmente.
Espero que ajude e estou pronto para todas as sugestões, perguntas e casos de uso adicionais no seu cenário. Aqui estão os casos de uso e exemplos para isso:
class Animal extends Model
{
use MorphTrait; // You'll find the trait in the very end of this answer
protected $morphKey = 'type'; // This is your column inside the database
protected $morphMap = [ // This is the value-to-class mapping
'dog' => AnimalDog::class,
'cat' => AnimalCat::class,
];
}
class AnimalCat extends Animal {}
class AnimalDog extends Animal {}
E este é um exemplo de como ele pode ser usado e abaixo dos respectivos resultados:
$cat = Animal::find(1);
$dog = Animal::find(2);
$new = Animal::find(3);
$all = Animal::all();
echo sprintf('ID: %s - Type: %s - Class: %s - Data: %s', $cat->id, $cat->type, get_class($cat), $cat, json_encode($cat->toArray())) . PHP_EOL;
echo sprintf('ID: %s - Type: %s - Class: %s - Data: %s', $dog->id, $dog->type, get_class($dog), $dog, json_encode($dog->toArray())) . PHP_EOL;
echo sprintf('ID: %s - Type: %s - Class: %s - Data: %s', $new->id, $new->type, get_class($new), $new, json_encode($new->toArray())) . PHP_EOL;
dd($all);
que resulta do seguinte:
ID: 1 - Type: cat - Class: App\AnimalCat - Data: {"id":1,"type":"cat"}
ID: 2 - Type: dog - Class: App\AnimalDog - Data: {"id":2,"type":"dog"}
ID: 3 - Type: new-animal - Class: App\Animal - Data: {"id":3,"type":"new-animal"}
// Illuminate\Database\Eloquent\Collection {#1418
// #items: array:2 [
// 0 => App\AnimalCat {#1419
// 1 => App\AnimalDog {#1422
// 2 => App\Animal {#1425
E, caso você queira usar o MorphTrait
aqui, é claro, o código completo:
<?php namespace App;
trait MorphTrait
{
public function newInstance($attributes = [], $exists = false)
{
// This method just provides a convenient way for us to generate fresh model
// instances of this current model. It is particularly useful during the
// hydration of new objects via the Eloquent query builder instances.
if (isset($attributes['force_class_morph'])) {
$class = $attributes['force_class_morph'];
$model = new $class((array)$attributes);
} else {
$model = new static((array)$attributes);
}
$model->exists = $exists;
$model->setConnection(
$this->getConnectionName()
);
$model->setTable($this->getTable());
return $model;
}
/**
* Create a new model instance that is existing.
*
* @param array $attributes
* @param string|null $connection
* @return static
*/
public function newFromBuilder($attributes = [], $connection = null)
{
$newInstance = [];
if ($this->isValidMorphConfiguration($attributes)) {
$newInstance = [
'force_class_morph' => $this->morphMap[$attributes->{$this->morphKey}],
];
}
$model = $this->newInstance($newInstance, true);
$model->setRawAttributes((array)$attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
private function isValidMorphConfiguration($attributes): bool
{
if (!isset($this->morphKey) || empty($this->morphMap)) {
return false;
}
if (!array_key_exists($this->morphKey, (array)$attributes)) {
return false;
}
return array_key_exists($attributes->{$this->morphKey}, $this->morphMap);
}
}