Отключение ленивой загрузки

Общие вопросы по использованию фреймворка. Если не знаете как что-то сделать и это про Yii, вам сюда.
Ответить
Аватара пользователя
Svyatov
Сообщения: 459
Зарегистрирован: 2010.08.12, 14:50
Откуда: Санкт-Петербург
Контактная информация:

Отключение ленивой загрузки

Сообщение Svyatov »

Такой вот вопрос. Есть некоторая функция, которая обрабатывает и возвращает данные модели. Например:

Код: Выделить всё

function prepareData($model)
{
    $prepared_data = array();

    if ($model->some == 'something')
    {
        $preparedData['some'] = 'blabla';
    }
    else
    {
        $preparedData['some'] = 'something else';
    }

    if ($model->withRelation !== null)
    {
        $preparedData['with'] = true;
    }

    return $prepared_data;
} 
В данном примере у нас свойство модели "withRelation" - это отношение с другой таблицей. Проблема вот в чем - в функцию может поступать модель как с использованием жадной загрузки ($model = Model::model()->with('withRelation')...), так и без. И вот когда модель приходит в функцию без жадной загруки, а мы пытаемся определить есть у нас отношение "withRelation" или нет (причем даже через isset(..)), Yii совершает ленивую загрузку, которая совсем не нужна.

Возможно как-то отключить функционал ленивой загрузки целиком для всего приложения? Или как-то более точно управлять ей?

Аватара пользователя
samdark
Администратор
Сообщения: 9327
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение samdark »

При запросах можно использовать параметр together.

Аватара пользователя
Svyatov
Сообщения: 459
Зарегистрирован: 2010.08.12, 14:50
Откуда: Санкт-Петербург
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Svyatov »

А что это даст? Если дальше по коду я обращусь к $model->withRelation - это опять приведет к запросу к БД.

Аватара пользователя
samdark
Администратор
Сообщения: 9327
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение samdark »

С чего? Если запрос был с together и данные были выбраны, то дополнительного запроса не будет.

Аватара пользователя
Svyatov
Сообщения: 459
Зарегистрирован: 2010.08.12, 14:50
Откуда: Санкт-Петербург
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Svyatov »

Дык в том-то и дело, что я говорю о случае, когда запрос был без together (или с together = false) - тогда все равно идет запрос к БД.

А проблема заключена в методе __isset класса CActiveRecord:

Код: Выделить всё

public function __isset($name)
        {
                if(isset($this->_attributes[$name]))
                        return true;
                else if(isset($this->getMetaData()->columns[$name]))
                        return false;
                else if(isset($this->_related[$name]))
                        return true;
                else if(isset($this->getMetaData()->relations[$name]))
                        return $this->getRelated($name)!==null;
                else
                        return parent::__isset($name);
        } 
А конкретнее в условии:

Код: Выделить всё

else if(isset($this->getMetaData()->relations[$name]))
    return $this->getRelated($name)!==null; 
Которое тут явно лишнее. Если мне нужно проверить есть ли в этом отношении данные я могу обратиться так:

Код: Выделить всё

if ($model->withRelation === null) ... 
А если я просто хочу проверить существование (наличие выбранных с отношением данных), то зачем делать запрос к БД?

По-моему стоит фиксить на уровне фреймворка. Или объясните мне в чем я не прав :)

Аватара пользователя
samdark
Администратор
Сообщения: 9327
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение samdark »

В том, что при отложенной загрузке данные не загрузятся и $model->withRelation даст null, хотя связанные данные на самом деле есть.

Аватара пользователя
Svyatov
Сообщения: 459
Зарегистрирован: 2010.08.12, 14:50
Откуда: Санкт-Петербург
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Svyatov »

У меня стойкое ощущения, что мы не понимаем друг друга.

Аватара пользователя
samdark
Администратор
Сообщения: 9327
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение samdark »

Давайте ещё раз с начала. Вам надо получить данные из отношения или просто проверить, объявлено ли отношение без загрузки данных?

Аватара пользователя
Svyatov
Сообщения: 459
Зарегистрирован: 2010.08.12, 14:50
Откуда: Санкт-Петербург
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Svyatov »

Ни то ни другое :)

Ок, распишу как можно подробнее. Допустим у нас есть модель "юзер", у юзера есть такие свойства как аватар, страна, город и дата рождения. Страна и город представлены внешними отношенями. Таким образом модель у нас получается примерно такая:

Код: Выделить всё

class User extends CActiveRecord
{
...
public function relations()
{
    return array(
        'country'=> array(self::BELONGS_TO, 'Country', 'user_country'),
        'city' => array(self::BELONGS_TO, 'City', 'user_city'),
    );
}
...
} 
Значения свойств "аватар" и "дата рождения" требуют некоторых преобразований. Для этого мы делаем отдельную функцию, которая обрабатывает нам данные модели и возвращает готовый массив данных для представления. Функция может выглядить как-то так:

Код: Выделить всё

function prepareUserData($userModel)
{
    $data = array();
    $data['avatar'] = '/images/'.$userModel->avatar;
    $data['country'] = $data['city'] = '';
    $data['age'] = some_age_calc_func($userModel->birthdate);

    if (isset($userModel->country)) // вот тут мы проверяем отношение, нам нужно просто знать есть данные или нет, если есть - хорошо, нету - бог с ними, запрос к БД при этом не нужен совсем
    {
        $data['country'] = $userModel->country->name;
    }

    if (isset($userModel->city)) // аналогично
    {
        $data['city'] = $userModel->city->name;
    }
} 
Этой функции отдаются данные модели в разных участках приложения и только в некоторых местах необходимы данные о стране и городе, но делать 2 отдельные функции излишне, поэтому во всех нужных местах используем эту функцию, с той лишь разницей, что где-то мы до этого получаем модель вместе с отношениями (with(...)), а где-то нам нужен только аватар, например, поэтому мы делаем запрос без with - и вот в этом месте и проблема, ибо Yii все равно делает запрос к БД.

Разжую. Допустим есть страница нашего юзера, тогда мы показываем ее как-то так.

Контроллер:

Код: Выделить всё

