Skip to content

Отличия от spatie ​

Spatie-совместимый API проходит тестовый набор spatie/laravel-query-builder 7.3.6, который гоняется против этого пакета в tests/Compat/Spatie с сохранённым уведомлением MIT. Отличия ниже намеренные.

Ключи связей добавляются в SELECT ​

Когда fields[...] сочетается с include, первичные и внешние ключи каждой подгруженной связи (включая morphTo, hasOneThrough/hasManyThrough и ключи pivot) выбираются автоматически. Подгруженные связи никогда не приходят пустыми из-за того, что ключ не попал в набор полей.

Добавленные ключи — обычные атрибуты загруженных моделей. Скройте их в API-ресурсах, если их нельзя отдавать клиенту.

Поля и include применяются отложенно ​

allowedFields() и allowedIncludes() проверяют запрос клиента сразу, но применяют SELECT и жадную загрузку (eager load) непосредственно перед первым вызовом, читающим запрос: get, paginate, first, firstWhere, toSql, getEloquentBuilder, макрос, зарегистрированный через Builder::macro(), например jsonPaginate(), и так далее. Макросы, которые добавляет глобальный скоуп, например withTrashed(), цепочку не завершают.

Поэтому:

  • порядок allowedFields() и allowedIncludes() не важен, а UnknownIncludedFieldsQuery никогда не выбрасывается (класс существует только для совместимости типов);
  • фильтры и сортировки по-прежнему применяются сразу, в порядке вызовов;
  • колонки, добавленные после allowedFields() (withCount(), addSelect() в собственной сортировке), сохраняются рядом с запрошенными полями, а select() после него побеждает запрошенные поля корня — в тех же местах, что и в spatie;
  • запрошенный агрегат (postsCount) сохраняет своё место в select, как в spatie, но select() или allowedFields() после allowedIncludes() больше его не теряет;
  • include, называющий связь, которой у модели нет, падает при планировании запроса, даже если ни одна запись не найдена; spatie падает только при загрузке связи.

Наборы полей по имени таблицы доходят до вложенных include ​

Если задан convert_relation_table_name_strategy, набор полей с ключом по связанной таблице (fields[comments]=body) применяется и к вложенному include, например posts.comments. Spatie определяет таблицу только у связей первого уровня.

Заранее заданная жадная загрузка тоже получает ключи ​

QueryBuilder::for(User::with('posts')) с fields[users]=name тоже выбирает ключ, нужный posts, как и ->with('posts.comments') под подгруженной posts с набором полей. Такой with() после allowedIncludes() сохраняет набор полей подгруженной связи, который spatie теряет. Переданное в него условие (->with(['posts' => fn ($q) => ...])) выполняется после применения набора полей, а не заменяет его.

Некорректные параметры ​

Вложенный массив там, где ожидается строка (include[a][]=b), игнорируется, пока эндпоинт не разрешает этот параметр. Когда разрешает, выбрасывается MalformedQueryParameter (400) вместо ошибки PHP.

Граничные случаи фильтров ​

  • Объект фильтра можно применять к нескольким запросам.
  • Оператор без значения (filter[views]=<) или фильтр </> с null пропускается.
  • Фильтр scope через связь разрешает параметры-модели на связанной модели.
  • partial, beginsWith и endsWith ищут слова true/false, а не превращают их в пустой шаблон.

morphTo учитывает fields ​

Набор полей для целевого типа morphTo применяется к каждому типу отдельно, с ключами, нужными этому типу.

Собственные классы фильтров и сортировок ​

В переопределении AllowedFilter::filter() или AllowedSort::sort() используйте $query->getSubject(), чтобы получить исходный builder. getEloquentBuilder() применяет отложенные поля и include, чего переопределение, вызванное из allowedFilters() или allowedSorts(), делать не должно.

AllowedInclude::include() не вызывается ​

Include планируются вместе с полями, поэтому переопределение include() в подклассе AllowedInclude никогда не выполняется. Используйте вместо него AllowedInclude::custom() с IncludeInterface.

Запросы, которые не трогаются ​

Ключи не добавляются в запросы с groupBy или distinct, а также в запросы, которые выбирают из raw-выражения. Такие запросы получают ровно те колонки, которые запросил клиент.

Собственные классы связей ​

Типы связей, которые поставляются с Laravel, пакету известны. Другие подклассы Relation разрешаются по их геттерам ключей. Если это не удаётся, зарегистрируйте собственный Planning\Keys\KeyResolver в Planning\Keys\KeyResolverRegistry через контейнер.