Рекурсивный вызов $activeQuery->joinWith

Предварительное обсуждение найденных ошибок перед отправкой их авторам фреймворка, а также внесение новых предложений.
Ответить
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

Приветствую!
Что будет, если делать рекурсивный вызов $activeQuery->joinWith ? То есть вызывать joinWith внутри Closure, которое передано как $activeQuery->joinWith([$name=> Closure]).

Я тестировал, и внутренние вызовы $query->joinWith не работают. По идее все они должны участвовать в генерации итогового запроса.

Пример:

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


// это линия $relations доступных в основной модели Profile
// finances.currency.eps

$activeQuery = Profile::find();

$activeQuery->joinWith([ 'finances' => function($query) { 
    \Yii::trace($query->modelClass);
    
    $query->joinWith([ 'currency' => function($query) {
       \Yii::trace($query->modelClass);

           $query->joinWith([ 'eps' => function($query) {
               \Yii::trace($query->modelClass);
            } ], false);

    } ], false);

} ], false);

 
В итоге в лог выведет только \app\models\ProfileFinances.
Последний раз редактировалось Stepan Selyuk 2014.08.01, 15:07, всего редактировалось 1 раз.
Сначала невидимое, затем видимое. И так у всех программистов :)
lynicidn
Сообщения: 2222
Зарегистрирован: 2014.05.24, 15:12

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение lynicidn »

ппц, вы в currency джойните с currency - срочно читать маны по mysql
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

Я вручную код этот писал в редакторе сообщений) видимо ошибся. Это пример, исправил его. А вопрос остается открытым.
Сначала невидимое, затем видимое. И так у всех программистов :)
lynicidn
Сообщения: 2222
Зарегистрирован: 2014.05.24, 15:12

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение lynicidn »

напишите подробнее и желательно лог трейс прикрепить и что выводит дамп
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

Так ничего не выводит. Метод почему-то не отрабатывает, кроме первого, вызванного с основной модели. То есть метод вызванный из Closure не работает (не вызывает следующую Closure, переданную ему).

Давайте представим ситуацию, что нужно получить данные из таблицы %profiles, с условием profile.finances.currency.eps.code='natural', но каждую таблицу из этой цепочки связей нужно сделать с алиасом (который создается по определенным условиям внутри Closure) в конечном запросе. Для простоты, прямые связи везде hasOne, кроме profile->finances, то есть `profile` hasMany `finances`, `finances` hasOne `currency`, `currency` hasOne `eps`.
Сначала невидимое, затем видимое. И так у всех программистов :)
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

Написал пример кода:

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

$profileQuery = \app\models\Profile::find();

    // Нужно добавить условия поиска profile.finances[{eps}natural:{currency}USD].currencyItem.epsItem.code
    // Нужно добавить алиасы к каждой таблице

    // Добавляем алиас для главной таблицы
    $profileQuery->from( ( new $profileQuery->modelClass )->tableName() . ' t' );

    $genAlias = function ( \yii\db\ActiveQuery $query ) {

        return 'tbl' . mt_rand( 0, 10000 );
    };

    // Сначала добавляем условия для главной модели
    $profileQuery->joinWith(
        [
            // название relation в основной модели
            'finances' => function ( \yii\db\ActiveQuery $query ) use ( $genAlias ) {

                $joinAlias = $genAlias( $query );

                // Алиас генерируется каким-то образом, сторонней функцией
                $query->from( ( new $query->modelClass )->tableName() . ' ' . $joinAlias );

                // Так как это связь hasMany, добавляем условия соединения
                $query->andOnCondition(
                // Используем имя алиаса
                    $joinAlias . '.eps = "natural"'
                );

                $query->andOnCondition(
                // Используем имя алиаса
                    $joinAlias . '.currency = "USD"'
                );

                echo 'modelClass: ' . $query->modelClass . '<br/>';
                echo 'primaryModel class: ' . get_class( $query->primaryModel ) . '<br/>';

                // Продолжаем соединение таблиц
                // Соединяем таблицу модели currencyItem
                $query->joinWith(
                    [
                        'currencyItem' => function ( \yii\db\ActiveQuery $query ) use ( $genAlias ) {

                            $joinAlias = $genAlias( $query );

                            // Алиас генерируется каким-то образом, сторонней функцией
                            $query->from( ( new $query->modelClass )->tableName() . ' ' . $joinAlias );

                            echo 'modelClass: ' . $query->modelClass . '<br/>';
                            echo 'primaryModel class: ' . get_class( $query->primaryModel ) . '<br/>';

                            // Продолжаем соединение таблиц
                            // Соединяем таблицу модели epsItem
                            $query->joinWith(
                                [
                                    'epsItem' => function ( \yii\db\ActiveQuery $query ) use ( $genAlias ) {

                                        $joinAlias = $genAlias( $query );

                                        // Алиас генерируется каким-то образом, сторонней функцией
                                        $query->from( ( new $query->modelClass )->tableName() . ' ' . $joinAlias );

                                        echo 'modelClass: ' . $query->modelClass . '<br/>';
                                        echo 'primaryModel class: ' . get_class( $query->primaryModel ) . '<br/>';
                                    }
                                ],
                                false
                            );

                        }
                    ],
                    false
                );

            }
        ],
        // stop eager loading
        false
    );

    echo $profileQuery->createCommand()->getSql();