public function actionShowUser($id)
{
    $user = User::model()->with('country', 'city')->findByPk($id);
    $this->render('show', array('user'=>prepareUserData($user));
} 
В представлении мы уже можем оперировать данными нашего массива:

Код: Выделить всё

echo $user['age'].', '.$user['country']; 
До сих пор все отлично. А дальше у нас есть страница, где нам нужно вывести только аватары и возраст в альте. Соответственно в контроллере мы пишем так:

Код: Выделить всё

public function actionShowAllUsers()
{
    $users = User::model()->findAll(); // нам НЕ нужны страна и город
    $usersData = array();
    foreach ($users as $user)
    {
        $usersData[] = prepareUserData($user); // наша функция, и вот тут-то у нас в цикле Yii зарядит отложенную загрузку
    }
    $this->render('all', array('users'=>$usersData);
} 
В представлении все как обычно:

Код: Выделить всё

foreach ($users as $user)
{
    echo '<img src="'.$user['avatar'].'" alt="'.$user['age'].'" />';
} 
Так понятно что я пытаюсь сказать? Пример немного надуман, поэтому не нужно предлагать его решать по-другому, он такой просто для наглядности.

Если я непосредственно пытаюсь использовать отношение ($user->city->name), то отложенная загрузка логична и уместна (используется __get()). Если же мне просто нужно знать были забраны данные или нет (isset($user->city), используем __isset()), то на кой черт делать обращение к БД?

Аватара пользователя
samdark
Администратор
Сообщения: 9327
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение samdark »

Интересный подход. Я бы делал не так, конечно, но попробуем разобраться. Yii ведёт себя как надо, т.е. проверяется именно наличие страны. Если её нет, то она подгружается. В этом и есть смысл отложенной загрузки.

Предлагаю передавать какой-то параметр в prepareUserData и уже на его основе разруливать ситуацию.

derelict
Сообщения: 52
Зарегистрирован: 2010.09.15, 11:46
Откуда: Украина, Ильичевск

Re: Отключение ленивой загрузки

Сообщение derelict »

А если заменить

Код: Выделить всё

isset($userModel->country) 
на

Код: Выделить всё

$userModel->hasRelated('country') 
?

Аватара пользователя
slavcodev
Сообщения: 3133
Зарегистрирован: 2009.04.02, 21:42
Откуда: Altea, Spain
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение slavcodev »

А я предлагаю воспользоваться сценариями возвращать только нужные связи

Код: Выделить всё

public function relations()
{
    $relations=array();
    if($this->scenario=='insert')
        $relations['country']=array(self::BELONGS_TO, 'Country', 'user_country');
    return $relations;
} 
Жду Yii 3!

Аватара пользователя
Svyatov
Сообщения: 459
Зарегистрирован: 2010.08.12, 14:50
Откуда: Санкт-Петербург
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Svyatov »

Sam Dark писал(а):Интересный подход. Я бы делал не так, конечно, но попробуем разобраться. Yii ведёт себя как надо, т.е. проверяется именно наличие страны. Если её нет, то она подгружается. В этом и есть смысл отложенной загрузки.

Предлагаю передавать какой-то параметр в prepareUserData и уже на его основе разруливать ситуацию.
Так уже не изящно получится :) Кроме того, не согласен, что такое поведение Yii логично, потому что мы имеем два одинаковых действия (запрос к БД) на разные по смыслу варианты обращения. Ниже вот правильно подсказали использовать для моих целей hasRelated(), но имхо это должен выполнять __isset(). Я просто не пойму какой смысл выполнять и в __get() и в __isset() практически одно и то же.
derelict писал(а):А если заменить

Код: Выделить всё

isset($userModel->country)  
на

Код: Выделить всё

$userModel->hasRelated('country')  
?
Спасибо, это оно самое то что нужно!
mc-bear писал(а):А я предлагаю воспользоваться сценариями возвращать только нужные связи

Код: Выделить всё

public function relations()
{
    $relations=array();
    if($this->scenario=='insert')
        $relations['country']=array(self::BELONGS_TO, 'Country', 'user_country');
    return $relations;
}  
Интересное решение, запомню на будущее. Сейчас применить его уже не получится, слишком много придется переделывать...

Dr0ID
Сообщения: 27
Зарегистрирован: 2010.04.04, 20:02
Откуда: Новосибирск
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Dr0ID »

а так не пойдет?

Код: Выделить всё

if($userModel->user_country){
   ...
}
 

Аватара пользователя
Svyatov
Сообщения: 459
Зарегистрирован: 2010.08.12, 14:50
Откуда: Санкт-Петербург
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Svyatov »

Dr0ID писал(а):а так не пойдет?

Код: Выделить всё

if($userModel->user_country){
   ...
}
 
Как раз так и было. Это 100% приводит к запросу, если данные не были забраны with :)

Единственное верное решение привел derelict, в полном виде оно выглядит так:

Код: Выделить всё

if ($userModel->hasRelated('country') && $userModel->country) ... 

Dr0ID
Сообщения: 27
Зарегистрирован: 2010.04.04, 20:02
Откуда: Новосибирск
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Dr0ID »

Я там написал не связь, а поле user_country.
Имелось ввиду проверять поле через которое происходит связь с другой таблицей на null/0/тд и в зависимости от результата делать выборку.

Аватара пользователя
Svyatov
Сообщения: 459
Зарегистрирован: 2010.08.12, 14:50
Откуда: Санкт-Петербург
Контактная информация:

Re: Отключение ленивой загрузки

Сообщение Svyatov »

Dr0ID писал(а):Я там написал не связь, а поле user_country.
Имелось ввиду проверять поле через которое происходит связь с другой таблицей на null/0/тд и в зависимости от результата делать выборку.
Да, извиняюсь, к вечеру глаз замылился уже. Но это тоже не очень хорошее решение. Данные могут быть, а связи может не быть. Да и даже пускай у нас есть и данные и связь, я проверю поле, после этого мне нужно будет проверить связь - опять будет запрос :)

Не, тут реально только hasRelated() помогает.

Ответить