简体   繁体   中英

Yii2 // Model as Model attribute

actually im working on a project, where i want to have all DB-tables as Models. But now im stucking at one Point. Lets say i have a "Master"-Table where many different relations are defined like the following (easy example):

Human has one heart; Human has one brain... and so on... Is it possible, to fill up the Master-Model with other Models? In PHP it would looks like that:

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

Finally i want to say:

$human-save(TRUE);

to VALIDATE all relational models AND save all relational data and the human object in DB.

Is that possible? I cant find something like that on the whole internet O_o.

Thank you very much!

You can override ActiveModel Save method, according to 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;

}

I suggest you following approach:

  1. Let's say you have same relation names as property names for nested objects (some rule needed to call $model->link() method)
  2. Declare common class for Models with nested Models (for example ActiveRecordWithNestedModels)
  3. Override in common class methods save and validate to perform cascade for these operations (using reflection)
  4. Let your models will inherit this common class

Or , as an alternative for overriding validate method, you can build some suitable implementation for rules method in common class.

This common class can looks as follows ( this is a simple draft, not tested, just to show the conception ):

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

}

Then your models can looks like this:

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']);
    }
}

And (in theory) you can do:

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

PS here can be many complex details in further implementation, as for example

  • using transactions to rollback save if some child object fails save
  • overriding delete
  • serving one-to-many and many-to-many relations
  • skip cascade if property has no corresponding relation
  • serving $attributeNames in cascade operations
  • etc

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM