Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ parameters:
entityQuery: true
entityRepository: true
stubFiles: true
configGetReturnType: false
rules:
testClassSuffixNameRule: false
dependencySerializationTraitPropertyRule: false
Expand All @@ -40,6 +41,7 @@ parameters:
entityStorageDirectInjectionRule: false
symfonyYamlParseRule: false
entityOperationsCacheabilityRule: false
configGetUnknownKeyRule: false
entityMapping:
aggregator_feed:
class: Drupal\aggregator\Entity\Feed
Expand Down Expand Up @@ -269,6 +271,7 @@ parametersSchema:
entityRepository: boolean()
stubFiles: boolean()
])
configGetReturnType: boolean()
rules: structure([
testClassSuffixNameRule: boolean()
dependencySerializationTraitPropertyRule: boolean()
Expand All @@ -281,6 +284,7 @@ parametersSchema:
entityStorageDirectInjectionRule: boolean()
symfonyYamlParseRule: boolean()
entityOperationsCacheabilityRule: boolean()
configGetUnknownKeyRule: boolean()
])
entityMapping: arrayOf(anyOf(
structure([
Expand All @@ -297,6 +301,8 @@ services:
class: mglaman\PHPStanDrupal\Drupal\ServiceMap
-
class: mglaman\PHPStanDrupal\Drupal\ExtensionMap
-
class: mglaman\PHPStanDrupal\Drupal\ConfigSchemaData
-
class: mglaman\PHPStanDrupal\Drupal\EntityDataRepository
arguments:
Expand Down Expand Up @@ -329,6 +335,11 @@ services:
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
arguments:
containerHasAlwaysTrue: %drupal.bleedingEdge.containerHasAlwaysTrue%
-
class: mglaman\PHPStanDrupal\Type\ConfigGetDynamicReturnTypeExtension

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
arguments:
configGetReturnType: %drupal.configGetReturnType%
-
class: mglaman\PHPStanDrupal\Type\DrupalClassResolverDynamicReturnTypeExtension
tags: [phpstan.broker.dynamicMethodReturnTypeExtension]
Expand Down
4 changes: 4 additions & 0 deletions rules.neon
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ rules:
- mglaman\PHPStanDrupal\Rules\Drupal\TestClassesProtectedPropertyModulesRule

conditionalTags:
mglaman\PHPStanDrupal\Rules\Drupal\ConfigGetUnknownKeyRule:
phpstan.rules.rule: %drupal.rules.configGetUnknownKeyRule%
mglaman\PHPStanDrupal\Rules\Drupal\HookFormAlterRule:
phpstan.rules.rule: %drupal.rules.hookRules%
mglaman\PHPStanDrupal\Rules\Drupal\Tests\TestClassSuffixNameRule:
Expand Down Expand Up @@ -44,6 +46,8 @@ conditionalTags:
phpstan.rules.rule: %drupal.rules.entityOperationsCacheabilityRule%

services:
-
class: mglaman\PHPStanDrupal\Rules\Drupal\ConfigGetUnknownKeyRule
-
class: mglaman\PHPStanDrupal\Rules\Drupal\HookFormAlterRule
-
Expand Down
92 changes: 92 additions & 0 deletions src/Drupal/ConfigNameResolverTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php declare(strict_types=1);

namespace mglaman\PHPStanDrupal\Drupal;

use Drupal\Core\Config\ConfigFactoryInterface;
use PhpParser\Node\Expr\MethodCall;
use PhpParser\Node\Expr\StaticCall;
use PhpParser\Node\Identifier;
use PhpParser\Node\Scalar\String_;
use PHPStan\Analyser\Scope;
use PHPStan\Type\ObjectType;
use function count;

/**
* Provides helpers to resolve a Drupal config name from a method call chain.
*
* Used by ConfigGetUnknownKeyRule and ConfigGetDynamicReturnTypeExtension to
* share the logic for extracting the config name from patterns such as:
* - \Drupal::config('name')->get(...)
* - $configFactory->get('name')->get(...)
* - $configFactory->getEditable('name')->get(...)
* - $this->config('name')->get(...) (ConfigFormBaseTrait)
*/
trait ConfigNameResolverTrait
{

private function resolveConfigName(MethodCall $methodCall, Scope $scope): ?string
{
$var = $methodCall->var;

if (!$var instanceof MethodCall && !$var instanceof StaticCall) {
return null;
}

$methodName = $var instanceof MethodCall
? ($var->name instanceof Identifier ? $var->name->name : null)
: ($var->name instanceof Identifier ? $var->name->name : null);

if ($methodName === null) {
return null;
}

// Pattern 1: \Drupal::config('name')
if ($var instanceof StaticCall && $methodName === 'config') {
return $this->extractFirstStringArg($var->getArgs());
}

// Pattern 2: $this->config('name') from ConfigFormBaseTrait
if ($var instanceof MethodCall && $methodName === 'config') {
return $this->extractFirstStringArg($var->getArgs());
}

// Pattern 3: $configFactory->get('name')
if ($var instanceof MethodCall && $methodName === 'get') {
$receiverType = $scope->getType($var->var);
$configFactoryType = new ObjectType(ConfigFactoryInterface::class);
if ($configFactoryType->isSuperTypeOf($receiverType)->yes()) {
return $this->extractFirstStringArg($var->getArgs());
}
return null;
}

// Pattern 4: $configFactory->getEditable('name')
if ($var instanceof MethodCall && $methodName === 'getEditable') {
$receiverType = $scope->getType($var->var);
$configFactoryType = new ObjectType(ConfigFactoryInterface::class);
if ($configFactoryType->isSuperTypeOf($receiverType)->yes()) {
return $this->extractFirstStringArg($var->getArgs());
}
return null;
}

return null;
}

/**
* @param \PhpParser\Node\Arg[] $args
*/
private function extractFirstStringArg(array $args): ?string
{
if (count($args) === 0) {
return null;
}

$argValue = $args[0]->value;
if ($argValue instanceof String_) {
return $argValue->value;
}

return null;
}
}
Loading
Loading