sanchezzzhak / kak-clickhouse

Yii2 ext. ClickHouse
69 stars 43 forks source link

activerecord не загружает модели #27

Closed pyhbka closed 5 years ago

pyhbka commented 6 years ago

При любом раскладе возвращает массив как пример TestModel::find()->one(); вернет массив TestModel::find()->all(); вернет массив массивов моделька ни разу не прогрузилась.

TestModel - https://github.com/sanchezzzhak/kak-clickhouse#activerecord-model как тут, ничего лишнего только rules добавил, связей нет.

пока смог решить добавлением двух методов в класс kak\clickhouse\ActiveQuery из yii\db\ActiveQuery, методы one и populate

Что я не так делаю ? :(

alexgivi commented 5 years ago

как я решил проблему: необходимо определить наследники классов kak\clickhouse\ActiveRecord и kak\clickhouse\ActiveQuery. в класс ActiveQuery скопировать следующие методы из yii\db\ActiveQuery: one, populate, removeDuplicatedModels, addInverseRelations. ничего менять не надо.

класс ActiveQuery:


use kak\clickhouse\ActiveQuery;
use kak\clickhouse\ActiveRecord;
use yii\base\InvalidConfigException;
use yii\db\ActiveRecordInterface;

/**
 * Копипаста из \yii\db\ActiveQuery для нормальной работы с моделями вместо массивов для clickHouse.
 */
class BaseClickHouseActiveQuery extends ActiveQuery
{
    /**
     * @param null $db
     * @return ActiveRecordInterface|null
     * @throws InvalidConfigException
     */
    public function one($db = null)
    {
        $row = parent::one($db);
        if ($row !== false) {
            $models = $this->populate([$row]);
            return reset($models) ?: null;
        }

        return null;
    }

    /**
     * {@inheritdoc}
     * @throws InvalidConfigException
     */
    public function populate($rows)
    {
        if (empty($rows)) {
            return [];
        }

        $models = $this->createModels($rows);
        if (!empty($this->join) && $this->indexBy === null) {
            $models = $this->removeDuplicatedModels($models);
        }
        if (!empty($this->with)) {
            $this->findWith($this->with, $models);
        }

        if ($this->inverseOf !== null) {
            $this->addInverseRelations($models);
        }

        if (!$this->asArray) {
            foreach ($models as $model) {
                $model->afterFind();
            }
        }

        return parent::populate($models);
    }

    /**
     * Removes duplicated models by checking their primary key values.
     * This method is mainly called when a join query is performed, which may cause duplicated rows being returned.
     * @param array $models the models to be checked
     * @throws InvalidConfigException if model primary key is empty
     * @return array the distinctive models
     */
    private function removeDuplicatedModels($models)
    {
        $hash = [];
        /* @var $class ActiveRecord */
        $class = $this->modelClass;
        $pks = $class::primaryKey();

        if (count($pks) > 1) {
            // composite primary key
            foreach ($models as $i => $model) {
                $key = [];
                foreach ($pks as $pk) {
                    if (!isset($model[$pk])) {
                        // do not continue if the primary key is not part of the result set
                        break 2;
                    }
                    $key[] = $model[$pk];
                }
                $key = serialize($key);
                if (isset($hash[$key])) {
                    unset($models[$i]);
                } else {
                    $hash[$key] = true;
                }
            }
        } elseif (empty($pks)) {
            throw new InvalidConfigException("Primary key of '{$class}' can not be empty.");
        } else {
            // single column primary key
            $pk = reset($pks);
            foreach ($models as $i => $model) {
                if (!isset($model[$pk])) {
                    // do not continue if the primary key is not part of the result set
                    break;
                }
                $key = $model[$pk];
                if (isset($hash[$key])) {
                    unset($models[$i]);
                } elseif ($key !== null) {
                    $hash[$key] = true;
                }
            }
        }

        return array_values($models);
    }

    /**
     * If applicable, populate the query's primary model into the related records' inverse relationship.
     * @param array $result the array of related records as generated by [[populate()]]
     * @since 2.0.9
     */
    private function addInverseRelations(&$result)
    {
        if ($this->inverseOf === null) {
            return;
        }

        foreach ($result as $i => $relatedModel) {
            if ($relatedModel instanceof ActiveRecordInterface) {
                if (!isset($inverseRelation)) {
                    $inverseRelation = $relatedModel->getRelation($this->inverseOf);
                }
                $relatedModel->populateRelation($this->inverseOf, $inverseRelation->multiple ? [$this->primaryModel] : $this->primaryModel);
            } else {
                if (!isset($inverseRelation)) {
                    /* @var $modelClass ActiveRecordInterface */
                    $modelClass = $this->modelClass;
                    $inverseRelation = $modelClass::instance()->getRelation($this->inverseOf);
                }
                $result[$i][$this->inverseOf] = $inverseRelation->multiple ? [$this->primaryModel] : $this->primaryModel;
            }
        }
    }
}

класс ActiveRecord:

use kak\clickhouse\ActiveRecord;
use yii\helpers\Inflector;
use yii\helpers\StringHelper;

class BaseClickHouseActiveRecord extends ActiveRecord
{
    public static function tableName()
    {
        return Inflector::underscore(StringHelper::basename(get_called_class()));
    }

    public static function find()
    {
        return new BaseClickHouseActiveQuery(get_called_class());
    }
}

Свои модели наследовать от этого класса - все будет работать.

@sanchezzzhak надо добавить реализацию ActiveQuery

alexgivi commented 5 years ago

https://github.com/sanchezzzhak/kak-clickhouse/pull/29