Выводит:
modelClass: app\models\ProfileFinances
primaryModel class: app\models\Profile
SELECT `t`.* FROM `ss_profiles` `t` LEFT JOIN `ss_profiles_finances` `tbl9618` ON (`t`.`id` = `tbl9618`.`profile_id`) AND ((tbl9618.eps = "natural") AND (tbl9618.currency = "USD"))
То есть не отрабатывают внутренние joinWith получается.
Сначала невидимое, затем видимое. И так у всех программистов :)
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

Судя по коду ActiveQuery после того как Closure была запущена через call_user_func, вызывается метод joinWithRelation(...,relation,...), но в нем нет обработки поля $relation->joinWith, поэтому эти данные добавленные в самой первой Closure уже не будут обработаны и последующие соответственно.
Сначала невидимое, затем видимое. И так у всех программистов :)
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

Если в \yii\db\ActiveQuery поправить код на:

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

/**
     * Modifies the current query by adding join fragments based on the given relations.
     * @param ActiveRecord $model the primary model
     * @param array $with the relations to be joined
     * @param string|array $joinType the join type
     */
    private function joinWithRelations($model, $with, $joinType)
    {
        //...
                if ($callback !== null) {
                    call_user_func($callback, $relation);
                    // Added
                    if(is_array($relation->joinWith))$relation->buildJoinWith();
                }
        //...
    }
 
То все будет работать,
а именно результат кода выше будет теперь:
modelClass: app\models\ProfileFinances
primaryModel class: app\models\Profile
modelClass: app\models\Currency
primaryModel class: app\models\ProfileFinances
modelClass: app\models\Eps
primaryModel class: app\models\Currency
SELECT `t`.* FROM `ss_profiles` `t` LEFT JOIN `ss_profiles_finances` `tbl7128` ON (`t`.`id` = `tbl7128`.`profile_id`) AND ((tbl7128.eps = "natural") AND (tbl7128.currency = "USD")) LEFT JOIN `ss_currencies` `tbl1950` ON `tbl7128`.`currency` = `tbl1950`.`code` LEFT JOIN `ss_eps` `tbl3003` ON `tbl1950`.`eps` = `tbl3003`.`code`
Запилить Pull Request?
Сначала невидимое, затем видимое. И так у всех программистов :)
Аватара пользователя
samdark
Администратор
Сообщения: 9489
Зарегистрирован: 2009.04.02, 13:46
Откуда: Воронеж
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение samdark »

Конечно.
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

Сначала невидимое, затем видимое. И так у всех программистов :)
mickgeek
Сообщения: 957
Зарегистрирован: 2014.05.31, 20:50
Откуда: Санкт-Петербург
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение mickgeek »

yii2-framework - репозиторий только для чтения, yii2 - правильный.
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

Добавил в PR еще код, чтобы можно было алиас в ActiveQuery выставить, и он автоматом применится в prepareBuild().
Сначала невидимое, затем видимое. И так у всех программистов :)
Аватара пользователя
Stepan Selyuk
Сообщения: 198
Зарегистрирован: 2010.02.03, 05:51
Откуда: Cyprus, Limassol
Контактная информация:

Re: Рекурсивный вызов $activeQuery->joinWith

Сообщение Stepan Selyuk »

mickgeek писал(а):yii2-framework - репозиторий только для чтения, yii2 - правильный.
Ок, буду знать) добавил в основной https://github.com/yiisoft/yii2/pull/4570
Сначала невидимое, затем видимое. И так у всех программистов :)
Ответить