Yii2 // 模型作为模型属性

Yii2 // Model as Model attribute

实际上我正在做一个项目,我想把所有的数据库表都作为模型。但现在我停留在一点。 假设我有一个 "Master"-Table,其中定义了许多不同的关系,如下所示(简单示例):

人有一颗心;人只有一个大脑……等等…… 是否可以用其他模型填充主模型? 在 PHP 中它看起来像这样:

$human = new Human();
$human->heart = new Heart(); 
$human->brain = new Brain(); 

最后我想说:

$human-save(TRUE);

验证所有关系模型并将所有关系数据和人类对象保存在数据库中。

这可能吗?我在整个互联网上找不到类似的东西 O_o。

非常感谢!

您可以覆盖 ActiveModel Save 方法,根据 docs:

public function save($runValidation = true, $attributeNames = null)
{
    if ($this->getIsNewRecord()) {
        $save = $this->insert($runValidation, $attributeNames);
    } else {
        $save = $this->update($runValidation, $attributeNames) !== false;
    }

    /* Condition Work if heart and brain is also ActiveModel then 
       you can trigger save method on these models as well
       or you can add your custom logic as well.  
    */

    if($this->heart && $this->brain) {
         return $this->heart->save() && $this->brain->save();
    }

    return $save;

}

我建议您采用以下方法:

  1. 假设您的关系名称与嵌套对象的 属性 名称相同(调用 $model->link() 方法需要一些规则)
  2. 为具有嵌套模型(例如 ActiveRecordWithNestedModels)的模型声明通用 class
  3. 覆盖常见的 class 方法 savevalidate 以对这些操作执行级联(使用反射)
  4. 让你的模型继承这个共同点class

,作为覆盖validate方法的替代方法,您可以为rules公共方法构建一些合适的实现class。

这个常见的class can看起来如下(这是一个简单的草稿,没有测试,只是为了展示概念):

<?php

namespace app\models;

use yii\db\ActiveRecord;

class ActiveRecordWithNestedModels extends ActiveRecord
{

    public function save($runValidation = true, $attributeNames = null)
    {
        $saveResult = parent::save($runValidation, $attributeNames);

        $class = new \ReflectionClass($this);

        foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
            $propertyValue = $property->getValue($this);
            if (!empty($propertyValue) && is_subclass_of($propertyValue, ActiveRecord::className())) {
                /* @var ActiveRecord $nestedModel */
                $nestedModel = $propertyValue;
                $nestedModel->save($runValidation);
                $relation = $property->name;
                $this->link($relation, $nestedModel);
            }
        }

        return $saveResult;
    }

    public function validate($attributeNames = null, $clearErrors = true)
    {
        $class = new \ReflectionClass($this);

        foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
            $propertyValue = $property->getValue($this);
            if (!empty($propertyValue) && is_subclass_of($propertyValue, ActiveRecord::className())) {
                /* @var ActiveRecord $nestedModel */
                $nestedModel = $propertyValue;

                if (!$nestedModel->validate(null, $clearErrors)) {
                    array_push($this->errors, [
                        $property->name => $nestedModel->errors
                    ]);
                }
            }
        }

        parent::validate($attributeNames, $clearErrors);

        if ($this->hasErrors()) return false;

        return true;
    }

}

那么你的模型可以是这样的:

class Heart extends ActiveRecordWithNestedModels
{

}

class Human extends ActiveRecordWithNestedModels
{
    /* @var Heart $heart */
    public $heart = null;

    /**
     * The relation name will be 'heart', same as property `heart'
     *
     * @return \yii\db\ActiveQuery
     */
    public function getHeart()
    {
        return $this->hasOne(Heart::className(), ['id', 'heart_id']);
    }
}

而且(理论上)您可以这样做:

$human = new Human();
$human->heart = new Heart();
$human->save();

P.S. 这里可以有很多复杂的细节进一步实现,例如

  • 如果某些子对象保存失败
  • ,则使用事务回滚save
  • 覆盖delete
  • 服务 one-to-manymany-to-many 关系
  • 如果属性没有对应关系则跳过级联
  • 在级联操作中提供 $attributeNames