VOOZH about

URL: https://deepwiki.com/MahoCommerce/maho-phpstan-plugin/5.2-template-scope-resolution

⇱ Template Scope Resolution | MahoCommerce/maho-phpstan-plugin | DeepWiki


Loading...
Menu

Template Scope Resolution

Purpose and Scope

This document describes the BindThisScopeResolverExtension system, which solves PHPStan analysis problems in Maho/Magento .phtml template files and data install scripts where $this references need access to protected or private methods and properties. The extension transforms PHPDoc annotations to provide proper type information while bypassing visibility restrictions during static analysis.

For information about magic method support on Varien_Object instances, see Magic Method Support. For details on the underlying visibility override mechanism, see Visibility Override System.


The Problem: Template Scope Access

In Maho/Magento codebases, .phtml template files and data install scripts commonly use $this to access methods and properties that are declared as protected or private in the rendering class. This is a legitimate pattern in the framework, but creates issues for static analysis:

ContextExample CodeIssue
.phtml Templates$this->getChildHtml('content') where getChildHtml() is protectedPHPStan reports access violation
Data Install Scripts$this->_conn->query(...) where _conn is protectedPHPStan cannot verify property access
Block Templates$this->formatPrice($price) where formatPrice() is protectedType information unavailable

Developers typically annotate these files with @var comments to provide type hints:


However, PHPStan still enforces visibility rules, causing analysis failures on otherwise valid code.

Sources: src/PhpDoc/BindThisScopeResolverExtension.php1-90


Solution Architecture

The BindThisScopeResolverExtension implements a two-phase solution that intercepts and transforms PHPDoc annotations, then provides a custom type that reports all members as public during analysis.

High-Level Flow


Sources: src/PhpDoc/BindThisScopeResolverExtension.php25-88


Component Integration

The extension integrates with PHPStan through two distinct extension points:


Sources: src/PhpDoc/BindThisScopeResolverExtension.php25 src/Reflection/PublicMethodReflection.php23 src/Reflection/PublicPropertyReflection.php19


Phase 1: PHPDoc Transformation

The first phase occurs during AST traversal, implemented via the NodeVisitorAbstract interface. The beforeTraverse() method scans for specific PHPDoc patterns and rewrites them.

Pattern Matching and Replacement

The extension defines two constants for the transformation:

ConstantValuePurpose
PHPDOC_PATTERN/@var\s+((\\\?\w+)+)\s+\$this/Matches @var Type $this annotations
PHPDOC_REPLACE@var bind-this-scope<$1> $thisReplacement template using captured type

src/PhpDoc/BindThisScopeResolverExtension.php28-29

Transformation Process


The implementation in src/PhpDoc/BindThisScopeResolverExtension.php31-51 performs the following:

  1. Namespace handling: If the node is a Node\Stmt\Namespace_, recursively process its statements lines 34-37
  2. DocComment check: Skip nodes without doc comments lines 39-41
  3. Pattern replacement: Use preg_replace() with a count parameter to detect matches line 43
  4. AST modification: If a match is found, create a new Comment\Doc object and update the node lines 45-46
  5. Early termination: Break after first match to avoid multiple transformations line 47

Example transformation:


Sources: src/PhpDoc/BindThisScopeResolverExtension.php31-51


Phase 2: Custom Generic Type Resolution

After PHPDoc transformation, PHPStan's type resolution system encounters the custom bind-this-scope<T> generic type. The extension implements TypeNodeResolverExtension to handle this.

Type Node Processing


The implementation in src/PhpDoc/BindThisScopeResolverExtension.php53-88 validates the type node structure:

  1. Generic type check: Return null if not a GenericTypeNode lines 55-57
  2. Type name validation: Confirm the identifier is bind-this-scope lines 59-62
  3. Argument count: Ensure exactly one generic type argument lines 64-68
  4. Name resolution: Convert the type argument to a fully qualified class name using NameScope line 70

Sources: src/PhpDoc/BindThisScopeResolverExtension.php53-70


Anonymous ObjectType Subclass

The core of the solution is an anonymous class that extends ObjectType and overrides member access methods to return visibility-wrapped reflections.

Class Structure


Method Override Implementation

The anonymous class definition appears in src/PhpDoc/BindThisScopeResolverExtension.php72-87:


Member Access Flow


Sources: src/PhpDoc/BindThisScopeResolverExtension.php72-87


Visibility Override Mechanism

The PublicMethodReflection and PublicPropertyReflection wrappers are decorator classes that intercept visibility checks while delegating all other operations to the original reflection objects.

PublicMethodReflection Key Methods

From src/Reflection/PublicMethodReflection.php23-175:

