Skip to content

fix: Improve Yii2 integration component property handling. #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 5, 2025
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- Bug #22: Make `$configPath` optional in constructor `ServiceMap` class and update docs `README.md` (@terabytesoftw)
- Bug #23: Update the path to `Yii.php` in the `stubFiles` configuration for correct referencing (@terabytesoftw)
- Bug #24: Improve `Yii2` integration component property handling.

## 0.2.1 June 03, 2025

Expand Down
29 changes: 14 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,21 +126,7 @@ includes:
- vendor/yii2-extensions/phpstan/extension.neon

parameters:
level: 8

paths:
- src
- controllers
- models
- widgets

excludePaths:
- src/legacy
- tests/_support
- vendor

bootstrapFiles:
- config/bootstrap.php
- tests/bootstrap.php

# Complete dynamic constants list (extension defaults + custom)
Expand All @@ -151,7 +137,20 @@ parameters:
- YII_ENV_PROD
- YII_ENV_TEST
- APP_VERSION
- MAINTENANCE_MODE
- MAINTENANCE_MODE

level: 8

paths:
- src
- controllers
- models
- widgets

excludePaths:
- src/legacy
- tests/_support
- vendor

yii2:
config_path: %currentWorkingDirectory%/config/web.php
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"extra": {
"branch-alias": {
"dev-main": "0.1-dev"
"dev-main": "0.2.x-dev"
}
},
"config": {
Expand Down
4 changes: 0 additions & 4 deletions extension.neon
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@ parameters:
yii2:
config_path: ''

stubFiles:
- stubs/BaseYii.stub
- %currentWorkingDirectory%/vendor/yiisoft/yii2/Yii.php

parametersSchema:
yii2: structure(
[
Expand Down
9 changes: 3 additions & 6 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ includes:
- phar://phpstan.phar/conf/bleedingEdge.neon

parameters:
bootstrapFiles:
- tests/bootstrap.php

ignoreErrors:
- '#Calling PHPStan\\Reflection\\Annotations\\AnnotationsPropertiesClassReflectionExtension\:\:(has|get)Property\(\) is not covered.+#'
- '#Creating new PHPStan\\Reflection\\Dummy\\DummyPropertyReflection is not covered.+#'
Expand All @@ -13,9 +16,3 @@ parameters:

paths:
- src

scanFiles:
- vendor/yiisoft/yii2/Yii.php

yii2:
config_path: tests/fixture/yii-config-valid.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa
return new ComponentPropertyReflection(
new DummyPropertyReflection($propertyName),
new ObjectType($componentClass),
$classReflection,
);
}

Expand Down
29 changes: 19 additions & 10 deletions src/reflection/ComponentPropertyReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
use PHPStan\TrinaryLogic;
use PHPStan\Type\Type;

use function sprintf;

/**
* Property reflection wrapper for Yii application components in PHPStan analysis.
*
Expand Down Expand Up @@ -38,9 +40,14 @@ final class ComponentPropertyReflection implements PropertyReflection
* Creates a new instance of the {@see ComponentPropertyReflection} class.
*
* @param PropertyReflection $fallbackProperty Fallback property reflection instance for delegation.
* @param Type $type Actual type of the dynamic component as resolved by the service map.
* @param Type $type Type of the dynamic component as resolved by the service map or dependency injection.
* @param ClassReflection $declaringClass Class reflection of the class declaring the dynamic property.
*/
public function __construct(private readonly PropertyReflection $fallbackProperty, private readonly Type $type) {}
public function __construct(
private readonly PropertyReflection $fallbackProperty,
private readonly Type $type,
private readonly ClassReflection $declaringClass,
) {}

/**
* Determines whether the type of the dynamic Yii application component property can change after assignment.
Expand Down Expand Up @@ -71,7 +78,7 @@ public function canChangeTypeAfterAssignment(): bool
*/
public function getDeclaringClass(): ClassReflection
{
return $this->fallbackProperty->getDeclaringClass();
return $this->declaringClass;
}

/**
Expand Down Expand Up @@ -99,11 +106,13 @@ public function getDeprecatedDescription(): string|null
* This method allows static analysis tools and IDEs to display inline documentation for dynamic application
* components, supporting accurate code completion, type checking, and developer guidance.
*
* @return string|null PHPDoc comment string if available, or `null` if no documentation is set.
* @return string PHPDoc comment string for the property, or an empty string if no comment is set.
*/
public function getDocComment(): string|null
public function getDocComment(): string
{
return $this->fallbackProperty->getDocComment();
$componentTypeName = $this->type->describe(\PHPStan\Type\VerbosityLevel::typeOnly());

return sprintf("/**\n * @var %s\n */", $componentTypeName);
}

/**
Expand All @@ -119,7 +128,7 @@ public function getDocComment(): string|null
*/
public function getReadableType(): Type
{
return $this->fallbackProperty->getReadableType();
return $this->type;
}

/**
Expand Down Expand Up @@ -151,7 +160,7 @@ public function getType(): Type
*/
public function getWritableType(): Type
{
return $this->fallbackProperty->getWritableType();
return $this->type;
}

/**
Expand All @@ -167,7 +176,7 @@ public function getWritableType(): Type
*/
public function isDeprecated(): TrinaryLogic
{
return $this->fallbackProperty->isDeprecated();
return TrinaryLogic::createNo();
}

/**
Expand All @@ -183,7 +192,7 @@ public function isDeprecated(): TrinaryLogic
*/
public function isInternal(): TrinaryLogic
{
return $this->fallbackProperty->isInternal();
return TrinaryLogic::createNo();
}

/**
Expand Down
27 changes: 22 additions & 5 deletions src/reflection/UserPropertiesClassReflectionExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@
};
use PHPStan\Reflection\Annotations\AnnotationsPropertiesClassReflectionExtension;
use PHPStan\Reflection\Dummy\DummyPropertyReflection;
use PHPStan\Type\MixedType;
use PHPStan\Type\ObjectType;
use yii\web\User;
use yii2\extensions\phpstan\ServiceMap;

/**
* Provides property reflection for a Yii user component in PHPStan analysis.
Expand Down Expand Up @@ -49,7 +50,10 @@ final class UserPropertiesClassReflectionExtension implements PropertiesClassRef
* @param AnnotationsPropertiesClassReflectionExtension $annotationsProperties Extension for handling
* annotation-based properties.
*/
public function __construct(private readonly AnnotationsPropertiesClassReflectionExtension $annotationsProperties) {}
public function __construct(
private readonly AnnotationsPropertiesClassReflectionExtension $annotationsProperties,
private readonly ServiceMap $serviceMap,
) {}

/**
* Retrieves the property reflection for a given property on the Yii user component.
Expand All @@ -69,8 +73,15 @@ public function __construct(private readonly AnnotationsPropertiesClassReflectio
*/
public function getProperty(ClassReflection $classReflection, string $propertyName): PropertyReflection
{
if ($propertyName === 'identity') {
return new ComponentPropertyReflection(new DummyPropertyReflection($propertyName), new MixedType());
if (
$propertyName === 'identity' &&
($componentClass = $this->serviceMap->getComponentClassById($propertyName)) !== null
) {
return new ComponentPropertyReflection(
new DummyPropertyReflection($propertyName),
new ObjectType($componentClass),
$classReflection,
);
}

if ($classReflection->hasNativeProperty($propertyName)) {
Expand All @@ -96,10 +107,16 @@ public function getProperty(ClassReflection $classReflection, string $propertyNa
*/
public function hasProperty(ClassReflection $classReflection, string $propertyName): bool
{
if ($classReflection->getName() !== User::class) {
if (
$classReflection->getName() !== User::class &&
$classReflection->isSubclassOf(User::class) === false) {
return false;
}

if ($propertyName === 'identity' && $this->serviceMap->getComponentClassById($propertyName) !== null) {
return true;
}

return $classReflection->hasNativeProperty($propertyName)
|| $this->annotationsProperties->hasProperty($classReflection, $propertyName);
}
Expand Down
15 changes: 0 additions & 15 deletions stubs/BaseYii.stub

This file was deleted.

4 changes: 2 additions & 2 deletions tests/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
define('YII_ENV', 'test');

// require composer autoloader if available
require(__DIR__ . '/../vendor/autoload.php');
require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php');
require(dirname(__DIR__) . '/vendor/autoload.php');
require(dirname(__DIR__) . '/vendor/yiisoft/yii2/Yii.php');