relationBehavior.php 7.63 KB
<?php

namespace common\modules\relation;

use yii\base\Behavior;
use yii\base\Exception;
use yii\db\ActiveRecord;
use Yii;
use yii\helpers\ArrayHelper;

class relationBehavior extends Behavior {

    /** @var array $fields Form-fields for current relations */
    public $fields = [];

    /** @var array $values Values for current relations */
    public $values = [];

    /** @var array $relations List of relations of current model */
    public $relations = [];

    /**
     * @param ActiveRecord $owner
     * @throws Exception
     */
    public function attach($owner)
    {
        parent::attach($owner);
    }

    /*
     * Inicialize behavior (read and prepare params)
     */
    public function init() {
        foreach ($this->relations as $relation_key => &$relation) {
            if (is_string($relation)) {
                // Get data from module's data
                $relation_entity = $relation;
                $relation = $this->_getRelationParams($relation_key);
                $relation['inner'] = $relation[$relation_entity];
                $relation['outer'] = $relation[$relation_entity == 'entity1' ? 'entity2' : 'entity1'];
                $relation['linked_table'] = $relation['via']['model']::tableName();
                if (!empty($relation['via']['alias'])) {
                    $relation['linked_alias'] = $relation['via']['alias'];
                }
            }
            $this->fields[$relation['field']] = $relation_key;
        }
    }

    /*
     * Events for auto-drive relations data
     */
    public function events()
    {
        return [
            ActiveRecord::EVENT_AFTER_INSERT    => 'relationsAfterSave',
            ActiveRecord::EVENT_AFTER_UPDATE    => 'relationsAfterSave',
            ActiveRecord::EVENT_BEFORE_DELETE   => 'relationBeforeDelete',
        ];
    }

    public function relationsAfterSave($insert) {
        if (is_array($modelPrimaryKey = $this->owner->getPrimaryKey())) {
            throw new ErrorException('This behavior does not support composite primary keys');
        }

        foreach ($this->relations as $relation_key => $relation) {
            if (empty($relation['field']) || !is_array($this->{$relation['field']}))
                continue;

            $values = $this->{$relation['field']};

            /** @var ActiveRecord $model */
            $model = new $relation['inner']['model'];

            $connection = $model::getDb();
            $transaction = $connection->beginTransaction();

            $delete_where = [$relation['inner']['linked_key'] => $this->owner->{$relation['inner']['key']}];
            if (!empty($relation['linked_alias'])) {
                $delete_where[$relation['linked_alias']] = $relation_key;
            }

            try {
                // Delete all links from viaTable
                $connection->createCommand()
                    ->delete
                    (
                        $relation['linked_table'],
                        $delete_where
                    )
                    ->execute();

                if (!empty($values)) {
                    foreach($values as $value) {
                        $insertData = [
                            $relation['inner']['linked_key'] => $this->owner->{$relation['inner']['key']},
                            $relation['outer']['linked_key'] => $value
                        ];
                        if (!empty($relation['linked_alias'])) {
                            $insertData[$relation['linked_alias']] = $relation_key;
                        }


                        $connection->createCommand()
                            ->insert
                            (
                                $relation['linked_table'],
                                $insertData
                            )
                            ->execute();
                    }
                }
                $transaction->commit();

//                $model->link($relation_key, )
            } catch (Exception $ex) {
//                var_dump($relation_key, $relation);exit;
                $transaction->rollback();
                throw $ex;
            }
        }
    }

    public function relationBeforeDelete() {
        if (is_array($modelPrimaryKey = $this->owner->getPrimaryKey())) {
            throw new ErrorException('This behavior does not support composite primary keys');
        }
        foreach ($this->relations as $relation_key => $relation) {
            if (empty($relation['field']))
                continue;
            $values = $this->{$relation['field']};

            /** @var ActiveRecord $model */
            $model = new $relation['inner']['model'];

            $connection = $model::getDb();
            $transaction = $connection->beginTransaction();

            // @todo Refix to ActiveRecord format
            try {
                // Delete all links from viaTable
                $connection->createCommand()
                    ->delete
                    (
                        $relation['linked_table'],
                        [$relation['inner']['linked_key'] => $this->owner->{$relation['inner']['key']}]
                    )
                    ->execute();
                $transaction->commit();
            } catch (Exception $ex) {
                $transaction->rollback();
                throw $ex;
            }
        }
    }

    /**
     * Get related data for $relation
     * @params string $relation Relation key
     */
    public function getRelations($relation) {
        $relation = $this->_getRelation($relation);
        return
            $this->owner
                ->hasMany($relation['outer']['model'], [$relation['outer']['key'] => $relation['outer']['linked_key']])
                ->viaTable($relation['linked_table'], [$relation['inner']['linked_key'] => $relation['inner']['key']]);
    }

    /*
     * Get relation params for $relation
     * @param string $relation Relation key
     */
    protected function _getRelation($relation) {
        $relation = strtolower($relation);
        return isset($this->relations[$relation]) ? $this->relations[$relation] : null;
    }

    /**
     * Return relation data from main app config
     * @params string $section Relations key
     */
    protected function _getRelationParams($section) {
        $relation = relationHelper::getRelation($section);
        if (!$relation)
            throw new Exception('Relation "' . $section . '" not set on this application.');
        return $relation;
    }

    protected function _getRelationNameByField($field) {
        return isset($this->fields[$field]) ? $this->fields[$field] : null;
    }

    protected function _getRelationByField($field) {
        return ( isset($this->fields[$field]) && isset($this->relations[$this->fields[$field]]) ) ? $this->relations[$this->fields[$field]] : null;
    }

    /**
     * @inheritdoc
     */
    public function canGetProperty($name, $checkVars = true)
    {
        return true;
    }

    /**
     * @inheritdoc
     */
    public function canSetProperty($name, $checkVars = true)
    {
        return array_key_exists($name, $this->fields) ?
            true : parent::canSetProperty($name, $checkVars = true);
    }

    /**
     * @inheritdoc
     */
    public function __set($name, $value) {
        if (isset($this->fields[$name])) {
            $this->values[$name] = $value;
        }
    }

    /**
     * @inheritdoc
     */
    public function __get($name) {
        if (isset($this->values[$name])) {
            return $this->values[$name];
        } else {
            $relation_key = $this->_getRelationNameByField($name);
            if (!$relation_key)
                return;

            return $this->getRelations($relation_key);
        }
    }
}