Php 使用Elount获取模型子类型的实例

Php 使用Elount获取模型子类型的实例,php,laravel,eloquent,Php,Laravel,Eloquent,我有一个基于Animal表的Animal模型 此表包含一个type字段,该字段可以包含诸如cat或dog之类的值 我希望能够创建以下对象: class Animal extends Model { } class Dog extends Animal { } class Cat extends Animal { } 然而,能够找到这样的动物: $animal = Animal::find($id); 但是,$animal将是Dog或Cat的实例,具体取决于类型字段,我可以使用实例进行检查,或

我有一个基于
Animal
表的
Animal
模型

此表包含一个
type
字段,该字段可以包含诸如cat或dog之类的值

我希望能够创建以下对象:

class Animal extends Model { }
class Dog extends Animal { }
class Cat extends Animal { }
然而,能够找到这样的动物:

$animal = Animal::find($id);
但是,
$animal
将是
Dog
Cat
的实例,具体取决于
类型
字段,我可以使用
实例
进行检查,或者使用类型提示方法进行检查。原因是90%的代码是共享的,但一个可以吠叫,另一个可以喵喵叫

我知道我可以做
Dog::find($id)
,但这不是我想要的:我只能在获取对象后确定对象的类型。我还可以获取动物,然后在正确的对象上运行
find()
,但这是在执行两个数据库调用,这显然是我不想要的


我试图寻找一种方法来“手动”实例化一个有说服力的模型,如Dog from Animal,但我找不到任何相应的方法。有什么我遗漏的想法或方法吗?

我想你可以覆盖
Animal
模型上的
newInstance
方法,从属性中检查类型,然后初始化相应的模型

公共函数newInstance($attributes=[],$exists=false)
{
//这种方法为我们生成新模型提供了一种方便的方法
//此当前模型的实例。在
//通过雄辩的查询生成器实例水合新对象。
$modelName=ucfirst($attributes['type']);
$model=新的$modelName((数组)$attributes);
$model->exists=$exists;
$model->setConnection(
$this->getConnectionName()
);
$model->setTable($this->getTable());
$model->mergeCasts($this->casts);
返回$model;
}
您还需要重写
newFromBuilder
方法


/**
*创建现有的新模型实例。
*
*@param数组$attributes
*@param string | null$连接
*@返回静态
*/
公共函数newFromBuilder($attributes=[],$connection=null)
{
$model=$this->newInstance([
'type'=>$attributes['type']
],对);
$model->setrawtattributes((数组)$attributes,true);
$model->setConnection($connection?:$this->getConnectionName());
$model->fireModelEvent('retrieved',false);
返回$model;
}

如果您真的想这样做,您可以在动物模型中使用以下方法

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

正如OP在其评论中所述:数据库设计已经设置好,因此Laravel似乎不是这里的选项

喜欢Chris Neal的答案,因为我最近不得不做一些类似的事情(编写自己的数据库驱动程序来支持Elount for dbase/DBF文件),并且在Laravel的Elount ORM的内部获得了很多经验

我为它添加了我的个人风格,使代码更加动态,同时保持每个模型的显式映射

我快速测试的支持功能:

  • Animal::find(1)
    按照您的问题中的要求工作
  • Animal::all()
    也适用
  • Animal::where(['type'=>'dog'])->get()
    将返回
    AnimalDog
    -对象作为集合
  • 每个使用此特性的雄辩类的动态对象映射
  • 在没有配置映射(或数据库中出现新映射)的情况下,返回到动物模型
缺点:

  • 它完全重写模型的内部
    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);
    }
}


您可以使用Laravel中的多态关系,如中所述。下面是你如何做到这一点的

定义模型中给定的关系

类动物扩展模型{
公共函数animable(){
返回$this->morphTo();
}
}
类狗扩展模型{
公共功能动物(){
返回$this->morphOne('App\Animal','animable');
}
}
类Cat扩展模型{
公共功能动物(){
返回$this->morphOne('App\Animal','animable');
}
}
在这里,
animals
表中需要两列,第一列是
animable\u type
,另一列是
animable\u id
,以确定运行时附加到它的模型的类型

您可以按给定的方式获取狗或猫模型

$animal=animal::find($id);
$anim=$animal->animatable//这将返回猫或狗模型
之后,您可以使用
instanceof
检查
$anim
对象的类

如果您在应用程序中添加其他动物类型(即狐狸或狮子),此方法将有助于您将来的扩展。它将在不改变代码库的情况下工作。这是实现您的要求的正确方法。但是,如果不使用多态关系,就没有其他方法可以实现多态性和即时加载。如果不使用多态关系,那么最终将得到不止一个数据库调用。但是,如果您有一个单独的列来区分模态类型,那么可能您有一个错误的s
- id:1 => Cat
- id:2 => Dog
- id:3 => Mouse
public function resolve()
{
    $model = $this;
    if ($this->type == 'dog'){
        $model = new Dog();
    }else if ($this->type == 'cat'){
        $model = new Cat();
    }
    $model->setRawAttributes($this->getAttributes(), true);
    return $model;
}
$animal = Animal::first()->resolve();