MethodImplementationPurpose
isPublic()return true;Always reports public visibility
isPrivate()return false;Never reports private visibility
All other methodsDelegate to $this->originalMethodPreserve all type information and metadata

PublicPropertyReflection Key Methods

From src/Reflection/PublicPropertyReflection.php19-167:

MethodImplementationPurpose
isPublic()return true;Always reports public visibility
isPrivate()return false;Never reports private visibility
isPrivateSet()return false;Allow property assignment
isProtectedSet()return false;Allow property assignment
All other methodsDelegate to $this->originalPropertyPreserve all type information

Delegation Pattern

Both wrappers use the same pattern for delegation:


Sources: src/Reflection/PublicMethodReflection.php39-46 src/Reflection/PublicPropertyReflection.php35-42


Complete Processing Pipeline

The following diagram illustrates the complete flow from source code to analysis result:


Sources: src/PhpDoc/BindThisScopeResolverExtension.php1-90 src/Reflection/PublicMethodReflection.php1-176 src/Reflection/PublicPropertyReflection.php1-168


Configuration and Registration

The extension is registered in extension.neon without any configuration parameters:


The dual tags enable both phases of operation:

  • phpstan.parser.richParserNodeVisitor: Enables AST transformation during parsing
  • phpstan.typeNodeResolverExtension: Enables custom type resolution

Sources: Configuration from extension.neon (referenced in architectural diagrams)


Use Cases and Examples

Use Case 1: Template Block Methods

Scenario: A .phtml template needs to call protected methods on the block instance.


Resolution:

  1. Annotation transformed to @var bind-this-scope<Mage_Catalog_Block_Product_View> $this
  2. Type resolves to anonymous ObjectType subclass
  3. getProductName() access wrapped in PublicMethodReflection
  4. PHPStan sees method as public, analysis succeeds

Use Case 2: Data Install Scripts

Scenario: Install script accesses protected connection property.


Resolution:

  1. Annotation transformed to @var bind-this-scope<Mage_Sales_Model_Resource_Setup> $this
  2. Property access wrapped in PublicPropertyReflection
  3. PHPStan allows property access, maintains type information

Use Case 3: Nested Template Includes

Scenario: Parent template passes $this to child template.


Resolution: The type information propagates through method calls, maintaining the bind-this-scope wrapper throughout the analysis scope.

Sources: Use cases derived from architectural understanding in src/PhpDoc/BindThisScopeResolverExtension.php1-90


Implementation Details

Constants and Configuration

ConstantValueLocation
GENERIC_TYPE'bind-this-scope'src/PhpDoc/BindThisScopeResolverExtension.php27
PHPDOC_PATTERN'/@var\s+((\\\?\w+)+)\s+\$this/'src/PhpDoc/BindThisScopeResolverExtension.php28
PHPDOC_REPLACE'@var bind-this-scope<$1> $this'src/PhpDoc/BindThisScopeResolverExtension.php29

Key Methods

MethodClassReturn TypePurpose
beforeTraverse()BindThisScopeResolverExtensionnullTransform PHPDoc annotations in AST
resolve()BindThisScopeResolverExtension?TypeCreate custom type for bind-this-scope<T>
getMethod()Anonymous ObjectTypeExtendedMethodReflectionReturn public method wrapper
getProperty()Anonymous ObjectTypeExtendedPropertyReflectionReturn public property wrapper

Sources: src/PhpDoc/BindThisScopeResolverExtension.php31-88


Limitations and Considerations

Scope of Transformation

The extension only transforms the first matching @var Type $this annotation in a file src/PhpDoc/BindThisScopeResolverExtension.php43-48 This is by design:

  • Templates typically have one primary $this type
  • Multiple transformations could cause confusion
  • The break statement after first match prevents over-processing

Type Safety Trade-offs

The system intentionally bypasses visibility checks, which means:

  • Benefit: PHPStan can analyze legitimate Magento template patterns
  • Trade-off: Actual visibility violations won't be detected in template contexts
  • Mitigation: The transformation only applies where developers explicitly use @var Type $this

PHPStan API Stability

Both PublicMethodReflection and PublicPropertyReflection implement internal PHPStan interfaces (ExtendedMethodReflection, ExtendedPropertyReflection) that are not covered by backward compatibility promises:

The @phpstan-ignore phpstanApi.interface annotations acknowledge this trade-off.

Sources: src/PhpDoc/BindThisScopeResolverExtension.php43-48 src/Reflection/PublicMethodReflection.php17-21 src/Reflection/PublicPropertyReflection.php12-17


Related Systems

This system integrates closely with:

Sources: References to related documentation sections

Refresh this wiki

On this page