Property naming assumptions may lead to error from StdlibAbstractOptionsPropertiesClassReflectionExtension
Letting PHPStan (2.x) analyze the following code/file leads to an internal error.
This is because PHPStan Laminas Framework assumes that properties in classes extending AbstractOptions are named a certain way, which is invalid because AbstractOptions only imposes naming conventions on property getters and setters but not underlying properties (which AbstractOptions does not know/access directly).
IMPORTANT NOTE
PHPStan Laminas Framework may check obvious candidates (queried property name, queried property name without underscores) first but since properties may be named "randomly", it must be prepared for those checks to fail and then provide some fallback handling.
Workaround: Rename properties
Code/file:
declare(strict_types=1);
use Laminas\Stdlib\AbstractOptions;
require __DIR__ . '/../vendor/autoload.php';
/**
* @extends AbstractOptions<mixed>
*/
class FooOptions extends AbstractOptions
{
private string $fooBar = '';
/** @param iterable<string, mixed> $options */
public function __construct($options)
{
parent::__construct($options);
if ($this->foo_bar === '') {
throw new InvalidArgumentException('Missing option foo_bar');
}
echo $this->foo_bar;
}
public function getFooBar(): string
{
return $this->fooBar;
}
public function setFooBar(string $fooBar): void
{
$this->fooBar = $fooBar;
}
}
$ok = new FooOptions(['foo_bar' => 'baz']);
$broken = new FooOptions([]);
Error:
Internal error: Property $foo_bar was not found in reflection of class FooOptions. while analysing file
/path/to/src/FooOptions.php
Stack trace:
## phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Reflection/ClassReflection.php(587)
#0
/path/to/vendor/slam/phpstan-laminas-framework/src/Type/Laminas/StdlibAbstractOptionsPropertiesClassReflectionExtension.php(72):
PHPStan\Reflection\ClassReflection->getNativeProperty('foo_bar')
#1 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Reflection/WrappedExtendedPropertyReflection.php(38):
PHPStan\Reflection\PropertyReflection@anonymous->getReadableType()
#2 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php(56):
PHPStan\Reflection\WrappedExtendedPropertyReflection->getReadableType()
#3 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Reflection/Type/CallbackUnresolvedPropertyPrototypeReflection.php(48):
PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection->transformPropertyWithStaticType(Object(PHPStan\Reflection\ClassReflection),
Object(PHPStan\Reflection\WrappedExtendedPropertyReflection))
#4 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Type/StaticType.php(170):
PHPStan\Reflection\Type\CallbackUnresolvedPropertyPrototypeReflection->getTransformedProperty()
#5 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(3860):
PHPStan\Type\StaticType->getProperty('foo_bar', Object(PHPStan\Analyser\MutatingScope))
#6 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(3867):
PHPStan\Analyser\MutatingScope->getPropertyReflection(Object(PHPStan\Type\ThisType), 'foo_bar')
#7 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(1560):
PHPStan\Analyser\MutatingScope->propertyFetchType(Object(PHPStan\Type\ThisType), 'foo_bar', Object(PhpParser\Node\Expr\PropertyFetch))
#8 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(1566):
PHPStan\Analyser\MutatingScope->PHPStan\Analyser\{closure}()
#9 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(546):
PHPStan\Analyser\MutatingScope->resolveType('$this->foo_bar', Object(PhpParser\Node\Expr\PropertyFetch))
#10 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/RicherScopeGetTypeHelper.php(29):
PHPStan\Analyser\MutatingScope->getType(Object(PhpParser\Node\Expr\PropertyFetch))
#11 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(703):
PHPStan\Analyser\RicherScopeGetTypeHelper->getIdenticalResult(Object(PHPStan\Analyser\MutatingScope), Object(PhpParser\Node\Expr\BinaryOp\Identical))
#12 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/MutatingScope.php(546):
PHPStan\Analyser\MutatingScope->resolveType('$this->foo_bar ...', Object(PhpParser\Node\Expr\BinaryOp\Identical))
#13 /path/to/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanRuleHelper.php(24):
PHPStan\Analyser\MutatingScope->getType(Object(PhpParser\Node\Expr\BinaryOp\Identical))
#14 /path/to/vendor/phpstan/phpstan-strict-rules/src/Rules/BooleansInConditions/BooleanInIfConditionRule.php(33):
PHPStan\Rules\BooleansInConditions\BooleanRuleHelper->passesAsBoolean(Object(PHPStan\Analyser\MutatingScope), Object(PhpParser\Node\Expr\BinaryOp\Identical))
#15 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnalyser.php(103):
PHPStan\Rules\BooleansInConditions\BooleanInIfConditionRule->processNode(Object(PhpParser\Node\Stmt\If_), Object(PHPStan\Analyser\MutatingScope))
#16 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Node/ClassStatementsGatherer.php(116):
PHPStan\Analyser\FileAnalyser->PHPStan\Analyser\{closure}(Object(PhpParser\Node\Stmt\If_), Object(PHPStan\Analyser\MutatingScope))
#17 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(647):
PHPStan\Node\ClassStatementsGatherer->__invoke(Object(PhpParser\Node\Stmt\If_), Object(PHPStan\Analyser\MutatingScope))
#18 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(520):
PHPStan\Analyser\NodeScopeResolver::PHPStan\Analyser\{closure}(Object(PhpParser\Node\Stmt\If_), Object(PHPStan\Analyser\MutatingScope))
#19 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(450):
PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\If_), Object(PHPStan\Analyser\MutatingScope), Object(Closure),
Object(PHPStan\Analyser\StatementContext))
#20 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(646):
PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\ClassMethod), Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure),
Object(PHPStan\Analyser\StatementContext))
#21 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(450):
PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\ClassMethod), Object(PHPStan\Analyser\MutatingScope),
Object(PHPStan\Node\ClassStatementsGatherer), Object(PHPStan\Analyser\StatementContext))
#22 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(786):
PHPStan\Analyser\NodeScopeResolver->processStmtNodes(Object(PhpParser\Node\Stmt\Class_), Array, Object(PHPStan\Analyser\MutatingScope),
Object(PHPStan\Node\ClassStatementsGatherer), Object(PHPStan\Analyser\StatementContext))
#23 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/NodeScopeResolver.php(398):
PHPStan\Analyser\NodeScopeResolver->processStmtNode(Object(PhpParser\Node\Stmt\Class_), Object(PHPStan\Analyser\MutatingScope), Object(Closure),
Object(PHPStan\Analyser\StatementContext))
#24 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Analyser/FileAnalyser.php(162):
PHPStan\Analyser\NodeScopeResolver->processNodes(Array, Object(PHPStan\Analyser\MutatingScope), Object(Closure))
#25 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Command/WorkerCommand.php(136):
PHPStan\Analyser\FileAnalyser->analyseFile('/path/to/...', Array, Object(PHPStan\Rules\LazyRegistry), Object(PHPStan\Collectors\Registry), NULL)
#26 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/EventEmitterTrait.php(111):
PHPStan\Command\WorkerCommand::PHPStan\Command\{closure}(Array)
#27 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/clue/ndjson-react/src/Decoder.php(117):
_PHPStan_2f712479f\Evenement\EventEmitter->emit('data', Array)
#28 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/EventEmitterTrait.php(111):
_PHPStan_2f712479f\Clue\React\NDJson\Decoder->handleData(Array)
#29 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/react/stream/src/Util.php(62):
_PHPStan_2f712479f\Evenement\EventEmitter->emit('data', Array)
#30 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/evenement/evenement/src/EventEmitterTrait.php(111):
_PHPStan_2f712479f\React\Stream\Util::_PHPStan_2f712479f\React\Stream\{closure}('{"action":"anal...')
#31 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/react/stream/src/DuplexResourceStream.php(168):
_PHPStan_2f712479f\Evenement\EventEmitter->emit('data', Array)
#32 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/react/event-loop/src/StreamSelectLoop.php(201):
_PHPStan_2f712479f\React\Stream\DuplexResourceStream->handleData(Resource id #5772)
#33 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/react/event-loop/src/StreamSelectLoop.php(173):
_PHPStan_2f712479f\React\EventLoop\StreamSelectLoop->waitForStreamActivity(NULL)
#34 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/src/Command/WorkerCommand.php(96):
_PHPStan_2f712479f\React\EventLoop\StreamSelectLoop->run()
#35 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Command/Command.php(259):
PHPStan\Command\WorkerCommand->execute(Object(_PHPStan_2f712479f\Symfony\Component\Console\Input\ArgvInput),
Object(_PHPStan_2f712479f\Symfony\Component\Console\Output\ConsoleOutput))
#36 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(870):
_PHPStan_2f712479f\Symfony\Component\Console\Command\Command->run(Object(_PHPStan_2f712479f\Symfony\Component\Console\Input\ArgvInput),
Object(_PHPStan_2f712479f\Symfony\Component\Console\Output\ConsoleOutput))
#37 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(261):
_PHPStan_2f712479f\Symfony\Component\Console\Application->doRunCommand(Object(PHPStan\Command\WorkerCommand),
Object(_PHPStan_2f712479f\Symfony\Component\Console\Input\ArgvInput), Object(_PHPStan_2f712479f\Symfony\Component\Console\Output\ConsoleOutput))
#38 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/vendor/symfony/console/Application.php(157):
_PHPStan_2f712479f\Symfony\Component\Console\Application->doRun(Object(_PHPStan_2f712479f\Symfony\Component\Console\Input\ArgvInput),
Object(_PHPStan_2f712479f\Symfony\Component\Console\Output\ConsoleOutput))
#39 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(114):
_PHPStan_2f712479f\Symfony\Component\Console\Application->run()
#40 phar:///path/to/vendor/phpstan/phpstan/phpstan.phar/bin/phpstan(115): _PHPStan_2f712479f\{closure}()
#41 /path/to/vendor/phpstan/phpstan/phpstan(8): require('phar:///path/to/...')
#42 /path/to/vendor/bin/phpstan(119): include('/path/to/...')
#43 {main}