【问题标题】:Yii2 // Model as Model attributeYii2 // 模型作为模型属性
【发布时间】:2016-07-19 07:32:45
【问题描述】:

实际上我正在从事一个项目,我希望将所有数据库表作为模型。但现在我停留在一点。 假设我有一个“主”表,其中定义了许多不同的关系,如下所示(简单示例):

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

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

最后我想说:

$human-save(TRUE);

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

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

非常感谢!

【问题讨论】:

  • 如果数据库表模型关系,他们通常也会在所有写入尝试时强制执行它们,所以我完全不清楚你想在哪里验证什么。在数据库级别上,如果支持我看到的最接近的东西是 TRANSACTION 块(失败或成功)。您是否正在从 Yi 框架中搜索方法?谢谢。
  • 嘿,我会尽量让你明白的。如果我用“true”参数调用 $human->save(true),我会强制执行模型验证。所以我的第一个问题是:如果我在一个模型中有不同的模型(如上面的示例),“子模型”是否也得到验证?第二个问题:我如何访问在模型中拥有模型的目标?我必须在我的主模型和子模型中做什么才能实现这一目标?
  • @jiGL yii2中的Model类没有save()方法。您使用的是 ActiveRecord 还是其他一些 ORM 实现?
  • @Maxim 是的,对不起!我的模型继承自 ActiveRecord!

标签: php validation yii2 models composite


【解决方案1】:

你可以覆盖ActiveModelSave方法,根据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;

}

【讨论】:

    【解决方案2】:

    我建议你以下方法:

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

    或者,作为覆盖validate 方法的替代方案,您可以在公共类中为rules 方法构建一些合适的实现。

    这个通用类可以如下所示(这是一个简单的草稿,没有经过测试,只是为了展示概念):

    <?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 提供服务

    【讨论】:

    • 是的!这正是我正在寻找的...谢谢!认为这会更容易一些,或者在 yii2 中存在类似的东西。不管怎样,谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-11-30
    • 1970-01-01
    相关资源
    最近更新 更多