Pull to refresh

Laravel Builder и подзапросы

Исходный код Laravel, при достаточно лаконичной и понятной документации, содержит информацию, которая дает дополнительное понимания, как возможно использовать этот framework. Речь пойдет о подзапросах.

Использование:

<?php
Post::select(['id_from_closure' => function ($query) {
    $query->select('id');
}])->get();
Post::from(Post::whereNotNull('id'))->get();
Post::whereIn('id', Post::select('id'))->get();

*Примеры только для демонстрации подхода и не несут практического смысла.

Таким образом возможно получить необходимые данные не выполняя дополнительных запросов к базе данных, а используя их как подзапрос, частью основного запроса.

К примеру метод класса \Illuminate\Database\Query\Builder whereIn содержит следующий комментарий:

If the value is a query builder instance we will assume the developer wants to look for any values that exists within this given query. So we will add the query accordingly so that this query is properly executed when it is run.

Работает это благодаря реализации преобразования входящих параметров в подзапрос после их проверки с помощью метода isQueryable класса \Illuminate\Database\Query\Builder. Из реализации этого метода можно узнать все ожидаемые типы, которые обеспечат положительный результат проверки.

<?php
protected function isQueryable($value)
{
    return $value instanceof self ||
           $value instanceof EloquentBuilder ||
           $value instanceof Relation ||
           $value instanceof Closure;
}

Получить некоторые из необходимых экземпляров возможно с помощью метода getQuery. Метод getQuery (интересующий нас) описан в двух классах. Первый - это абстрактный класс Illuminate\Database\Eloquent\Relations\Relation таким образом, все методы отношений в классе Eloquent содержат экземпляр класса в котором реализован метод getQuery. Метод возвращает экземпляр класса \Illuminate\Database\Eloquent\Builder.

Второй - это сам класс \Illuminate\Database\Eloquent\Builder, который возвращает экземпляр класса \Illuminate\Database\Query\Builder. Реализация в обоих случаях одинакова:

<?
public function getQuery()
{
    return $this->query;
}

Благодаря реализации в laravel статического вызова методов класса Builder, классами наследуемыми от класса Eloquent, данный вызов: Post::select('id') возвращает экземпляр класса \Illuminate\Database\Eloquent\Builder, что дает возможность использовать его как параметр для функций.

Методы, принимающие "isQueryable" параметры:

  • select

  • addSelect

  • from

  • whereIn

Получение "isQueryable" параметра:

  • Post::whereNotNull('id') \Illuminate\Database\Eloquent\Builder

  • Post::select('id')->getQuery() \Illuminate\Database\Query\Builder

  • User::first()->posts() Illuminate\Database\Eloquent\Relations\BelongsToMany

  • User::first()->posts()->getQuery() \Illuminate\Database\Eloquent\Builder

Что следует знать:

В зависимости от версии laravel можно столкнуться со сложностями использования такого подхода. Так в версиях 6.* нет возможности передавать экземпляр созданный от класса наследуемого класс Relation, связано это с тем, что проверка $value instanceof Relation в методе isQueryable периодически пропадает из репозитория фрейморка. Именно по этой причине выше было указано:

все методы отношений в классе Eloquent содержат экземпляр класса в котором реализован метод getQuery

<?php
// Может не сработать:
Post::whereIn('id', $user->posts()->select('id'))->get();

// Сработает:
Post::whereIn('id', $user->posts()->select('id')->getQuery())->get();

В некоторых версиях 5.* метод whereIn ожидает экземпляр класса только \Illuminate\Database\Query\Builder, в этом случае придется воспользоваться повторным вызовом метода getQuery:

<?php
Post::whereIn('id', $user->posts()->select('id')->getQuery()->getQuery())->get();

На момент написания статьи актуальная версия laravel v8.35.1, нижняя версия в которой были пройдены все тесты v7.16.0.

Репозиторий с тестами.

Благодарю за внимание!

Tags:
Hubs:
You can’t comment this publication because its author is not yet a full member of the community. You will be able to contact the author only after he or she has been invited by someone in the community. Until then, author’s username will be hidden by an alias.