Answer the question
In order to leave comments, you need to log in
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);
$authors = Author::find()->all(); //Без with()
and want to somehow apply with()
to an array $authors
? Answer the question
In order to leave comments, you need to log in
I did not find similar methods in the framework, but I managed to pull out what I need:
<?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;
}
}
Eagerifier::populate(['posts'], $authors);
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);
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 questionAsk a Question
731 491 924 answers to any question