# phpstan-drupal

This page lists every rule and extension registered by phpstan-drupal 2.2.2, and what takes its place in a Mago setup with this extension. To find a check, search for the identifier printed by PHPStan with an error, or named in a baseline or an `@phpstan-ignore` comment.

Each identifier and extension is followed by what handles it:

- an analyzer check of this extension, such as `drupal/config-unknown-key`. Its entry under [Analyzer](<https://project.pages.drupalcode.org/mago/analyzer/index.md>) says where it differs from phpstan-drupal;
- a linter rule of this extension, such as `drupal/discouraged-function`, for a check that needs no types. Its entry is under [Linter rules](<https://project.pages.drupalcode.org/mago/rules/index.md>);
- `mago analyze` with one of Mago's own issues;
- a section of the analyzer docs, for an extension that types Drupal's APIs;
- nothing, when no tool checks it yet. The note says why.

The analyzer checks run by default, including the ones whose phpstan-drupal rule is off by default. To turn one off, use `[analyzer] ignore`, as [Turning rules off](<https://project.pages.drupalcode.org/mago/setup/configuration/index.md#turning-rules-off>) shows. A `@phpstan-ignore` comment with one of phpstan-drupal's identifiers doesn't drop the matching Mago issue, see [PHPStan ignores](<https://project.pages.drupalcode.org/mago/analyzer/phpstan-ignores/index.md>).

## Rules

### AccessResultConditionRule

- `identical.alwaysFalse`: `mago analyze` (`redundant-comparison`)
- `notIdentical.alwaysFalse`: `mago analyze` (`redundant-comparison`)

Mago reports a strict comparison that is always true or always false anywhere, not only as the condition of `AccessResult::allowedIf()` or `forbiddenIf()`.

### BrowserTestBaseDefaultThemeRule

- `BrowserTestBase.defaultThemeRequired`: [`drupal/browser-test-default-theme`](<https://project.pages.drupalcode.org/mago/analyzer/tests/index.md#drupalbrowser-test-default-theme>)

### CacheableDependencyRule

- `cacheable.dependency`: [`drupal/cacheable-dependency`](<https://project.pages.drupalcode.org/mago/analyzer/render-and-forms/index.md#drupalcacheable-dependency>)

### ClassExtendsInternalClassRule

- `classExtendsInternalClass.classExtendsInternalClass`: [`drupal/internal-class-extension`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalinternal-class-extension>)

### ConfigEntityConfigExportRule

- `configEntityConfigExport.noConfigExportKey`: [`drupal/config-entity-export`](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#drupalconfig-entity-export>)

### ConfigGetUnknownKeyRule

- `drupal.configGetUnknownKey`: [`drupal/config-unknown-key`](<https://project.pages.drupalcode.org/mago/analyzer/config/index.md#drupalconfig-unknown-key>)

Off by default in phpstan-drupal.

### DependencySerializationTraitPropertyRule

- `dependencySerializationTraitProperty.unsupportedPrivateProperty`: [`drupal/dependency-serialization-property`](<https://project.pages.drupalcode.org/mago/analyzer/injection/index.md#drupaldependency-serialization-property>)
- `dependencySerializationTraitProperty.unsupportedReadOnlyProperty`: [`drupal/dependency-serialization-property`](<https://project.pages.drupalcode.org/mago/analyzer/injection/index.md#drupaldependency-serialization-property>)

### DeprecatedHookImplementation

- `deprecatedHookImplementation.other`: [`drupal/deprecated-hook`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupaldeprecated-hook>)
- `deprecatedHookImplementation.useHookFieldWidgetCompleteFormAlter`: [`drupal/deprecated-hook`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupaldeprecated-hook>)
- `deprecatedHookImplementation.useHookFieldWidgetMulitValueWidgetTypeFormAlter`: [`drupal/deprecated-hook`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupaldeprecated-hook>)

phpstan-drupal runs it only when a project adds it to `rules`.

### DiscouragedFunctionsRule

- `discouragedFunctions.discouragedFunctionCalled`: [`drupal/discouraged-function`](<https://project.pages.drupalcode.org/mago/rules/phpstan-drupal/index.md#drupaldiscouraged-function>)

### EntityListBuilderOperationsCacheabilityRule

- `drupal.entityListBuilderMissingCacheabilityParameter`: [`drupal/list-builder-cacheability`](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#drupallist-builder-cacheability>)

### EntityQueryHasAccessCheckRule

- `entityQueryHasAccessCheck.noAccessCheck`: [`drupal/entity-query-access-check`](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#drupalentity-query-access-check>)

### EntityStorageDirectInjectionRule

- `drupal.entityStorageDirectInjection`: [`drupal/entity-storage-injection`](<https://project.pages.drupalcode.org/mago/analyzer/injection/index.md#drupalentity-storage-injection>)

### EntityStoragePropertyAssignmentRule

- `drupal.entityStoragePropertyAssignment`: [`drupal/entity-storage-property`](<https://project.pages.drupalcode.org/mago/analyzer/injection/index.md#drupalentity-storage-property>)

### GetDeprecatedServiceRule

- `getDeprecatedService.deprecated`: [`drupal/deprecated-service`](<https://project.pages.drupalcode.org/mago/analyzer/services/index.md#drupaldeprecated-service>)

### GlobalDrupalDependencyInjectionRule

- `globalDrupalDependencyInjection.useDependencyInjection`: [`drupal/global-drupal-call`](<https://project.pages.drupalcode.org/mago/analyzer/injection/index.md#drupalglobal-drupal-call>), partly

### HookEntityOperationCacheabilityRule

- `drupal.hookEntityOperationAlterMissingCacheabilityParameter`: [`drupal/hook-entity-operation-cacheability`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-entity-operation-cacheability>)
- `drupal.hookEntityOperationMissingCacheabilityParameter`: [`drupal/hook-entity-operation-cacheability`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-entity-operation-cacheability>)

### HookFormAlterRule

- `hookFormAlter.formParameterNotByRef`: [`drupal/hook-form-alter-signature`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-form-alter-signature>)
- `hookFormAlter.invalidFormIdParameterType`: [`drupal/hook-form-alter-signature`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-form-alter-signature>)
- `hookFormAlter.invalidFormParameterType`: [`drupal/hook-form-alter-signature`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-form-alter-signature>)
- `hookFormAlter.invalidFormStateParameterType`: [`drupal/hook-form-alter-signature`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-form-alter-signature>)
- `hookFormAlter.invalidParameterCount`: [`drupal/hook-form-alter-signature`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-form-alter-signature>)

### LoadIncludes

- `loadIncludes.fileNotLoadable`: [`drupal/load-include`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalload-include>)
- `loadIncludes.moduleNotFound`: [`drupal/load-include`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalload-include>)

### LoggerFromFactoryPropertyAssignmentRule

- `loggerFromFactory.propertyAssignment`: [`drupal/logger-from-factory`](<https://project.pages.drupalcode.org/mago/analyzer/injection/index.md#drupallogger-from-factory>)

### ModuleLoadInclude

- `moduleLoadInclude.moduleNotFound`: nothing
- `moduleLoadInclude.moduleNotLoadable`: nothing

Drupal 11 removed `module_load_include()`, and Mago reports a call to it as `non-existent-function`.

### PluginAnnotationContextDefinitionsRule

- `pluginAnnotationContextDefinitions.useContextDefinitionsKey`: [`drupal/plugin-annotation-context`](<https://project.pages.drupalcode.org/mago/analyzer/plugins/index.md#drupalplugin-annotation-context>)

### PluginManagerInspectionRule

- `pluginManagerInspection.alterInfoMissing`: [`drupal/plugin-manager-alter-info`](<https://project.pages.drupalcode.org/mago/analyzer/plugins/index.md#drupalplugin-manager-alter-info>)
- `pluginManagerInspection.yamlPluginManagersInvokesParentConstructor`: nothing. A plugin manager with YAML discovery that calls `parent::__construct()`. The call is harmless, since the parent constructor only stores its arguments.

Off by default in phpstan-drupal.

### PluginManagerSetsCacheBackendRule

- `pluginManagerSetsCacheBackend.missingCacheBackend`: [`drupal/plugin-manager-cache-backend`](<https://project.pages.drupalcode.org/mago/analyzer/plugins/index.md#drupalplugin-manager-cache-backend>)

### ProceduralHookEntityOperationCacheabilityRule

- `drupal.proceduralHookEntityOperationAlterMissingCacheabilityParameter`: [`drupal/hook-entity-operation-cacheability`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-entity-operation-cacheability>)
- `drupal.proceduralHookEntityOperationMissingCacheabilityParameter`: [`drupal/hook-entity-operation-cacheability`](<https://project.pages.drupalcode.org/mago/analyzer/hooks/index.md#drupalhook-entity-operation-cacheability>)

### RenderCallbackRule

- `renderCallback.callbackNotImplementsTrustedCallback`: [`drupal/untrusted-callback`](<https://project.pages.drupalcode.org/mago/analyzer/render-and-forms/index.md#drupaluntrusted-callback>)
- `renderCallback.callbackNotImplementsTrustedCallbackInterface`: [`drupal/untrusted-callback`](<https://project.pages.drupalcode.org/mago/analyzer/render-and-forms/index.md#drupaluntrusted-callback>)
- `renderCallback.containsClosure`: [`drupal/form-closure-callback`](<https://project.pages.drupalcode.org/mago/analyzer/render-and-forms/index.md#drupalform-closure-callback>)
- `renderCallback.expectsArrayOfCallbacks`: [`drupal/render-callback`](<https://project.pages.drupalcode.org/mago/rules/phpstan-drupal/index.md#drupalrender-callback>)
- `renderCallback.expectsCallableArrayWithArguments`: [`drupal/render-callback`](<https://project.pages.drupalcode.org/mago/rules/phpstan-drupal/index.md#drupalrender-callback>)
- `renderCallback.invalidKeyValue`: [`drupal/invalid-callback`](<https://project.pages.drupalcode.org/mago/analyzer/render-and-forms/index.md#drupalinvalid-callback>), partly
- `renderCallback.nonCallableCallback`: [`drupal/unknown-callback`](<https://project.pages.drupalcode.org/mago/analyzer/render-and-forms/index.md#drupalunknown-callback>), [`drupal/invalid-callback`](<https://project.pages.drupalcode.org/mago/analyzer/render-and-forms/index.md#drupalinvalid-callback>)
- `renderCallback.nonTrustedCallback`: [`drupal/render-callback`](<https://project.pages.drupalcode.org/mago/rules/phpstan-drupal/index.md#drupalrender-callback>)

### StaticServiceDeprecatedServiceRule

- `staticServiceDeprecatedService.deprecated`: [`drupal/deprecated-service`](<https://project.pages.drupalcode.org/mago/analyzer/services/index.md#drupaldeprecated-service>)

### SymfonyYamlParseRule

- `drupal.symfonyYamlParse`: [`drupal/symfony-yaml-parse`](<https://project.pages.drupalcode.org/mago/rules/phpstan-drupal/index.md#drupalsymfony-yaml-parse>)

### TestClassesProtectedPropertyModulesRule

- `testClass.propertyModulesNonProtected`: [`drupal/test-modules-visibility`](<https://project.pages.drupalcode.org/mago/analyzer/tests/index.md#drupaltest-modules-visibility>)

### TestClassSuffixNameRule

- `nonAbstractTestClass.NameNoSuffixTest`: [`drupal/test-class-suffix`](<https://project.pages.drupalcode.org/mago/rules/naming-and-imports/index.md#drupaltest-class-suffix>), [`drupal/test-class-name`](<https://project.pages.drupalcode.org/mago/analyzer/tests/index.md#drupaltest-class-name>)

### TodoCommentWithIssueUrlRule

- `drupal.todoCurrentIssue`: [`drupal/todo-current-issue`](<https://project.pages.drupalcode.org/mago/rules/phpstan-drupal/index.md#drupaltodo-current-issue>)

phpstan-drupal runs it only when a project adds it to `rules`.

## Extensions

phpstan-drupal's extensions type what Drupal's APIs return, read entity fields as properties, replace core's docblocks with stub files and decide where a deprecated call is allowed. The analyzer plugin does the same work, and the linked section says how.

- `AccessCheckTypeSpecifyingExtension`: [Entities: Entity queries](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#entity-queries>)
- `BootstrapResultCacheMetaExtension`: nothing. It invalidates PHPStan's result cache when an extension's info file, a services file, a config schema file or the Drupal version changes. A one-shot run doesn't need anything in its place: Mago doesn't keep a result cache, and the analyzer keys its own index cache by the modification times and sizes of its files, see [Index cache](<https://project.pages.drupalcode.org/mago/setup/worker/index.md#index-cache>). A watch session re-analyzes only the PHP files that change, so an edit to one of those files shows on the PHP files that depend on it once they change or the session restarts.
- `ConfigGetDynamicReturnTypeExtension`: [Config: Config objects](<https://project.pages.drupalcode.org/mago/analyzer/config/index.md#config-objects>). Off by default in phpstan-drupal.
- `ContainerDynamicReturnTypeExtension`: [Services: Container lookups](<https://project.pages.drupalcode.org/mago/analyzer/services/index.md#container-lookups>)
- `DeprecationHelperScope`: [Deprecations: Deprecation scopes](<https://project.pages.drupalcode.org/mago/analyzer/deprecations/index.md#deprecation-scopes>)
- `DrupalClassResolverDynamicReturnTypeExtension`: [Services: Container lookups](<https://project.pages.drupalcode.org/mago/analyzer/services/index.md#container-lookups>)
- `DrupalClassResolverDynamicStaticReturnTypeExtension`: [Services: Container lookups](<https://project.pages.drupalcode.org/mago/analyzer/services/index.md#container-lookups>)
- `DrupalServiceDynamicReturnTypeExtension`: [Services: Container lookups](<https://project.pages.drupalcode.org/mago/analyzer/services/index.md#container-lookups>)
- `DrupalStaticEntityQueryDynamicReturnTypeExtension`: [Entities: Entity queries](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#entity-queries>)
- `DrupalStubFilesExtension`: [Core types and stubs: Stub files](<https://project.pages.drupalcode.org/mago/analyzer/core-types/index.md#stub-files>), partly. Not every stub is translated, see [Not ported from phpstan-drupal](<https://project.pages.drupalcode.org/mago/analyzer/core-types/index.md#not-ported-from-phpstan-drupal>).
- `EntityAccessControlHandlerReturnTypeExtension`: [Entities: Access checks](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#access-checks>)
- `EntityFieldMethodsViaMagicReflectionExtension`: [Entities: Magic entity fields](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#magic-entity-fields>)
- `EntityFieldsViaMagicReflectionExtension`: [Entities: Magic entity fields](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#magic-entity-fields>)
- `EntityIdNarrowedByNew`: nothing. `id()` isn't null once `isNew()` is false. A stub can't say it: Mago and PHPStan read a docblock assertion both ways, so `@psalm-assert-if-false` on `isNew()` would also make `id()` null when `isNew()` is true. A check on `id()` itself narrows the later calls, see [Stub files](<https://project.pages.drupalcode.org/mago/analyzer/core-types/index.md#stub-files>) and [Not ported from phpstan-drupal](<https://project.pages.drupalcode.org/mago/analyzer/core-types/index.md#not-ported-from-phpstan-drupal>).
- `EntityQueryAccessCheckDynamicReturnTypeExtension`: [Entities: Entity queries](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#entity-queries>)
- `EntityQueryDynamicReturnTypeExtension`: [Entities: Entity queries](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#entity-queries>)
- `EntityRepositoryReturnTypeExtension`: [Entities: Handlers and storage](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#handlers-and-storage>)
- `EntityStorageDynamicReturnTypeExtension`: [Entities: Handlers and storage](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#handlers-and-storage>)
- `EntityTypeIdTypeNodeResolverExtension`: nothing. The `entity-type-id` docblock type. Mago reports it as an unknown class, so write `string` instead. phpstan-drupal 2.2.2 accepts any string for it, so `string` doesn't lose any check.
- `EntityTypeKeyByExistence`: [Entities: Entity type keys](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#entity-type-keys>), partly. `getKey()` is a string for the keys that every entity type has. A `hasKey()` check for the same key doesn't narrow it, but the reports about a possible `false` from a `getKey()` call that such a check guards in an `if` or an `&&` chain are dropped.
- `EntityTypeLinkTemplateByExistence`: [Entities: Entity type keys](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#entity-type-keys>), partly. A `hasLinkTemplate()` check for the same template doesn't narrow `getLinkTemplate()`, but the reports about a possible `false` from a `getLinkTemplate()` call that such a check guards in an `if` or an `&&` chain are dropped. A false check doesn't narrow the call to `false`.
- `EntityTypeManagerGetStorageDynamicReturnTypeExtension`: [Entities: Handlers and storage](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#handlers-and-storage>)
- `GetQueryReturnTypeExtension`: [Entities: Entity queries](<https://project.pages.drupalcode.org/mago/analyzer/entities/index.md#entity-queries>)
- `GroupLegacyScope`: [Deprecations: Deprecation scopes](<https://project.pages.drupalcode.org/mago/analyzer/deprecations/index.md#deprecation-scopes>)
- `IgnoreDeprecationsScope`: [Deprecations: Deprecation scopes](<https://project.pages.drupalcode.org/mago/analyzer/deprecations/index.md#deprecation-scopes>)
- `InspectorTypeExtension`: [Core types and stubs: Inspector assertions](<https://project.pages.drupalcode.org/mago/analyzer/core-types/index.md#inspector-assertions>)
