Отличия от 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 через контейнер.