Skip to content

Property naming assumptions may lead to error from StdlibAbstractOptionsPropertiesClassReflectionExtension #78

@InvisibleSmiley

Description

@InvisibleSmiley

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} 

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions