Skip to content

Commit 72833fa

Browse files
feat: add Yii::$app->params type inference from configuration for precise array shape typing. (#85)
1 parent 8caeb88 commit 72833fa

16 files changed

Lines changed: 601 additions & 9 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
- fix: update Rector command in `composer.json` to remove unnecessary 'src' argument.
1111
- feat: replace static stub files with dynamic stub generation for `Yii::$app` type inference, adding support for custom application types.
1212
- chore: remove `sync-metadata` script and `docs/development.md`, update documentation links.
13+
- feat: add `Yii::$app->params` type inference from configuration for precise array shape typing.
1314

1415
## 0.4.0 January 26, 2026
1516

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ Create a PHPStan-specific config file (`config/phpstan-config.php`).
7070
declare(strict_types=1);
7171

7272
return [
73+
// Application params: enables precise type inference for Yii::$app->params
74+
'params' => [
75+
'turnstile.siteKey' => '',
76+
'adminEmail' => 'admin@example.com',
77+
'maxItems' => 100,
78+
],
7379
// PHPStan only: used by this extension for behavior property/method type inference
7480
'behaviors' => [
7581
app\models\User::class => [
@@ -136,6 +142,25 @@ if (Yii::$app->user->isGuest === false) {
136142
}
137143
```
138144

145+
#### Application params
146+
147+
```php
148+
// Types are inferred from the values in your phpstan-config.php 'params' key
149+
150+
// ✅ Typed as array{'turnstile.siteKey': string, adminEmail: string, maxItems: int}
151+
$params = Yii::$app->params;
152+
153+
// ✅ Typed as string
154+
$email = Yii::$app->params['adminEmail'];
155+
156+
// ✅ Typed as int
157+
$maxItems = Yii::$app->params['maxItems'];
158+
159+
// ✅ Nested arrays are also supported
160+
// 'nested' => ['db' => ['host' => 'localhost', 'port' => 3306]]
161+
$host = Yii::$app->params['nested']['db']['host']; // string
162+
```
163+
139164
#### Behaviors
140165

141166
```php

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"ecs": "./vendor/bin/ecs --fix",
5353
"rector": "./vendor/bin/rector process",
5454
"static": "./vendor/bin/phpstan --memory-limit=-1",
55-
"tests": "./vendor/bin/phpunit"
55+
"tests": "php -d memory_limit=-1 ./vendor/bin/phpunit"
5656
},
5757
"repositories": [
5858
{

src/ServiceMap.php

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use yii\base\{BaseObject, InvalidArgumentException};
1313
use yii\web\Application;
1414

15+
use function array_key_exists;
1516
use function define;
1617
use function defined;
1718
use function file_exists;
@@ -54,7 +55,8 @@
5455
* container?: array{
5556
* definitions?: array<array-key, DefinitionType>,
5657
* singletons?: array<array-key, DefinitionType>,
57-
* }
58+
* },
59+
* params?: array<string, mixed>,
5860
* }
5961
*
6062
* @copyright Copyright (C) 2023 Terabytesoftw.
@@ -97,6 +99,13 @@ final class ServiceMap
9799
*/
98100
private array $componentsDefinitions = [];
99101

102+
/**
103+
* Application params for PHPStan type inference.
104+
*
105+
* @phpstan-var array<string, mixed>
106+
*/
107+
private array $params = [];
108+
100109
/**
101110
* Service definitions map for Yii Application analysis.
102111
*
@@ -133,6 +142,7 @@ public function __construct(string $configPath = '')
133142
$this->processBehaviors($config);
134143
$this->processComponents($config);
135144
$this->processDefinition($config);
145+
$this->processParams($config);
136146
$this->processSingletons($config);
137147
}
138148

@@ -237,6 +247,19 @@ public function getComponentDefinitionById(string $id): array
237247
return is_array($definition) ? $definition : [];
238248
}
239249

250+
/**
251+
* Retrieves the application params map for PHPStan type inference.
252+
*
253+
* Returns the `params` key-value pairs extracted from the Yii Application configuration file, enabling static
254+
* analysis tools to infer precise array shape types for `Yii::$app->params` access.
255+
*
256+
* @return array<string, mixed> Params key-value pairs from configuration.
257+
*/
258+
public function getParams(): array
259+
{
260+
return $this->params;
261+
}
262+
240263
/**
241264
* Retrieves the fully qualified class name of a Yii Service by its identifier.
242265
*
@@ -306,6 +329,10 @@ private function loadConfig(string $configPath): array
306329
$this->throwErrorWhenConfigFileIsNotArray($configPath, 'components');
307330
}
308331

332+
if (array_key_exists('params', $config) && is_array($config['params']) === false) {
333+
$this->throwErrorWhenConfigFileIsNotArray($configPath, 'params');
334+
}
335+
309336
if (isset($config['container'])) {
310337
if (is_array($config['container']) === false) {
311338
$this->throwErrorWhenConfigFileIsNotArray($configPath, 'container');
@@ -517,6 +544,23 @@ private function processDefinition(array $config): void
517544
}
518545
}
519546

547+
/**
548+
* Processes application params from the Yii Application configuration array.
549+
*
550+
* Extracts the `params` section and stores it for type inference of `Yii::$app->params` array access.
551+
*
552+
* @param array $config Yii Application configuration array containing params definitions.
553+
*
554+
* @phpstan-import-type ServiceType from ServiceMap
555+
* @phpstan-param ServiceType $config
556+
*/
557+
private function processParams(array $config): void
558+
{
559+
if ($config !== []) {
560+
$this->params = $config['params'] ?? [];
561+
}
562+
}
563+
520564
/**
521565
* Processes singleton service definitions from the Yii Application configuration array.
522566
*
@@ -584,7 +628,9 @@ private function throwErrorWhenConfigFileIsNotArray(string ...$args): never
584628
*/
585629
private function throwErrorWhenIsNotString(string ...$args): never
586630
{
587-
throw new RuntimeException(sprintf("'%s': '%s' must be a 'string', got '%s'.", ...$args));
631+
throw new RuntimeException(
632+
sprintf("'%s': '%s' must be a 'string', got '%s'.", ...$args),
633+
);
588634
}
589635

590636
/**
@@ -602,6 +648,8 @@ private function throwErrorWhenIsNotString(string ...$args): never
602648
*/
603649
private function throwErrorWhenUnsupportedDefinition(string $id): never
604650
{
605-
throw new RuntimeException(sprintf("Unsupported definition for '%s'.", $id));
651+
throw new RuntimeException(
652+
sprintf("Unsupported definition for '%s'.", $id),
653+
);
606654
}
607655
}

0 commit comments

Comments
 (0)