PHPStan Configuration
phpstan.neon Configuration File
parameters:
level: 6
paths:
- app/code/Vendor/Module/Api/
- app/code/Vendor/Module/Model/
- app/code/Vendor/Module/Service/
# Exclude test files and generated code
excludePaths:
- */Test/*
- */_files/*
- */generated/*
# Ignore specific errors
ignoreErrors:
- '#Call to an undefined method Magento\\Framework\\DataObject::.*#'
- '#Property .* is never assigned#'
- '#has invalid typehints#'
# Treat certain errors as specific level
reportUnmatchedIgnoredErrors: false
reportAlwaysUsedPhpDocTypesAsConstant: false
# Check missing typehints on method parameters
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
# Tuning
polluteScopeWithAlwaysIterable: false
rememberPossiblyAlwaysTrueConditionAndCheckType: true
Running PHPStan
# Basic analyse
vendor/bin/phpstan analyse
# With specific level override
vendor/bin/phpstan analyse src/ --level=8
# Generate baseline (for existing code)
vendor/bin/phpstan analyse --generate-baseline
# analyse with maximum memory
vendor/bin/phpstan analyse --memory-limit=2G
# Clear cache
vendor/bin/phpstan clear-cache
PHPStan Extensions
Magento PHPStan Extension
# phpstan.neon
includes:
- vendor/magento/magento2-phpstan/extension.neon
parameters:
level: 6
paths:
- app/code/Vendor/
This adds type stubs for:
- Dynamic methods on
DataObject - Magic
__callmethods - Magento's factory pattern
- Scope config methods
Custom PHPStan Extension
namespace Vendor\Module\PHPStan\Rules;
use PhpParser\Node;
use PHPStan\Analyser\Scope;
use PHPStan\Rules\Rule;
class NoDirectObjectManagerUsage implements Rule
{
public function getNodeType(): string
{
return Node\Expr\MethodCall::class;
}
public function processNode(Node $node, Scope $scope): array
{
if ($this->isGetObjectCall($node, $scope)) {
return [
'Direct ObjectManager usage is not allowed. Use dependency injection.',
];
}
return [];
}
private function isGetObjectCall(Node $expr, Scope $scope): bool
{
if (!$expr->name instanceof Node\Identifier) {
return false;
}
if ($expr->name->name !== 'get') {
return false;
}
$type = $scope->getType($expr->var);
return $type instanceof \PHPStan\Type\ObjectType
&& $type->getClassName() === \Magento\Framework\ObjectManager\ObjectManager::class;
}
}
Register in extension:
# phpstan-extension.neon
services:
-
class: Vendor\Module\PHPStan\Rules\NoDirectObjectManagerUsage
tags:
- phpstan.rules.rule
PHPStan for Magento Best Practices
# Recommended Magento configuration
parameters:
level: 6
# Magento ignores
ignoreErrors:
# DataObject magic methods
- '#Property .* does not exist on \*DataObject#'
# Factory create() return types
- '#returned type \*Factory::create\(\) is not \*Interface#'
# Scope config dynamic methods
- '#Call to undefined method .*getValue\(#'
# Custom stubs for Magento
stubs:
- etc/phpstan/stubs/*.stub
CI/CD Integration
GitHub Actions Integration
# .github/workflows/phpstan.yml
name: PHPStan
on: [push, pull_request]
jobs:
phpstan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
extensions: mbstring, intl
coverage: none
- name: Install Dependencies
run: composer install --prefer-dist --no-progress
- name: Run PHPStan
run: vendor/bin/phpstan analyse --error-format=github --no-progress
- name: Run PHPStan (strict)
run: vendor/bin/phpstan analyse --level=8 --error-format=github
continue-on-error: true # Allow failure for strict level
Pre-commit Hook
#!/bin/bash
# .git/hooks/pre-commit
echo "Running PHPStan..."
vendor/bin/phpstan analyse app/code/Vendor/Module/ --no-progress --error-format=raw
if [ $? -ne 0 ]; then
echo "PHPStan failed. Fix errors before committing."
exit 1
fi
echo "PHPStan passed!"
PHPStan Baseline
# Generate baseline for existing code
vendor/bin/phpstan analyse --generate-baseline phpstan-baseline.neon
# phpstan.neon
includes:
- phpstan-baseline.neon # Existing errors are ignored
parameters:
level: 6
The baseline lets you adopt PHPStan gradually — fix new errors but don't fix existing ones yet.
Quiz
1. What PHPStan level is recommended as a starting point for Magento?
2. What does a PHPStan baseline do?
3. PHPStan ignoreErrors is used for:
Flashcards
Question
Recommended PHPStan level for Magento?
Click to reveal answer
Answer
Level 6 as starting point, can increase gradually
Question
What is phpstan-baseline.neon?
Click to reveal answer
Answer
Captures existing errors to allow gradual adoption
Question
How to run PHPStan?
Click to reveal answer
Answer
vendor/bin/phpstan analyse src/ --level=6
Question
Magento PHPStan extension provides?
Click to reveal answer
Answer
Type stubs for DataObject magic methods, factories, scope config
Revision Notes
Key Takeaways
- 1. PHPStan level 6 is the recommended starting point for Magento
- 2. Use the Magento PHPStan extension for dynamic method stubs
- 3. Generate a baseline to adopt PHPStan gradually
- 4. Integrate into CI pipeline for every push/PR
- 5. Custom rules enforce project-specific coding standards
Interview Tips
- • Explain how to gradually adopt PHPStan (baseline, start at level 6)
- • Discuss Magento-specific PHPStan challenges (magic methods, dynamic config)
- • Give examples of custom rules (no ObjectManager usage, required type hints)
Cheat Sheet
PHPStan Setup:
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse src/ --level=6
Magento: vendor/magento/magento2-phpstan/extension.neon
Baseline: vendor/bin/phpstan analyse --generate-baseline
CI: vendor/bin/phpstan analyse --error-format=github
Levels: 0-9 (9 = strictest)
6 = recommended start
8+ = for new codebases
Custom Rules:
class X implements Rule { getNodeType(); processNode(); }