Config::get return type extension#993
Conversation
|
This passes on 11.3 but fails on 10.4, which kind of proves that this is working, since the same config in 10.4 is not |
There was a problem hiding this comment.
🤖 review incoming:
Nice proof-of-concept — the core instinct (gate everything on FullyValidatable so the schema is authoritative) is the right one, and the conservative mixed/null fallbacks mean the type extension can only add info, not break existing code. Tests pass locally (14/14).
Left inline notes. Two blocking items: a confirmed sequence false-positive in the rule, and the type extension being on-by-default (release/versioning implications). The rest are smells worth cleaning up before merge. On your open question — yes, keep the type nullable: Config::get() returns null for an absent key regardless of schema completeness, so T|null is correct.
| return true; | ||
| } | ||
|
|
||
| if (!isset($definition['mapping']) || !is_array($definition['mapping'])) { |
There was a problem hiding this comment.
Blocking — confirmed false positive on sequence children. This only walks mapping, so a path segment that lands on a sequence returns false and any child key is reported as unknown. A sequence has arbitrary keys, so this is wrong. Reproduced against system.mail (FullyValidatable, interface is a sequence):
\Drupal::config('system.mail')->get('interface.default');
// => Config key "interface.default" does not exist in the schema for "system.mail". ← false positiveWhen traversal hits a sequence, accept the remaining key path. The same gap exists in resolveKeyType below, but there it just yields mixed (safe), so only the rule emits a bad error.
| * | ||
| * @var array<string, array<string, mixed>> | ||
| */ | ||
| private static $definitions = []; |
There was a problem hiding this comment.
These are static on a service that's registered as a singleton — no reason for static state here. It leaks across container instances and risks test cross-contamination (one test's schema bleeding into another). Make $definitions and $fullyValidatable instance properties.
| arguments: | ||
| containerHasAlwaysTrue: %drupal.bleedingEdge.containerHasAlwaysTrue% | ||
| - | ||
| class: mglaman\PHPStanDrupal\Type\ConfigGetDynamicReturnTypeExtension |
There was a problem hiding this comment.
Blocking — on by default. Unlike the rule (opt-in via configGetUnknownKeyRule: false), this type extension is registered unconditionally, so every user gets mixed → bool|null etc. on upgrade. That can surface new errors (e.g. a now-string|null value passed into a non-null param). Per the versioning policy this can't ship in a patch — either gate it behind a parameter too, or release as a minor and call it out in the changelog.
| return $errors; | ||
| } | ||
|
|
||
| private function resolveConfigName(MethodCall $methodCall, Scope $scope): ?string |
There was a problem hiding this comment.
resolveConfigName() and extractFirstStringArg() are copy-pasted verbatim from ConfigGetDynamicReturnTypeExtension (line 79 there) — ~60 lines, and the comments are already drifting apart. Hoist to one place (a trait, or methods on ConfigSchemaData) so the four config-name patterns stay in sync.
|
|
||
| // Parse all schema files. | ||
| foreach ($schemaDirs as $dir) { | ||
| $files = glob($dir . '/*.schema.yml'); |
There was a problem hiding this comment.
Non-blocking, worth noting: this parses every *.schema.yml (core + all modules + all themes) on every autoloader boot, even when neither feature is active. On a large site that's real startup latency. Consider lazy-loading the schemas, or only parsing when the extension/rule is enabled.
|
1. Sequence traversal fix (BLOCKING) — ConfigSchemaData.php
New test cases added:
2. Opt-in gate for type extension (BLOCKING) — extension.neon + ConfigGetDynamicReturnTypeExtension.php Added 3. Static property improvements — ConfigSchemaData.php Properties kept static (required for PHPStan test infrastructure cross-container survival) but given proper type declarations: 4. ConfigNameResolverTrait — new src/Drupal/ConfigNameResolverTrait.php
5. Lazy-loading TODO — DrupalAutoloader.php Added |
I vibe-coded a solution to #753. Please accept my apologies, as I don't have enough experience with this repo to verify if this is a good approach, so please only review as a proof of concept.
The TL;DR is:
Config::get()return types using Drupal's config schema files, so calls like\Drupal::config('system.cron')->get('logging')resolve tobool|nullinstead of mixed. Only applies to configs with theFullyValidatableconstraint, which guarantees the schema is complete. Types are always nullable since any key can be absent at runtime, but maybe we should assume they are not?configGetUnknownKeyRule) that reports calls to->get()with a key that doesn't exist in the schema. This catches typos at analysis time rather than runtime.I've attached a more verbose explanation, but fair warning about the use of the LLM here.
config-get-return-type-extension.md