1. Go to this page and download the library: Download mspirkov/yii2-phpstan-rules library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
mspirkov / yii2-phpstan-rules example snippets
/**
* @property string $email
* @property-read string $fullName
*/
final class ContactModel extends Model
{
public $name;
public function getPhone(): string { /* ... */ }
public function setPhone(string $phone): void { /* ... */ }
}
/** @var ContactModel $model */
$form = ActiveForm::begin();
echo $form->field($model, 'name'); // ✓ declared property
echo $form->field($model, 'email'); // ✓ declared via @property
echo $form->field($model, 'phone'); // ✓ has both getPhone() and setPhone()
echo $form->field($model, 'fullName'); // ✗ read-only — declared via @property-read, nothing to write the submitted value back to
echo $form->field($model, 'nickname'); // ✗ typo — "nickname" is not a property on ContactModel
ActiveForm::end();
/**
* @property-read Address $address
*/
class Customer extends ActiveRecord
{
/** @return ActiveQuery<Order> */
public function getOrders()
{
return $this->hasMany(Order::class, ['customer_id' => 'id']);
}
public function getAddress()
{
return $this->hasOne(Address::class, ['id' => 'address_id']);
}
}
class Order extends ActiveRecord
{
/** @return ActiveQuery<Item> */
public function getItems()
{
return $this->hasMany(Item::class, ['order_id' => 'id']);
}
}
class Address extends ActiveRecord
{
/** @return ActiveQuery<Country> */
public function getCountry()
{
return $this->hasOne(Country::class, ['id' => 'country_id']);
}
}
class Item extends ActiveRecord { /* ... */ }
class Country extends ActiveRecord { /* ... */ }
Customer::find()->with('orders')->all(); // ✓
Customer::find()->with('orders.items')->all(); // ✓ Order declares its own "items" relation
Customer::find()->with('address.country')->all(); // ✓ related model resolved via @property-read
Customer::find()->joinWith('orders o')->all(); // ✓ alias is stripped before the relation is checked
Customer::find()->with('oders')->all(); // ✗ typo — no such relation on Customer
Customer::find()->with('orders.oops')->all(); // ✗ typo — no such relation on Order
/**
* @property int $id
* @property int $status
* @property string $updated_at
*/
final class Customer extends ActiveRecord { /* ... */ }
Customer::findOne(1); // ✓ primary key lookup, not a condition hash
Customer::findOne(['status' => 1]); // ✓
Customer::findOne(['status' => [1, 2]]); // ✓ IN (1, 2)
Customer::findOne(['updated_at' => new Expression('NOW()')]); // ✓ raw SQL, not type-checked
Customer::findOne(['statuss' => 1]); // ✗ typo — unknown attribute
Customer::findOne(['status' => '1']); // ✗ wrong type — int expected
Customer::deleteAll(['statuss' => 1]); // ✗ typo — unknown attribute
Customer::updateAll(['status' => 1], ['idd' => 5]); // ✗ typo — unknown attribute in the condition
/**
* @property int $id
* @property int $customer_id
* @property int $shipping_address_id
*/
final class Order extends ActiveRecord
{
public function getShippingAddress(): ActiveQuery
{
// ✗ missing property "uuid" on Address
return $this->hasOne(Address::class, ['uuid' => 'shipping_address_id']);
}
public function getItems(): ActiveQuery
{
// ✗ missing property "order_uuid" on Order
return $this->hasMany(OrderItem::class, ['order_id' => 'order_uuid']);
}
public function getCustomer(): ActiveQuery
{
// ✓
return $this->hasOne(Customer::class, ['id' => 'customer_id']);
}
}
/**
* @property int $id
*/
final class Customer extends ActiveRecord { /* ... */ }
/**
* @property int $id
*/
final class Address extends ActiveRecord { /* ... */ }
/**
* @property int $id
* @property int $order_id
*/
final class OrderItem extends ActiveRecord { /* ... */ }
/**
* @property int $id
* @property int $status
* @property int $age
* @property string $updated_at
*/
final class Customer extends ActiveRecord { /* ... */ }
Customer::updateAll(['status' => 1], ['id' => 5]); // ✓
Customer::updateAll(['updated_at' => new Expression('NOW()')]); // ✓ raw SQL, not type-checked
Customer::updateAll(['statuss' => 1]); // ✗ typo — unknown attribute
Customer::updateAll(['status' => 'active']); // ✗ wrong type — int expected
Customer::updateAllCounters(['age' => 1]); // ✓
Customer::updateAllCounters(['agee' => 1]); // ✗ typo — unknown attribute
$countQuery = Article::find()->where(['status' => 1]);
new Pagination(['totalCount' => $countQuery->count()]); // ✓
new Pagination(['totalCoutn' => 100]); // ✗ typo — unknown option "totalCoutn"
new Pagination(['defaultPageSize' => '20']); // ✗ wrong type — int expected
/**
* @property string $email
*/
final class ContactModel extends Model
{
public $name;
}
/** @var ContactModel $model */
echo Html::activeLabel($model, 'name'); // ✓ declared property
echo Html::activeInput('text', $model, 'email'); // ✓ declared via @property
echo Html::activeTextInput($model, 'nema'); // ✗ typo — "nema" is not a property on ContactModel
echo Html::activeHint($model, 'nickname'); // ✗ typo — "nickname" is not a property on ContactModel
/**
* @property string $email
*/
final class ContactModel extends Model
{
public $name;
public function attributeHints(): array
{
return [
'name' => 'Your full name',
'emial' => 'We will reply here', // ✗ typo — "emial" is not a property on ContactModel
'email' => 'We will reply here', // ✓ declared via @property
];
}
}
/**
* @property string $email
*/
final class ContactModel extends Model
{
public $name;
public function attributeLabels(): array
{
return [
'name' => 'Name',
'emial' => 'E-mail', // ✗ typo — "emial" is not a property on ContactModel
'email' => 'E-mail', // ✓ declared via @property
];
}
}
/**
* @property string $email
*/
final class ContactModel extends Model
{
public $name;
public function rules(): array
{
return [
['name', ' }
}
final class ContactModel extends Model
{
public $name;
public $email;
public function scenarios(): array
{
return [
'create' => ['name', 'email'],
'update' => ['name', '!emial'], // ✗ typo — "emial" is not a property on ContactModel
'' => ['name'], // ✗ empty scenario name
'delete' => 'name', // ✗ must be an array of attribute names
];
}
}
final class UploadForm extends Model
{
public $imageFile;
public $imageFiles;
public function rules(): array
{
return [[['imageFile', 'imageFiles'], 'file']];
}
}
/** @var UploadForm $model */
$model->imageFile = UploadedFile::getInstance($model, 'imageFile'); // ✓
$model->imageFiles = UploadedFile::getInstances($model, 'imageFiles'); // ✓
$model->imageFile = UploadedFile::getInstance($model, 'imagefile'); // ✗ typo — "imagefile" is not a property on UploadForm
$files = UploadedFile::getInstances($model, 'imagefiles'); // ✗ typo — "imagefiles" is not a property on UploadForm
// ✗ flagged: 4 `if` statements against a default limit of 3
public function actionCheckout(): string
{
if ($this->cart->isEmpty()) { /* ... */ }
if (!$this->cart->hasPaymentMethod()) { /* ... */ }
if ($this->cart->hasOutOfStockItems()) { /* ... */ }
if ($this->cart->hasExpiredCoupon()) { /* ... */ }
return $this->render('checkout', ['cart' => $this->cart]);
}
// ✓ the decision tree moves to a service, the action just orchestrates
public function actionCheckout(): string
{
return $this->render('checkout', $this->checkoutService->process($this->cart));
}
// ✗ flagged: bypasses the action-resolution pipeline (filters, events, results)
public function actionEdit(int $id): Response
{
return $this->actionView($id);
}
// ✓ redirect, or extract the shared part into a private method / service
public function actionEdit(int $id): Response
{
return $this->redirect(['view', 'id' => $id]);
}
// ✗ flagged in a view: queries the database instead of just rendering data
foreach (Post::find()->where(['status' => 1])->all() as $post):
// ✗ flagged, with the fix suggested in the error message
$id = $_GET['id'];
// ✓ read through the injected yii\web\Request instead
$id = $this->request->get('id');
// ✗ flagged: string-built condition, one step from SQL injection
$query->where("status = $status");
$query->andWhere('status = ' . $status);
$query->orWhere("id IN ($ids)");
// ✓ array condition syntax — parameterized, and PHPStan can see the shape
$query->where(['status' => $status]);
$query->andWhere(['in', 'id', $ids]);
// ✗ arbitrary component access
$cache = Yii::$app->cache;
// ✓ inject the component instead
public function __construct(private CacheInterface $cache) {}
// ✗ flagged: fetches a full row just to test whether one is there
public function emailIsTaken(string $email): bool
{
return User::find()->where(['email' => $email])->one() !== null;
}
// ✗ flagged: counts every matching row just to test whether one is there
public function emailIsAvailable(string $email): bool
{
return User::find()->where(['email' => $email])->count() < 1;
}
// ✓ exists() only asks the database whether a row is there
public function emailIsTaken(string $email): bool
{
return User::find()->where(['email' => $email])->exists();
}
// ✓ same, negated
public function emailIsAvailable(string $email): bool
{
return !User::find()->where(['email' => $email])->exists();
}
/**
* @var numeric-string $id
* @var string $name
*/
echo Html::encode($id); // ✗ flagged — $id can only ever be a numeric-string
echo Html::encode($name); // ✓ a plain string may still contain special characters
// ✗ mutation of properties
Yii::$app->params = [...];
Yii::$app->setComponents([...]);
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.