{"id":15024797,"url":"https://github.com/phpstan/phpstan-doctrine","last_synced_at":"2026-01-25T16:15:41.777Z","repository":{"id":38101675,"uuid":"80469068","full_name":"phpstan/phpstan-doctrine","owner":"phpstan","description":"Doctrine extensions for PHPStan","archived":false,"fork":false,"pushed_at":"2026-01-18T16:16:02.000Z","size":1169,"stargazers_count":656,"open_issues_count":98,"forks_count":112,"subscribers_count":8,"default_branch":"2.0.x","last_synced_at":"2026-01-19T00:39:36.210Z","etag":null,"topics":["doctrine","doctrine2","php","php7","phpstan","static-analysis","static-analyzer","static-code-analysis","testing"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phpstan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2017-01-30T22:13:21.000Z","updated_at":"2026-01-18T16:16:06.000Z","dependencies_parsed_at":"2024-01-29T22:09:33.964Z","dependency_job_id":"746e8b78-af16-4f38-b161-fb1f1fa9f2e6","html_url":"https://github.com/phpstan/phpstan-doctrine","commit_stats":{"total_commits":709,"total_committers":71,"mean_commits":9.985915492957746,"dds":0.3808180535966149,"last_synced_commit":"813296e18e232d8937fc2823cd987db1d0477ab1"},"previous_names":[],"tags_count":179,"template":false,"template_full_name":null,"purl":"pkg:github/phpstan/phpstan-doctrine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpstan%2Fphpstan-doctrine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpstan%2Fphpstan-doctrine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpstan%2Fphpstan-doctrine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpstan%2Fphpstan-doctrine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phpstan","download_url":"https://codeload.github.com/phpstan/phpstan-doctrine/tar.gz/refs/heads/2.0.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phpstan%2Fphpstan-doctrine/sbom","scorecard":{"id":534467,"data":{"date":"2025-08-11","repo":{"name":"github.com/phpstan/phpstan-doctrine","commit":"bddac51f0ffeb323e23bc379838bc08a4de3d0ba"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.1,"checks":[{"name":"Maintained","score":5,"reason":"4 commit(s) and 3 issue activity found in the last 90 days -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":4,"reason":"Found 11/27 approved changesets -- score normalized to 4","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch '2.0.x'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: github.com/phpstan/.github/SECURITY.md:1","Info: Found linked content: github.com/phpstan/.github/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: github.com/phpstan/.github/SECURITY.md:1","Info: Found text in security policy: github.com/phpstan/.github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 14 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T06:36:01.658Z","repository_id":38101675,"created_at":"2025-08-20T06:36:01.658Z","updated_at":"2025-08-20T06:36:01.658Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28755123,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-25T13:59:49.818Z","status":"ssl_error","status_checked_at":"2026-01-25T13:59:33.728Z","response_time":113,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["doctrine","doctrine2","php","php7","phpstan","static-analysis","static-analyzer","static-code-analysis","testing"],"created_at":"2024-09-24T20:00:57.009Z","updated_at":"2026-01-25T16:15:41.757Z","avatar_url":"https://github.com/phpstan.png","language":"PHP","readme":"# Doctrine extensions for PHPStan\n\n[![Build](https://github.com/phpstan/phpstan-doctrine/workflows/Build/badge.svg)](https://github.com/phpstan/phpstan-doctrine/actions)\n[![Latest Stable Version](https://poser.pugx.org/phpstan/phpstan-doctrine/v/stable)](https://packagist.org/packages/phpstan/phpstan-doctrine)\n[![License](https://poser.pugx.org/phpstan/phpstan-doctrine/license)](https://packagist.org/packages/phpstan/phpstan-doctrine)\n\n* [PHPStan](https://phpstan.org/)\n* [Doctrine](https://www.doctrine-project.org/)\n\nThis extension provides following features:\n\n* DQL validation for parse errors, unknown entity classes and unknown persistent fields. QueryBuilder validation is also supported.\n* Recognizes magic `findBy*`, `findOneBy*` and `countBy*` methods on EntityRepository.\n* Validates entity fields in repository `findBy`, `findBy*`, `findOneBy`, `findOneBy*`, `count` and `countBy*` method calls.\n* Interprets `EntityRepository\u003cMyEntity\u003e` correctly in phpDocs for further type inference of methods called on the repository.\n* Provides correct return for `Doctrine\\ORM\\EntityManager::getRepository()`.\n* Provides correct return type for `Doctrine\\ORM\\EntityManager::find`, `getReference` and `getPartialReference` when `Foo::class` entity class name is provided as the first argument\n* Adds missing `matching` method on `Doctrine\\Common\\Collections\\Collection`. This can be turned off by setting `parameters.doctrine.allCollectionsSelectable` to `false`.\n* Also supports Doctrine ODM.\n* Analysis of discrepancies between entity column types and property field types. This can be relaxed with the `allowNullablePropertyForRequiredField: true` setting.\n* Provides return type for `Doctrine\\ORM\\Query::getResult`, `getOneOrNullResult`, `getSingleResult`, `toIterable` and `execute` in `HYDRATE_OBJECT` mode (see below).\n\n## Installation\n\nTo use this extension, require it in [Composer](https://getcomposer.org/):\n\n```bash\ncomposer require --dev phpstan/phpstan-doctrine\n```\n\nIf you also install [phpstan/extension-installer](https://github.com/phpstan/extension-installer) then you're all set!\n\n\u003cdetails\u003e\n  \u003csummary\u003eManual installation\u003c/summary\u003e\n\nIf you don't want to use `phpstan/extension-installer`, include extension.neon in your project's PHPStan config:\n\n```neon\nincludes:\n    - vendor/phpstan/phpstan-doctrine/extension.neon\n```\n\nIf you're interested in DQL/QueryBuilder validation, include also `rules.neon` (you will also need to provide the `objectManagerLoader`, see below):\n\n```neon\nincludes:\n    - vendor/phpstan/phpstan-doctrine/rules.neon\n```\n\u003c/details\u003e\n\n\n## Configuration\n\nIf your repositories have a common base class, you can configure it in your `phpstan.neon` and PHPStan will see additional methods you define in it:\n\n```neon\nparameters:\n\tdoctrine:\n\t\tormRepositoryClass: MyApp\\Doctrine\\BetterEntityRepository\n\t\todmRepositoryClass: MyApp\\Doctrine\\BetterDocumentRepository\n```\n\nYou can opt in for more advanced analysis by providing the object manager from your own application. This will enable DQL validation:\n\n```neon\nparameters:\n\tdoctrine:\n\t\tobjectManagerLoader: tests/object-manager.php\n```\n\nExample for Symfony 4:\n\n```php\n// tests/object-manager.php\n\nuse App\\Kernel;\n\nrequire __DIR__ . '/../config/bootstrap.php';\n$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);\n$kernel-\u003eboot();\nreturn $kernel-\u003egetContainer()-\u003eget('doctrine')-\u003egetManager();\n```\n\nExample for Symfony 5:\n\n```php\n// tests/object-manager.php\n\nuse App\\Kernel;\nuse Symfony\\Component\\Dotenv\\Dotenv;\n\nrequire __DIR__ . '/../vendor/autoload.php';\n\n(new Dotenv())-\u003ebootEnv(__DIR__ . '/../.env');\n\n$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);\n$kernel-\u003eboot();\nreturn $kernel-\u003egetContainer()-\u003eget('doctrine')-\u003egetManager();\n```\n\n## Query type inference\n\nThis extension can infer the result type of DQL queries when an `objectManagerLoader` is provided.\n\nExamples:\n\n```php\n$query = $entityManager-\u003ecreateQuery('SELECT u FROM Acme\\User u');\n$query-\u003egetResult(); // array\u003cAcme\\User\u003e\n\n$query = $entityManager-\u003ecreateQuery('SELECT u.id, u.email, u.name FROM Acme\\User u');\n$query-\u003egetResult(); // array\u003carray{id: int, email: string, name: string|null}\u003e\n\n$query = $entityManager-\u003ecreateQuery('\n    SELECT u.id, u.email, COALESCE(u.name, \"Anonymous\") AS name\n    FROM   Acme\\User u\n');\n$query-\u003egetSingleResult(Query::HYDRATE_OBJECT); // array{id: int, email: string, name: string}\u003e\n\n$query = $entityManager-\u003ecreateQueryBuilder()\n    -\u003eselect('u')\n    -\u003efrom(User::class, 'u')\n    -\u003egetQuery();\n$query-\u003egetResult(); // array\u003cAcme\\User\u003e\n```\n\nQueries are analyzed statically and do not require a running database server. This makes use of the Doctrine DQL parser and entities metadata.\n\nMost DQL features are supported, including `GROUP BY`, `INDEX BY`, `DISTINCT`, all flavors of `JOIN`, arithmetic expressions, functions, aggregations, `NEW`, etc. Sub queries are not yet supported (infered type will be `mixed`).\n\n### Query type inference of expressions\n\nWhether e.g. `SUM(e.column)` is fetched as `float`, `numeric-string` or `int` highly [depends on drivers, their setup and PHP version](https://github.com/janedbal/php-database-drivers-fetch-test).\nThis extension autodetects your setup and provides quite accurate results for `pdo_mysql`, `mysqli`, `pdo_sqlite`, `sqlite3`, `pdo_pgsql` and `pgsql`.\n\n### Supported methods\n\nThe `getResult` method is supported when called without argument, or with the hydrateMode argument set to `Query::HYDRATE_OBJECT`:\n\n``` php\n$query = $entityManager-\u003ecreateQuery('SELECT u FROM Acme\\User u');\n\n$query-\u003egetResult(); // array\u003cUser\u003e\n\n$query-\u003egetResult(Query::HYDRATE_OBJECT); // array\u003cUser\u003e\n```\n\nThe methods `getOneOrNullResult`, `getSingleResult`, `toIterable`, and `execute` are supported when the hydrateMode argument is explicitly set to `Query::HYDRATE_OBJECT`:\n\n``` php\n$query = $entityManager-\u003ecreateQuery('SELECT u FROM Acme\\User u');\n\n$query-\u003egetOneOrNullResult(); // mixed\n\n$query-\u003egetOneOrNullResult(Query::HYDRATE_OBJECT); // User\n```\n\nThis is due to the design of the `Query` class preventing from determining the hydration mode used by these functions unless it is specified explicitly during the call.\n\n### Problematic approaches\n\nNot every QueryBuilder can be statically analysed, here are few advices to maximize type inferring:\n- Do not pass QueryBuilder to methods\n- Do not use dynamic expressions in QueryBuilder methods (mainly in `select`/`join`/`from`/`set`)\n\nYou can enable reporting of places where inferring is unavailable by:\n\n```neon\nparameters:\n\tdoctrine:\n\t\treportDynamicQueryBuilders: true\n```\n\n## Custom types\n\nIf your application uses custom Doctrine types, you can write your own type descriptors to analyse them properly.\nType descriptors implement the interface `PHPStan\\Type\\Doctrine\\Descriptors\\DoctrineTypeDescriptor` which looks like this:\n\n```php\n\u003c?php\n\npublic function getType(): string;\n\npublic function getWritableToPropertyType(): Type;\n\npublic function getWritableToDatabaseType(): Type;\n```\n\n* The `getType()` method simply returns the class name of the custom type.\n* The `getWritableToPropertyType()` method returns the PHPStan type that the custom type will write into the entity's property field. Basically it is the return type of the custom type's `convertToPHPValue()` method.\n* The `getWritableToDatabaseType()` method returns the PHPStan type that can be written from the entity's property field into the custom type. Again, basically it's the allowed type for the custom type's `convertToDatabaseValue()`'s first argument.\n\nGenerally, at least for most of Doctrine's native types, these last two methods will return the same type, but it is not always the case. One example would be the `datetime` type, which allows you to set any `\\DateTimeInterface` into to property field, but will always contain the `\\DateTime` type when loaded from the database.\n\n### Nullable types\n\nType descriptors don't have to deal with nullable types, as these are transparently added/removed from the descriptor's types as needed. Therefore you don't have to return the union type of your custom type and `NullType` from the descriptor's methods, even if your custom type allows `null`.\n\n### ReflectionDescriptor\n\nIf your custom type's `convertToPHPValue()` and `convertToDatabaseValue()` methods have proper typehints, you don't have to write your own descriptor for it. The `PHPStan\\Type\\Doctrine\\Descriptors\\ReflectionDescriptor` can analyse the typehints and do the rest for you.\n\nIf parent of your type is one of the Doctrine's non-abstract ones, `ReflectionDescriptor` will reuse its descriptor even for expression resolution (e.g. `AVG(t.cost)`).\nFor example, if you extend `Doctrine\\DBAL\\Types\\DecimalType`, it will know that sqlite fetches that as `float|int` and other drivers as `numeric-string`.\nIf you extend only `Doctrine\\DBAL\\Types\\Type`, you should use custom descriptor and optionally implement even `DoctrineTypeDriverAwareDescriptor` to provide driver-specific resolution.\n\n### Registering type descriptors\n\nWhen you write a custom type descriptor, you have to let PHPStan know about it. Add something like this into your `phpstan.neon`:\n\n```neon\nservices:\n\t-\n\t\tclass: MyCustomTypeDescriptor\n\t\ttags: [phpstan.doctrine.typeDescriptor]\n\n\t# in case you are using the ReflectionDescriptor\n\t-\n\t\tfactory: PHPStan\\Type\\Doctrine\\Descriptors\\ReflectionDescriptor('MyApp\\MyCustomTypeName')\n\t\ttags: [phpstan.doctrine.typeDescriptor]\n```\n\n### Ensure types have descriptor\n\nIf you want to be sure you never forget descriptor for some custom type, you can enable:\n\n```neon\nparameters:\n\tdoctrine:\n\t\treportUnknownTypes: true\n```\n\nThis causes failures when your entity uses custom type without descriptor:\n\n```php\n#[Entity]\nabstract class Uuid7Entity\n{\n\n    #[Id]\n    #[Column(type: Uuid7Type::NAME)] // reported when descriptor for such type is missing\n    private Uuid7 $hsCode;\n\n```\n\n## Custom DQL functions\n\nAny [custom DQL function](https://www.doctrine-project.org/projects/doctrine-orm/en/3.2/cookbook/dql-user-defined-functions.html) that implements Doctrine's `TypedExpression` is understood by this extension and is inferred with the type used in its `getReturnType()` method.\nAll other custom DQL functions are inferred as `mixed`.\nPlease note that you cannot use native `StringType` to cast (and infer) string results (see [ORM issue](https://github.com/doctrine/orm/issues/11537)).\n\n```php\n\nuse Doctrine\\DBAL\\Types\\Type;\nuse Doctrine\\DBAL\\Types\\Types;\nuse Doctrine\\ORM\\Query\\AST\\TypedExpression;\nuse Doctrine\\ORM\\Query\\AST\\Functions\\FunctionNode;\nuse Doctrine\\ORM\\Query\\Parser;\nuse Doctrine\\ORM\\Query\\SqlWalker;\nuse Doctrine\\ORM\\Query\\TokenType;\n\nclass Floor extends FunctionNode implements TypedExpression\n{\n    private AST\\Node|string $arithmeticExpression;\n\n    public function getSql(SqlWalker $sqlWalker): string\n    {\n        return 'FLOOR(' . $sqlWalker-\u003ewalkSimpleArithmeticExpression($this-\u003earithmeticExpression) . ')';\n    }\n\n    public function parse(Parser $parser): void\n    {\n        $parser-\u003ematch(TokenType::T_IDENTIFIER);\n        $parser-\u003ematch(TokenType::T_OPEN_PARENTHESIS);\n\n        $this-\u003earithmeticExpression = $parser-\u003eSimpleArithmeticExpression();\n\n        $parser-\u003ematch(TokenType::T_CLOSE_PARENTHESIS);\n    }\n\n\n    public function getReturnType(): Type\n    {\n        return Type::getType(Types::INTEGER);\n    }\n}\n\n```\n\n## Literal strings\n\nStub files in phpstan-doctrine come with many parameters marked with `literal-string`. This is a security-focused type that only allows literal strings written in code to be passed into these parameters.\n\nThis reduces risk of SQL injection because dynamic strings from user input are not accepted in place of `literal-string`.\n\nAn example where this type is used is `$sql` parameter in `Doctrine\\Dbal\\Connection::executeQuery()`.\n\nTo enable this advanced type in phpstan-doctrine, use this configuration parameter:\n\n```neon\nparameters:\n\tdoctrine:\n\t\tliteralString: true\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphpstan%2Fphpstan-doctrine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphpstan%2Fphpstan-doctrine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphpstan%2Fphpstan-doctrine/lists"}