Это расширение предоставляет следующие функции:
findBy* , findOneBy* и countBy* методы на EntityRepository.findBy , findBy* , findOneBy , findOneBy* , count и countBy* вызовы метода.EntityRepository<MyEntity> в PHPDOC для дальнейшего типа методов, вызванных репозиторием.DoctrineORMEntityManager::getRepository() .DoctrineORMEntityManager::find , getReference и getPartialReference когда имя класса Foo::class Class Class предоставляется как первый аргументmatching на DoctrineCommonCollectionsCollection . Это можно отключить, установив parameters.doctrine.allCollectionsSelectable . Doctrine.allcollections Selectable для false .allowNullablePropertyForRequiredField: true Setting.DoctrineORMQuery::getResult , getOneOrNullResult , getSingleResult , toIterable и execute в режиме HYDRATE_OBJECT (см. Ниже). Чтобы использовать это расширение, требовать его в композиторе:
composer require --dev phpstan/phpstan-doctrineЕсли вы также установите Phpstan/Extension-Installer, то вы все настроены!
Если вы не хотите использовать phpstan/extension-installer , включите Extension.neon в конфигурации вашего проекта Phpstan:
includes :
- vendor/phpstan/phpstan-doctrine/extension.neon Если вы заинтересованы в проверке DQL/QueryBuilder, включите также rules.neon (вам также необходимо предоставить objectManagerLoader , см. Ниже):
includes :
- vendor/phpstan/phpstan-doctrine/rules.neon Если ваши репозитории имеют общий базовый класс, вы можете настроить его в своем phpstan.neon , и Phpstan увидят дополнительные методы, которые вы определяете в нем:
parameters :
doctrine :
ormRepositoryClass : MyAppDoctrineBetterEntityRepository
odmRepositoryClass : MyAppDoctrineBetterDocumentRepositoryВы можете выбрать более продвинутый анализ, предоставив объектному менеджеру из собственного приложения. Это позволит валидации DQL:
parameters :
doctrine :
objectManagerLoader : tests/object-manager.phpПример для Symfony 4:
// tests/object-manager.php
use App Kernel ;
require __DIR__ . ' /../config/bootstrap.php ' ;
$ kernel = new Kernel ( $ _SERVER [ ' APP_ENV ' ], ( bool ) $ _SERVER [ ' APP_DEBUG ' ]);
$ kernel -> boot ();
return $ kernel -> getContainer ()-> get ( ' doctrine ' )-> getManager ();Пример для Symfony 5:
// tests/object-manager.php
use App Kernel ;
use Symfony Component Dotenv Dotenv ;
require __DIR__ . ' /../vendor/autoload.php ' ;
( new Dotenv ())-> bootEnv ( __DIR__ . ' /../.env ' );
$ kernel = new Kernel ( $ _SERVER [ ' APP_ENV ' ], ( bool ) $ _SERVER [ ' APP_DEBUG ' ]);
$ kernel -> boot ();
return $ kernel -> getContainer ()-> get ( ' doctrine ' )-> getManager (); Это расширение может сделать вывод типа результатов запросов DQL, когда предоставляется objectManagerLoader .
Примеры:
$ query = $ entityManager -> createQuery ( ' SELECT u FROM AcmeUser u ' );
$ query -> getResult (); // array<AcmeUser>
$ query = $ entityManager -> createQuery ( ' SELECT u.id, u.email, u.name FROM AcmeUser u ' );
$ query -> getResult (); // array<array{id: int, email: string, name: string|null}>
$ query = $ entityManager -> createQuery ( '
SELECT u.id, u.email, COALESCE(u.name, "Anonymous") AS name
FROM AcmeUser u
' );
$ query -> getSingleResult (Query:: HYDRATE_OBJECT ); // array{id: int, email: string, name: string}>
$ query = $ entityManager -> createQueryBuilder ()
-> select ( ' u ' )
-> from (User::class, ' u ' )
-> getQuery ();
$ query -> getResult (); // array<AcmeUser>Запросы анализируются статически и не требуют запущенного сервера баз данных. Это использует доктрину DQL SARSER и METADATA.
Большинство функций DQL поддерживаются, в том числе GROUP BY INDEX BY , DISTINCT , все ароматы JOIN , арифметические выражения, функции, агрегации, NEW и т. Д. Подобочки еще не поддерживаются (предполагаемый тип будет mixed ).
Находясь ли, например, SUM(e.column) , как float , numeric-string или int сильно зависит от драйверов, их настройки и PHP-версии. Это расширение автоматически выпускает вашу настройку и дает довольно точные результаты для pdo_mysql , mysqli , pdo_sqlite , sqlite3 , pdo_pgsql и pgsql .
Метод getResult поддерживается при вызове без аргумента или аргументом HydrateMode, установленным для Query::HYDRATE_OBJECT :
$ query = $ entityManager -> createQuery ( ' SELECT u FROM AcmeUser u ' );
$ query -> getResult (); // array<User>
$ query -> getResult (Query:: HYDRATE_OBJECT ); // array<User> Методы getOneOrNullResult , getSingleResult , toIterable и execute поддерживаются, когда аргумент Hydratemode явно устанавливается на Query::HYDRATE_OBJECT :
$ query = $ entityManager -> createQuery ( ' SELECT u FROM AcmeUser u ' );
$ query -> getOneOrNullResult (); // mixed
$ query -> getOneOrNullResult (Query:: HYDRATE_OBJECT ); // User Это связано с конструкцией класса Query , не позволяющей определить режим гидратации, используемый этими функциями, если он не указан явно во время вызова.
Не каждый запрос -повесток может быть статически проанализирован, вот мало советов, чтобы максимизировать тип, вывод:
select / join / from / set )Вы можете дать возможность сообщать о местах, где вывод недоступен:
parameters :
doctrine :
reportDynamicQueryBuilders : true Если в вашем приложении используются пользовательские типы доктрины, вы можете написать свои дескрипторы типа для правильного анализа их. Тип дескрипторов Реализуйте интерфейс PHPStanTypeDoctrineDescriptorsDoctrineTypeDescriptor который выглядит следующим образом:
<?php
public function getType(): string ;
public function getWritableToPropertyType(): Type ;
public function getWritableToDatabaseType(): Type ;getType() просто возвращает имя класса пользовательского типа.getWritableToPropertyType() возвращает тип PHPSTAN, который пользовательский тип будет писать в поле свойства объекта. По сути, это тип возврата метода Custom Type convertToPHPValue() .getWritableToDatabaseType() возвращает тип PHPStan, который можно записать из поля свойств объекта в пользовательский тип. Опять же, в основном это допустимый тип для первого аргумента пользовательского типа convertToDatabaseValue() . Как правило, по крайней мере для большинства нативных типов доктрины эти последние два метода возвращают один и тот же тип, но это не всегда так. Одним из примеров будет тип datetime , который позволяет вам установить любое DateTimeInterface в поле свойства, но всегда будет содержать тип DateTime при загрузке из базы данных.
Дескрипторы типов не должны иметь дело с нулевыми типами, так как они прозрачно добавлены/удалены из типов дескриптора по мере необходимости. Поэтому вам не нужно возвращать тип Union вашего пользовательского типа и NullType из методов дескриптора, даже если ваш пользовательский тип допускает null .
Если методы вашего пользовательского типа convertToPHPValue() и convertToDatabaseValue() имеют правильные типовые ручки, вам не нужно писать свой собственный дескриптор для него. PHPStanTypeDoctrineDescriptorsReflectionDescriptor может проанализировать типовые и сделать для вас все остальное.
Если родитель вашего типа является одним из необоснованных доктрины, ReflectionDescriptor повторно использует свой дескриптор даже для разрешения экспрессии (например AVG(t.cost) ). Например, если вы расширяете DoctrineDBALTypesDecimalType , он будет знать, что SQLite извлекает это как float|int и другие драйверы в виде numeric-string . Если вы расширяете только DoctrineDBALTypesType , вам следует использовать пользовательский дескриптор и, необязательно, реализовать даже DoctrineTypeDriverAwareDescriptor для предоставления конкретного разрешения.
Когда вы пишете дескриптор пользовательского типа, вы должны сообщить об этом Phpstan. Добавьте что -то подобное в свой phpstan.neon :
services :
-
class : MyCustomTypeDescriptor
tags : [ phpstan.doctrine.typeDescriptor ]
# in case you are using the ReflectionDescriptor
-
factory : PHPStanTypeDoctrineDescriptorsReflectionDescriptor ( ' MyAppMyCustomTypeName ' )
tags : [ phpstan.doctrine.typeDescriptor ]Если вы хотите быть уверенным, что никогда не забудете дескриптор для какого -то пользовательского типа, вы можете включить:
parameters :
doctrine :
reportUnknownTypes : trueЭто вызывает сбои, когда ваш объект использует пользовательский тип без дескриптора:
#[Entity]
abstract class Uuid7Entity
{
#[Id]
#[Column(type: Uuid7Type:: NAME )] // reported when descriptor for such type is missing
private Uuid7 $ hsCode ; Любая пользовательская функция DQL, которая реализует TypedExpression доктрины, понимается с помощью этого расширения и выводится с типом, используемым в его методе getReturnType() . Все другие пользовательские функции DQL выводятся как mixed . Обратите внимание, что вы не можете использовать Native StringType для составления (и вывода) результатов строк (см. Проблема ORM).
use Doctrine DBAL Types Type ;
use Doctrine DBAL Types Types ;
use Doctrine ORM Query AST TypedExpression ;
use Doctrine ORM Query AST Functions FunctionNode ;
use Doctrine ORM Query Parser ;
use Doctrine ORM Query SqlWalker ;
use Doctrine ORM Query TokenType ;
class Floor extends FunctionNode implements TypedExpression
{
private AST Node | string $ arithmeticExpression ;
public function getSql ( SqlWalker $ sqlWalker ): string
{
return ' FLOOR( ' . $ sqlWalker -> walkSimpleArithmeticExpression ( $ this -> arithmeticExpression ) . ' ) ' ;
}
public function parse ( Parser $ parser ): void
{
$ parser -> match (TokenType:: T_IDENTIFIER );
$ parser -> match (TokenType:: T_OPEN_PARENTHESIS );
$ this -> arithmeticExpression = $ parser -> SimpleArithmeticExpression ();
$ parser -> match (TokenType:: T_CLOSE_PARENTHESIS );
}
public function getReturnType (): Type
{
return Type:: getType (Types:: INTEGER );
}
} Файлы заглушки в Phpstan-Doctrine поставляются со многими параметрами, отмеченными literal-string . Это тип, ориентированный на безопасность, который позволяет лишь лишь литеральным строкам, записанным в коде, передаваться в эти параметры.
Это снижает риск инъекции SQL, потому что динамические строки от пользовательского ввода не принимаются вместо literal-string .
Примером, в котором используется этот тип, является параметр $sql в DoctrineDbalConnection::executeQuery() .
Чтобы включить этот расширенный тип в Phpstan-Doctrine, используйте этот параметр конфигурации:
parameters :
doctrine :
literalString : true