5 回答
TA贡献1827条经验 获得超8个赞
正如OP在他的评论中所说:数据库设计已经设置好了,因此Laravel的多态关系似乎不是这里的一个选项。
我喜欢克里斯·尼尔(Chris Neal)的答案,因为我最近不得不做类似的事情(编写我自己的数据库驱动程序来支持dbase / DBF文件的Eloquent),并且在Laravel的雄辩ORM的内部积累了丰富的经验。
我添加了我的个人风格,使代码更加动态,同时保持每个模型的显式映射。
我快速测试的支持功能:
Animal::find(1)按照您的问题中提出的问题工作Animal::all()也可以工作Animal::where(['type' => 'dog'])->get()将返回 -objects 作为集合AnimalDog使用此特征的每个雄辩类的动态对象映射
回退到 -model,以防未配置映射(或数据库中出现新映射)
Animal
弊:
它正在重写模型的内部和完全(复制和粘贴)。这意味着如果从框架到此成员函数有任何更新,则需要手动采用代码。
newInstance()newFromBuilder()
我希望它有所帮助,并且我会在您的场景中提出任何建议,问题和其他用例。以下是它的用例和示例:
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 {}
这是如何使用它的一个例子,下面是它的相应结果:
$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);
这将产生以下结果:
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
如果你想使用这里,你当然是它的完整代码:MorphTrait
<?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);
}
}
TA贡献1789条经验 获得超8个赞
您可以在拉拉维尔中使用多态关系,如官方拉拉维尔文档中所述。这是你如何做到这一点。
定义给定模型中的关系
class Animal extends Model{
public function animable(){
return $this->morphTo();
}
}
class Dog extends Model{
public function animal(){
return $this->morphOne('App\Animal', 'animable');
}
}
class Cat extends Model{
public function animal(){
return $this->morphOne('App\Animal', 'animable');
}
}
在这里,您需要在 animals 表中使用两列,第一列是animable_type,另一列是animable_id以确定在运行时附加到它的模型的类型。
您可以获取给定的狗或猫模型,
$animal = Animal::find($id);
$anim = $animal->animable; //this will return either Cat or Dog Model
之后,您可以使用实例of检查对象的类。$anim
如果您在应用程序中添加其他动物类型(即狐狸或狮子),则此方法将帮助您进行将来的扩展。它将在不更改代码库的情况下工作。这是满足您要求的正确方法。然而,在不使用多态关系的情况下,没有替代方法可以实现多态性和渴望加载在一起。如果不使用多态关系,则最终会得到多个数据库调用。但是,如果您有一个区分模式类型的列,则可能是您有一个错误的结构化架构。我建议你改进它,如果你想简化它以备将来的发展。
重写模型的内部新实例()和 newFromBuilder() 不是一种好/推荐的方法,一旦你从框架中获得更新,你必须重新处理它。
TA贡献1786条经验 获得超13个赞
我认为您可以重写模型上的方法,并从属性中检查类型,然后初始化相应的模型。newInstanceAnimal
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.
$modelName = ucfirst($attributes['type']);
$model = new $modelName((array) $attributes);
$model->exists = $exists;
$model->setConnection(
$this->getConnectionName()
);
$model->setTable($this->getTable());
$model->mergeCasts($this->casts);
return $model;
}
您还需要重写该方法。newFromBuilder
/**
* Create a new model instance that is existing.
*
* @param array $attributes
* @param string|null $connection
* @return static
*/
public function newFromBuilder($attributes = [], $connection = null)
{
$model = $this->newInstance([
'type' => $attributes['type']
], true);
$model->setRawAttributes((array) $attributes, true);
$model->setConnection($connection ?: $this->getConnectionName());
$model->fireModelEvent('retrieved', false);
return $model;
}
TA贡献1826条经验 获得超6个赞
如果您真的想这样做,可以在 Animal 模型中使用以下方法。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Animal extends Model
{
// other code in animal model ....
public static function __callStatic($method, $parameters)
{
if ($method == 'find') {
$model = parent::find($parameters[0]);
if ($model) {
switch ($model->type) {
case 'dog':
return new \App\Dog($model->attributes);
case 'cat':
return new \App\Cat($model->attributes);
}
return $model;
}
}
return parent::__callStatic($method, $parameters);
}
}
TA贡献1852条经验 获得超1个赞
我想我知道你在找什么。请考虑使用 Laravel 查询范围的优雅解决方案,有关其他信息,请参阅 https://laravel.com/docs/6.x/eloquent#query-scopes:
创建保存共享逻辑的父类:
class Animal extends \Illuminate\Database\Eloquent\Model
{
const TYPE_DOG = 'dog';
const TYPE_CAT = 'cat';
}
创建具有全局查询作用域和事件处理程序的子级(或多个级):saving
class Dog extends Animal
{
public static function boot()
{
parent::boot();
static::addGlobalScope('type', function(\Illuminate\Database\Eloquent\Builder $builder) {
$builder->where('type', self::TYPE_DOG);
});
// Add a listener for when saving models of this type, so that the `type`
// is always set correctly.
static::saving(function(Dog $model) {
$model->type = self::TYPE_DOG;
});
}
}
(这同样适用于另一个类,只需替换常量)Cat
全局查询作用域充当默认查询修改,以便类将始终查找包含 的记录。Dogtype='dog'
假设我们有 3 条记录:
- id:1 => Cat
- id:2 => Dog
- id:3 => Mouse
现在调用将导致 ,因为默认查询范围将找不到 哪个是 。调用 和 都将工作,尽管只有最后一个会给你一个实际的 Cat 对象。Dog::find(1)nullid:1CatAnimal::find(1)Cat::find(1)
这种设置的好处是,您可以使用上面的类来创建如下关系:
class Owner
{
public function dogs()
{
return $this->hasMany(Dog::class);
}
}
这种关系会自动只给你所有的动物与(以类的形式)。将自动应用查询范围。type='dog'Dog
此外,调用将自动将 设置为 由于事件挂钩(请参阅 https://laravel.com/docs/6.x/eloquent#events)。Dog::create($properties)type'dog'saving
请注意,调用没有默认值,因此在这里您需要手动设置(这是预期的)。Animal::create($properties)type
- 5 回答
- 0 关注
- 262 浏览
添加回答
举报
