Skip to content

Статический анализ с Larastan ​

С Larastan пакет проверяет определения запросов до их выполнения. Подключите его расширение после Larastan в phpstan.neon:

neon
includes:
    - vendor/larastan/larastan/extension.neon
    - vendor/myprogs/query-builder/extension.neon

Что проверяется ​

PHPStan сообщает на строке объявления:

ПроблемаИдентификатор
путь связи с сегментом, которого нет у модели (includes(), Relationship, поля и фильтры через точку, сортировки по связям и агрегатам)queryBuilder.relation
Sort::relation() через связь, которая не «к одному»queryBuilder.toOne
Relationship::to() или тип Relationship::morphTo(), определение которого описывает другую модельqueryBuilder.definition
колонка, которой нет в таблице: поля, зависимости виртуальных полей, колонки фильтров и сортировок, колонки агрегатовqueryBuilder.column
Filter::scope() без такого скоупа у моделиqueryBuilder.scope
сортировка по умолчанию, которой нет среди сортировокqueryBuilder.defaultSort

Колонки берутся из миграций и дампов схемы, которые читает Larastan, поэтому проверяются только для моделей, чью таблицу Larastan знает. Чтобы отключить проверку колонок:

neon
parameters:
    queryBuilder:
        checkColumns: false

Что пропускается ​

Проверяется только то, что записано буквально: метод, возвращающий [...] со строками и вызовами Field, Relationship, Filter и Sort. Элементы, собираемые во время выполнения (переменные, spread, array_merge()), методы, унаследованные от базового определения, и разовые запросы Query::for() пропускаются.

Связь прослеживается, когда её возвращаемый тип называет связанную модель (@return HasMany<Post, $this>); morphTo прослеживается через типы, перечисленные в morphTypes() или Relationship::morphTo(). Вложенное определение сверяется со связью, когда анализируются оба файла.

Типизированные запросы ​

Query обобщён по своей модели, поэтому скоупы модели и методы builder остаются известны по всей цепочке, например OrderCommentQuery::for($request)->where('order_id', $id)->owned()->paginate().

  • Query::for(User::class) и Query::for(User::query()) — это Query<User>.
  • Определение называет свою модель через @extends QueryDefinition<User>, либо расширение берёт её из model(), когда тот возвращает User::class. Без того и другого запрос — Query<Model>.
  • Переопределённый query() может возвращать собственный тип builder определения (Builder<User>).