Skip to content

Commit e1a23e0

Browse files
fix: Update licenses, docs, configuration, and reflection/type logic. (#20)
1 parent 5f60a8e commit e1a23e0

27 files changed

+1571
-394
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## 0.2.1 Under development
44

5+
- Bug #20: Update licenses, docs, configuration, and reflection/type logic (@terabytesoftw)
6+
57
## 0.2.0 June 02, 2025
68

79
- Enh #12: Upgrade to `PHPStan` `2.1` (@glpzzz)

LICENSE

Lines changed: 0 additions & 16 deletions
This file was deleted.

LICENSE.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
Copyright © 2008 by Terabytesoftw (<https://github.com/terabytesoftw/>)
2+
All rights reserved.
3+
4+
Redistribution and use in source and binary forms, with or without modification,
5+
are permitted provided that the following conditions are met:
6+
7+
* Redistributions of source code must retain the above copyright notice, this
8+
list of conditions and the following disclaimer.
9+
* Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
* Neither the name of Yii Software nor the names of its contributors may be
13+
used to endorse or promote products derived from this software without
14+
specific prior written permission.
15+
16+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23+
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ parameters:
7373

7474
## Quality code
7575

76-
[![phpstan-level](https://img.shields.io/badge/PHPStan%20level-6-blue)](https://github.com/yii2-extensions/phpstan/actions/workflows/static.yml)
76+
[![phpstan-level](https://img.shields.io/badge/PHPStan%20level-9-blue)](https://github.com/yii2-extensions/phpstan/actions/workflows/static.yml)
7777
[![style-ci](https://github.styleci.io/repos/701347895/shield?branch=main)](https://github.styleci.io/repos/701347895?branch=main)
7878

7979
## Testing
@@ -86,7 +86,7 @@ parameters:
8686

8787
## License
8888

89-
The MIT License. Please see [License File](LICENSE) for more information.
89+
BSD-3-Clause license. Please see [License File](LICENSE.md) for more information.
9090

9191
## Fork
9292

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"yii2",
77
"phpstan"
88
],
9-
"license": "mit",
9+
"license": "BSD-3-Clause",
1010
"require": {
1111
"php": ">=8.1",
1212
"nikic/php-parser": "^4.1|^5.4.0",

phpstan.neon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ parameters:
99
- '#Calling PHPStan\\Reflection\\Annotations\\AnnotationsPropertiesClassReflectionExtension\:\:(has|get)Property\(\) is not covered.+#'
1010
- '#Creating new PHPStan\\Reflection\\Dummy\\DummyPropertyReflection is not covered.+#'
1111

12-
level: 6
12+
level: 9
1313

1414
paths:
1515
- src

phpunit.xml.dist

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<phpunit
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.2/phpunit.xsd"
5-
bootstrap="vendor/autoload.php"
5+
bootstrap="tests/bootstrap.php"
66
cacheDirectory=".phpunit.cache"
77
colors="true"
88
executionOrder="depends,defects"
@@ -20,5 +20,9 @@
2020
<include>
2121
<directory suffix=".php">./src</directory>
2222
</include>
23+
<exclude>
24+
<directory suffix=".php">./src/reflection</directory>
25+
<directory suffix=".php">./src/type</directory>
26+
</exclude>
2327
</source>
2428
</phpunit>

src/ServiceMap.php

Lines changed: 125 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,42 +5,80 @@
55
namespace yii2\extensions\phpstan;
66

77
use Closure;
8-
use InvalidArgumentException;
98
use PhpParser\Node;
109
use ReflectionException;
1110
use ReflectionFunction;
1211
use ReflectionNamedType;
1312
use RuntimeException;
14-
use yii\base\BaseObject;
13+
use yii\base\{BaseObject, InvalidArgumentException};
1514

1615
use function class_exists;
1716
use function define;
1817
use function defined;
18+
use function file_exists;
1919
use function get_class;
2020
use function is_array;
2121
use function is_object;
2222
use function is_string;
2323
use function is_subclass_of;
2424
use function sprintf;
2525

26+
/**
27+
* Provides service and component class resolution for Yii application analysis in PHPStan.
28+
*
29+
* Integrates Yii's dependency injection and component configuration with PHPStan's static analysis, enabling accurate
30+
* type inference, autocompletion, and service/component resolution for dynamic application services and components.
31+
*
32+
* This class parses the Yii application configuration to extract service and component definitions mapping service IDs
33+
* and component IDs to their corresponding class names.
34+
*
35+
* It supports both singleton and definition-based service registration, as well as component configuration via arrays
36+
* or instantiated objects.
37+
*
38+
* The implementation provides lookup methods for resolving the class name of a service or component by its ID, which
39+
* are used by PHPStan reflection extensions to enable static analysis and IDE support for dynamic properties and
40+
* dependency-injected services.
41+
*
42+
* Key features.
43+
* - Handles both array and object component configuration.
44+
* - Integrates with PHPStan reflection and type extensions for accurate analysis.
45+
* - Maps service and component IDs to their fully qualified class names.
46+
* - Parses Yii application config for service and component definitions.
47+
* - Provides lookup methods for service and component class resolution by ID.
48+
* - Supports singleton, definition, and closure-based service registration.
49+
* - Throws descriptive exceptions for invalid or unsupported definitions.
50+
*
51+
* @copyright Copyright (C) 2023 Terabytesoftw.
52+
* @license https://opensource.org/license/bsd-3-clause BSD 3-Clause License.
53+
*/
2654
final class ServiceMap
2755
{
2856
/**
29-
* @var string[]
57+
* Service definitions map for Yii application analysis.
58+
*
59+
* @phpstan-var string[]
3060
*/
3161
private array $services = [];
3262

3363
/**
34-
* @var array<string, string>
64+
* Component definitions map for Yii application analysis.
65+
*
66+
* @phpstan-var array<string, string>
3567
*/
3668
private array $components = [];
3769

3870
/**
39-
* @throws ReflectionException
71+
* Creates a new instance of the {@see ServiceMap} class.
72+
*
73+
* @param string $configPath Path to the Yii application configuration file.
74+
*
75+
* @throws InvalidArgumentException If the provided config path doesn't exist.
76+
* @throws ReflectionException If the service definitions can't be resolved or are invalid.
77+
* @throws RuntimeException If the provided configuration path doesn't exist or is invalid.
4078
*/
4179
public function __construct(string $configPath)
4280
{
43-
if (!file_exists($configPath)) {
81+
if (file_exists($configPath) === false) {
4482
throw new InvalidArgumentException(sprintf('Provided config path %s must exist', $configPath));
4583
}
4684

@@ -50,6 +88,7 @@ public function __construct(string $configPath)
5088
defined('YII_ENV_TEST') || define('YII_ENV_TEST', true);
5189

5290
$config = require $configPath;
91+
5392
foreach ($config['container']['singletons'] ?? [] as $id => $service) {
5493
$this->addServiceDefinition($id, $service);
5594
}
@@ -61,49 +100,108 @@ public function __construct(string $configPath)
61100
foreach ($config['components'] ?? [] as $id => $component) {
62101
if (is_object($component)) {
63102
$this->components[$id] = get_class($component);
103+
64104
continue;
65105
}
66106

67-
if (!is_array($component)) {
107+
if (is_array($component) === false) {
68108
throw new RuntimeException(
69109
sprintf('Invalid value for component with id %s. Expected object or array.', $id),
70110
);
71111
}
72112

73-
if (null !== $class = $component['class'] ?? null) {
74-
$this->components[$id] = $class;
113+
if (isset($component['class']) && is_string($component['class']) && $component['class'] !== '') {
114+
$this->components[$id] = $component['class'];
75115
}
76116
}
77117
}
78118

79-
public function getServiceClassFromNode(Node $node): ?string
119+
/**
120+
* Registers a service definition in the service map for Yii application analysis.
121+
*
122+
* Adds a service definition to the internal service map resolving the fully qualified class name for the specified
123+
* service ID.
124+
*
125+
* This method supports various service definition formats, including class names, arrays, closures, and integer
126+
* identifiers, enabling accurate type inference and autocompletion for dependency injected services in PHPStan
127+
* analysis.
128+
*
129+
* The method delegates the resolution of the service class to {@see guessServiceDefinition()} which determines the
130+
* appropriate class name based on the provided service definition.
131+
*
132+
* This ensures compatibility with Yii's flexible service registration mechanisms and supports both singleton and
133+
* definition-based services.
134+
*
135+
* @param string $id Service identifier to register in the service map.
136+
* @param array|Closure|int|string $service Service definition in supported format.
137+
*
138+
* @throws ReflectionException if the service definition is invalid or can't be resolved.
139+
*
140+
* @phpstan-param array<mixed>|Closure|string|int $service
141+
*/
142+
private function addServiceDefinition(string $id, array|string|Closure|int $service): void
80143
{
81-
if ($node instanceof Node\Scalar\String_ && isset($this->services[$node->value])) {
82-
return $this->services[$node->value];
83-
}
84-
85-
return null;
144+
$this->services[$id] = $this->guessServiceDefinition($id, $service);
86145
}
87146

88-
public function getComponentClassById(string $id): ?string
147+
/**
148+
* Retrieves the fully qualified class name of a Yii application component by its identifier.
149+
*
150+
* Looks up the component class name registered under the specified component ID in the internal component map.
151+
*
152+
* This method enables static analysis tools and IDEs to resolve the actual class type of dynamic application
153+
* components for accurate type inference, autocompletion, and property reflection.
154+
*
155+
* @param string $id Component identifier to look up in the component map.
156+
*
157+
* @return string|null Fully qualified class name of the component, or `null` if not found.
158+
*/
159+
public function getComponentClassById(string $id): string|null
89160
{
90161
return $this->components[$id] ?? null;
91162
}
92163

93164
/**
94-
* @throws ReflectionException
165+
* Resolves the fully qualified class name of a service from a PHP-Parser AST node.
95166
*
96-
* @phpstan-param array<mixed>|string|Closure|int $service
167+
* Inspects the provided AST node to determine if it represents a string service identifier, and if so, look up
168+
* the corresponding class name in the internal service map.
169+
*
170+
* This method enables static analysis tools and IDEs to infer the actual class type of services referenced by
171+
* string IDs in Yii application code supporting accurate type inference, autocompletion, and dependency injection
172+
* analysis.
173+
*
174+
* @param Node $node PHP-Parser AST node representing a service identifier.
175+
*
176+
* @return string|null Fully qualified class name of the service, or `null` if not found.
97177
*/
98-
private function addServiceDefinition(string $id, array|string|Closure|int $service): void
178+
public function getServiceClassFromNode(Node $node): ?string
99179
{
100-
$this->services[$id] = $this->guessServiceDefinition($id, $service);
180+
if ($node instanceof Node\Scalar\String_ && isset($this->services[$node->value])) {
181+
return $this->services[$node->value];
182+
}
183+
184+
return null;
101185
}
102186

103187
/**
104-
* @throws ReflectionException
188+
* Infers the fully qualified class name for a Yii service definition.
189+
*
190+
* Determines the class name associated with a service definition provided in various supported formats, including
191+
* class name strings, configuration arrays, closures, or integer identifiers.
105192
*
106-
* @phpstan-param array<mixed>|string|Closure|int $service
193+
* This method enables static analysis tools and IDEs to resolve the actual class type of dependency injected
194+
* services for accurate type inference, autocompletion, and service resolution in PHPStan analysis.
195+
*
196+
* @param string $id Service identifier being resolved.
197+
* @param array|Closure|int|string $service Service definition in supported format.
198+
*
199+
* @throws ReflectionException if the service definition is invalid or can't be resolved.
200+
* @throws RuntimeException if the service definition format is unsupported or missing required information.
201+
*
202+
* @return string Fully qualified class name of the resolved service.
203+
*
204+
* @phpstan-param array<mixed>|Closure|string|int $service
107205
*/
108206
private function guessServiceDefinition(string $id, array|string|Closure|int $service): string
109207
{
@@ -113,22 +211,23 @@ private function guessServiceDefinition(string $id, array|string|Closure|int $se
113211

114212
if ($service instanceof Closure || is_string($service)) {
115213
$returnType = (new ReflectionFunction($service))->getReturnType();
116-
if (!$returnType instanceof ReflectionNamedType) {
214+
215+
if ($returnType instanceof ReflectionNamedType === false) {
117216
throw new RuntimeException(sprintf('Please provide return type for %s service closure', $id));
118217
}
119218

120219
return $returnType->getName();
121220
}
122221

123-
if (!is_array($service)) {
222+
if (is_array($service) === false) {
124223
throw new RuntimeException(sprintf('Unsupported service definition for %s', $id));
125224
}
126225

127-
if (isset($service['class'])) {
226+
if (isset($service['class']) && is_string($service['class']) && $service['class'] !== '') {
128227
return $service['class'];
129228
}
130229

131-
if (isset($service[0]['class'])) {
230+
if (isset($service[0]['class']) && is_string($service[0]['class']) && $service[0]['class'] !== '') {
132231
return $service[0]['class'];
133232
}
134233

0 commit comments

Comments
 (0)