N
N
Nikita2016-09-06 16:54:05
Yii
Nikita, 2016-09-06 16:54:05

Is it possible to emulate eager loading when there is already an array of models?

From the documentation: $authors = Author::find()->with('posts')->all();will pull out everything you need with 2 queries

SELECT * FROM author;
SELECT * FROM post WHERE author_id IN (1, 3, 7, 10, ... X);

What if I already have $authors = Author::find()->all(); //Без with()and want to somehow apply with()to an array $authors?
PS I’ll immediately explain that the example is greatly exaggerated, in fact there is an array of models formed by complex logic at the php level and I can simply manually create a request and fill the array with data, I still want to get by with the framework tools.

Answer the question

In order to leave comments, you need to log in

3 answer(s)
N
Nikita, 2016-09-07
@bitver

I did not find similar methods in the framework, but I managed to pull out what I need:

A class that does what is described in the question
<?php

namespace app\components;


use yii\base\Model;
use yii\db\ActiveQuery;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveRecord;

class Eagerifier
{
    /**
     * @param $with
     * @param Model[] $models
     * @param bool $asArray
     * @throws \yii\base\InvalidConfigException
     */
    public static function populate($with, array &$models, $asArray = false)
    {
        $modelClass = '';
        foreach ($models as $key => $model) {
            $modelClass = $model->className();
            break;
        }

//        From ActiveQuery::findWith()
        $primaryModel = new $modelClass;
        $relations = self::normalizeRelations($primaryModel, $with);
        /* @var $relation ActiveQuery */
        foreach ($relations as $name => $relation) {
            if ($relation->asArray === null) {
                // inherit asArray from primary query
                $relation->asArray($asArray);
            }
            $relation->populateRelation($name, $models);
        }
    }

    /**
     * Adapted method from ActiveQuery
     *
     * @param ActiveRecord $model
     * @param array $with
     * @return ActiveQueryInterface[]
     */
    private static function normalizeRelations($model, $with)
    {
        $relations = [];
        foreach ($with as $name => $callback) {
            if (is_int($name)) {
                $name = $callback;
                $callback = null;
            }
            if (($pos = strpos($name, '.')) !== false) {
                // with sub-relations
                $childName = substr($name, $pos + 1);
                $name = substr($name, 0, $pos);
            } else {
                $childName = null;
            }

            if (!isset($relations[$name])) {
                $relation = $model->getRelation($name);
                $relation->primaryModel = null;
                $relations[$name] = $relation;
            } else {
                $relation = $relations[$name];
            }

            if (isset($childName)) {
                $relation->with[$childName] = $callback;
            } elseif ($callback !== null) {
                call_user_func($callback, $relation);
            }
        }

        return $relations;
    }
}

Usage: Eagerifier::populate(['posts'], $authors);
Leave, maybe someone will come in handy.

A
Andrew, 2016-09-06
@mhthnz

Why not use IN ?

<?

$authors = Author::find()->all();
$authorsIds = ArrayHelper::getColumn($authors, 'id');
$allPosts = Posts::find()->where(['author_id' => $authorsIds])->orderBy('author_id')->all();

##### И далее чтобы вытащить все посты определенного автора

foreach($authors as $author) {
    $author_id = $author->id;
    $posts = ArrayHelper::getColumn($allPosts, function($element) use ($author_id){
        if ($element['author_id'] == $author_id) {
            return $element;
        }
        return false;
    });

    // Записываем в объект author данные связи с posts
    $author->populateRelation('posts', $posts);
}

#### Теперь данные о постах можно спокойно доставать из модели Authors

$author = array_pop($authors);
var_dump($author->posts);


#### Все это можно инкапсулировать за статической функцией, например в классе Authors

public static function fillPosts($authors) 
{
  $authorsIds = ArrayHelper::getColumn($authors, 'id');
  $allPosts = Posts::find()->where(['author_id' => $authorsIds])->orderBy('author_id')->all();
  foreach($authors as $author) {
      $author_id = $author->id;
      $posts = ArrayHelper::getColumn($allPosts, function($element) use ($author_id){
          if ($element['author_id'] == $author_id) {
              return $element;
          }
          return false;
      });

      // Записываем в объект author данные связи с posts
      $author->populateRelation('posts', $posts);
  }
}

### Ну и вызывать

$authors = Author::find()->all();
Author::fillPosts($authors);

Something like this should work, the code has not been tested.

M
Maxim Timofeev, 2016-09-06
@webinar

Make a getter in the model that will return what you need. Implement your own logic in it. You can even put it in afterfind. In general, it is difficult to understand from such an example what exactly is needed.
What is this for? For ease of use in code or for optimization? If the latter, then there will be more sense from using the cache.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question