Enhanced static analysis for Yii2 applications with PHPStan
Precise type inference, dynamic method resolution, and comprehensive property reflection
composer require --dev yii2-extensions/phpstan:^0.5Create config/phpstan.php with an analysis-only description of your application:
<?php
declare(strict_types=1);
return [
'params' => ['adminEmail' => 'admin@example.com'],
'components' => [
'db' => ['class' => yii\db\Connection::class],
],
];Create phpstan.neon in the project root, choosing paths that exist in your application:
includes:
- vendor/yii2-extensions/phpstan/extension.neon
parameters:
level: 5
paths:
- models
yii2:
config_path: config/phpstan.phpRun PHPStan:
vendor/bin/phpstan analyseThe snippets below assume an app\models\Article model with id and title property tags and a published()
query scope. The usage guide describes the analysis configuration for the components, params and
behaviors shown here.
Yii supplies generic types for findOne(), findAll(), relations and the standard query methods. This extension
adds row shapes from model @property tags and precise getAttribute() types:
use app\models\Article;
$article = Article::findOne(1); // Article|null
$articles = Article::findAll(['title' => 'Yii']); // array<Article>
$query = Article::find()->where(['id' => 1]); // ArticleQuery&ActiveQuery<Article>
$published = Article::find()->published()->one(); // Article|null
$rows = Article::find()->asArray()->all(); // array<array{id: int, title: string}>
if ($article !== null) {
$title = $article->getAttribute('title'); // string
}A custom query without generic PHPDoc, such as one generated by Gii, is bound to the model at the find() call site.
Use static or $this scope return types to preserve that binding. Row shapes include inherited property tags;
relation and read-only keys are optional and need guards such as ??. See query limitations
when a query is reached through a typed parameter or a widened relation getter.
Yii::$app uses phpstan.application_type, which defaults to yii\web\Application. Component classes come from
the analysis configuration:
$view = Yii::$app->exampleView; // yii\web\View
$typed = Yii::$app->get('typedView'); // yii\web\View
$untyped = Yii::$app->get('untypedView'); // object
$request = Yii::$app->request; // yii\web\RequestClass names, class-key arrays, object instances and factories with a single class return type can provide precise
types. An untyped factory yields object; adding an accurate return type makes the result specific. Core components
configured without a class keep the application's annotated property type.
The built-in user: identityClass mapping also supplies the configured identity class to yii\web\User's generic
parameter. Its identity property remains nullable. See generic components
and the separate console application configuration.
Params values become an array shape, including nested arrays and keys containing dots or other punctuation:
$email = Yii::$app->params['adminEmail']; // string
$maxItems = Yii::$app->params['maxItems']; // int
$enabled = Yii::$app->params['nested']['enabled']; // boolScalars are widened to their types rather than retained as literal values. The same shape applies to inherited
module params, including $this->module->params in a controller. With no configured params, the type stays array.
The analysis-only behaviors map exposes members from attached behavior classes. In the example,
TaggedComponent is mapped to TagBehavior; the application must also attach that behavior at runtime:
use app\behaviors\TagBehavior;
use app\components\TaggedComponent;
$component = new TaggedComponent();
$component->attachBehavior('tag', new TagBehavior());
$label = $component->label; // string
$summary = $component->summarize(); // stringProperties and methods are resolved from the behavior's native members and PHPDoc. See behavior configuration.
Container service definitions and aliases are resolved by their IDs:
$view = Yii::$container->get('view.alias'); // yii\web\View
$shared = Yii::$container->get('sharedView'); // yii\web\ViewThe example config maps view.alias to view.real and gives sharedView a typed factory. An unresolved definition
keeps the result as object. Container factories, invocable objects and aliases have distinct rules; see
definition forms.
$headers = new yii\web\HeaderCollection();
$host = $headers->get('Host'); // string|null
$ids = $headers->get('X-Request-ID', [], false); // array<string>
$requestId = $headers->get('X-Request-ID', 'default'); // stringA non-null default removes null from the return type. The $first argument selects a single value or an array.
Logger and target message buffers have tuple types without local stubs or PHPDoc overrides:
foreach ((new yii\log\Logger())->messages as $message) {
$level = $message[1]; // int
$category = $message[2]; // string
$timestamp = $message[3]; // float
$memory = $message[5] ?? null; // int|null
}
$empty = yii\log\Target::filterMessages([]); // array{}Payload types follow the installed Yii version. Filtering preserves the input key and tuple types while allowing an empty result. See the logger examples.
A service locator resolves configured component IDs, while container-only IDs belong to the container:
$view = Yii::$app->get('exampleView'); // yii\web\View
$optional = Yii::$app->get('exampleView', false); // yii\web\View|null
$unknown = Yii::$app->get('view.alias'); // object; not a configured component IDPassing false or a non-constant boolean as $throwException makes a resolved component nullable. An object
fallback does not guarantee a successful runtime lookup: unknown component IDs can throw. Use
Yii::$container->get('view.alias') for the configured container alias.
- π Installation guide
- βοΈ Configuration reference
- π‘ Usage examples
- π Migration guide and upgrade entry point
- π€ AI assistance
- π§ͺ Testing guide