为了账号安全,请及时绑定邮箱和手机立即绑定

使用雄辩获取模型子类型的实例

使用雄辩获取模型子类型的实例

PHP
潇潇雨雨 2022-09-17 21:32:43
我有一个基于表格的模型。Animalanimal此表包含一个字段,该字段可以包含诸如猫或狗之类的值。type我希望能够创建诸如以下内容的对象:class Animal extends Model { }class Dog extends Animal { }class Cat extends Animal { }然而,能够像这样获取动物:$animal = Animal::find($id);但是,根据字段的实例或取决于字段,我可以检查使用或将使用类型提示方法。原因是90%的代码是共享的,但一个可以吠叫,另一个可以喵喵叫。$animalDogCattypeinstance of我知道我可以做到,但这不是我想要的:我只有在它被获取后才能确定对象的类型。我也可以获取 Animal,然后在正确的对象上运行,但这是在进行两次数据库调用,我显然不想要。Dog::find($id)find()我试图寻找一种方法来“手动”实例化一个雄辩的模型,比如《动物的狗》,但我找不到任何相应的方法。我错过了任何想法或方法吗?
查看完整描述

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

    }

}


查看完整回答
反对 回复 2022-09-17
?
拉丁的传说

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() 不是一种好/推荐的方法,一旦你从框架中获得更新,你必须重新处理它。


查看完整回答
反对 回复 2022-09-17
?
开满天机

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;

    }


查看完整回答
反对 回复 2022-09-17
?
跃然一笑

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

    }

}


查看完整回答
反对 回复 2022-09-17
?
小怪兽爱吃肉

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


查看完整回答
反对 回复 2022-09-17
  • 5 回答
  • 0 关注
  • 262 浏览

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号