How to Use PHPStan for WordPress Plugin Development
Introduction
WordPress plugin development is flexible, but that flexibility can make certain PHP bugs difficult to detect before production.
A plugin may contain:
Untyped variables
Incorrect method calls
Missing null checks
Invalid return values
Undefined properties
Wrong constructor dependencies
Inconsistent array structures
Incorrect assumptions about WordPress APIs
Traditional testing can catch many runtime problems, but tests only execute the paths you cover.
Static analysis provides another layer of protection.
PHPStan analyzes PHP source code without executing the application's full runtime behavior. It can identify many potential problems before code reaches production.
A useful development pipeline is:
Developer Writes Code ↓ PHPStan ↓ PHP_CodeSniffer ↓ Automated Tests ↓ Build ↓ Release
For modern WordPress plugins using namespaces, Composer, dependency injection, services, repositories, and typed PHP, PHPStan becomes especially valuable.
This guide explains how PHPStan works, how to configure it for WordPress plugins, how to handle dynamic WordPress APIs, and how to introduce static analysis into both new and legacy plugins.
What Is PHPStan?
PHPStan is a static-analysis tool for PHP.
Instead of running your application and waiting for an error, PHPStan examines the code and attempts to identify potential problems.
For example:
function calculate( int $quantity ): float { return $quantity * 10.5; }
PHPStan can reason about the expected input and output types.
A problematic example:
function get_total(): float { return null; }
The declared return type conflicts with the returned value.
Static analysis can identify this before the code is executed.
Why PHPStan Matters for WordPress Plugins
PHPStan is particularly useful as plugins become larger.
It can help detect:
Type mismatches
Invalid method calls
Undefined properties
Incorrect return types
Impossible conditions
Nullability problems
Missing class references
Dependency mistakes
Incorrect array assumptions
Benefits include:
Earlier bug detection
Safer refactoring
Better IDE support
Improved type coverage
Greater confidence in large codebases
PHPStan vs PHPUnit
PHPStan and PHPUnit solve different problems.
PHPStan
Analyzes source code statically.
Code ↓ PHPStan ↓ Potential Problems
PHPUnit
Executes tests.
Code ↓ Tests ↓ Observed Behavior
A strong plugin development workflow uses both:
Static Analysis + Automated Tests = Stronger Quality Controls
PHPStan cannot replace functional tests.
Tests also cannot detect every statically visible problem.
Installing PHPStan With Composer
For a Composer-based plugin, install PHPStan as a development dependency.
composer require --dev phpstan/phpstan
Then run:
vendor/bin/phpstan analyse
A basic Composer structure might be:
plugin/ ├── composer.json ├── src/ ├── tests/ └── vendor/
Composer keeps development dependencies separate from the plugin's production runtime.
Create a PHPStan Configuration
PHPStan commonly uses a phpstan.neon or phpstan.neon.dist configuration file.
Example:
parameters: level: 5 paths: - src
Run:
vendor/bin/phpstan analyse -c phpstan.neon
The level controls analysis strictness.
A useful strategy is to start at a manageable level and increase strictness over time.
Choosing an Analysis Level
PHPStan supports progressively stricter analysis levels.
A large legacy plugin may not be ready for the strictest configuration immediately.
A practical progression is:
Existing Code ↓ Start at Manageable Level ↓ Fix Important Errors ↓ Increase Level ↓ Fix New Findings ↓ Repeat
Do not increase strictness merely to make the configuration look impressive.
The goal is useful, trustworthy analysis.
WordPress Creates a Special Challenge
WordPress uses many dynamic patterns.
Examples include:
do_action(); apply_filters(); get_option(); get_post(); get_user_by(); WP_Query;
Plugins may also use:
Dynamic hooks
Dynamic properties in third-party code
Global APIs
Runtime-created values
Database queries
Flexible arrays
Plugin-defined metadata
Static analyzers need enough information to understand these APIs.
That is where WordPress stubs and PHPDoc become important.
Use WordPress Stubs
PHPStan needs to know what WordPress functions and classes look like.
WordPress-specific stubs provide static-analysis information for common WordPress APIs.
A WordPress plugin project can use a WordPress-aware PHPStan setup so that calls such as:
get_option(); get_post(); wp_remote_get(); current_user_can();
can be analyzed with better type information.
The exact package and setup should match the project's supported WordPress and PHP versions.
Example of Missing Type Information
Suppose you write:
$post = get_post( $post_id ); echo $post->post_title;
Depending on the API signature, get_post() may return a post object or null.
A type-aware analysis can warn that $post may not exist.
A safer approach is:
$post = get_post( $post_id ); if ( $post === null ) { return; } echo $post->post_title;
This is exactly the kind of issue static analysis can make visible.
Use PHPDoc to Improve Analysis
Native PHP types are excellent, but PHPDoc can provide additional information.
For arrays:
/** * @param array{ * customer_id: int, * total: float, * currency: string * } $data */ function process_order( array $data ): void { // ... }
Now PHPStan knows the expected array structure.
For collections:
/** * @return OrderData[] */ public function getOrders(): array { // ... }
PHPDoc can significantly improve analysis of WordPress plugins that use structured arrays.
Type Your Plugin Services
PHPStan becomes much more useful when application code uses explicit types.
For example:
final class OrderService { public function process( int $order_id ): void { // ... } public function calculateTotal( float $subtotal, float $tax ): float { return $subtotal + $tax; } }
The contracts are explicit.
PHPStan can now detect incompatible calls.
For example:
$service->calculateTotal( '100', [] );
would be identified as invalid.
PHPStan and Dependency Injection
Dependency injection creates explicit dependency contracts.
final class OrderService { public function __construct( private OrderRepositoryInterface $orders, private CrmInterface $crm ) {} }
PHPStan can reason about these dependencies.
If an incompatible object is passed:
new OrderService( $wrongRepository, $wrongIntegration );
static analysis can detect the mismatch.
This is one reason typed architectures and PHPStan work well together.
PHPStan With Repositories
Repositories benefit from explicit return contracts.
interface OrderRepositoryInterface { public function find( int $order_id ): ?OrderData; }
Then:
$order = $repository->find( $order_id ); if ( $order === null ) { return; }
PHPStan understands the control flow.
Without explicit types, static analysis has much less information to work with.
Analyze WordPress Hook Callbacks
WordPress hooks can pass dynamic arguments.
For example:
add_action( 'kdr_order_completed', [ $listener, 'handle' ], 10, 1 );
Listener:
final class OrderListener { public function handle( $order_id ): void { $this->orders->process( (int) $order_id ); } }
Static analysis can provide more confidence when your callback contract and application service contract are explicit.
The WordPress boundary may still require normalization or validation.
The important principle is:
Dynamic WordPress Input ↓ Validation / Normalization ↓ Typed Application Data ↓ Typed Service
Detect Nullability Problems
One of PHPStan's most useful features is identifying possible null values.
For example:
$user = get_user_by( 'id', $user_id ); echo $user->user_email;
If the API can return null, that property access may be unsafe.
Handle the absence case:
$user = get_user_by( 'id', $user_id ); if ( ! $user ) { return; } echo $user->user_email;
This prevents an entire category of runtime errors.
Analyze External API Code
Static analysis cannot determine whether a remote API will actually return the expected response.
But it can help once the response is validated and mapped.
For example:
$data = json_decode( $body, true );
This may produce loosely typed data.
Validate first:
if ( ! is_array( $data ) || ! isset( $data['id'] ) || ! is_string( $data['id'] ) ) { throw new RuntimeException( 'Invalid API response.' ); } $customer_id = $data['id'];
Now application code has a stronger contract.
Avoid Suppressing Every Error
PHPStan provides ways to ignore individual findings.
But excessive suppression defeats the purpose of static analysis.
Bad approach:
Hundreds of ignored errors ↓ Green build ↓ Little real confidence
A better approach is:
Finding ↓ Understand Cause ↓ Fix Code or Improve Type Information ↓ Suppress Only When Justified
Use ignores sparingly and document important exceptions.
PHPStan Baselines for Legacy Plugins
Legacy WordPress plugins may produce many findings when PHPStan is introduced.
A baseline can record known issues while preventing new problems from being introduced.
Conceptually:
Legacy Errors ↓ Baseline ↓ Existing Issues Accepted Temporarily ↓ New Errors Must Be Fixed
This allows teams to adopt static analysis without stopping development.
The baseline should be treated as technical debt, not a permanent dumping ground.
Make the Baseline Shrink Over Time
A healthy process is:
Baseline ↓ Fix 10 Existing Problems ↓ Update Baseline ↓ Fix More ↓ Update Again ↓ Eventually Remove Baseline
Measure progress rather than allowing suppressed errors to grow indefinitely.
PHPStan With PHP_CodeSniffer
PHPStan and PHP_CodeSniffer are complementary.
PHPStan
Focuses on code correctness and types.
PHP_CodeSniffer
Focuses on coding standards and style.
A quality pipeline can be:
PHPStan ↓ PHPCS ↓ PHPUnit ↓ Build
For WordPress plugins, WordPress Coding Standards can be added to the PHPCS stage.
Add PHPStan to Composer Scripts
You can define:
{ "scripts": { "analyse": "phpstan analyse -c phpstan.neon", "test": "phpunit", "lint": "phpcs" } }
Then run:
composer analyse composer test composer lint
This standardizes developer workflows.
Run PHPStan in CI
Static analysis is most useful when every change is checked automatically.
A CI pipeline can look like:
Pull Request ↓ Composer Install ↓ PHPStan ↓ PHPCS ↓ PHPUnit ↓ Build
If PHPStan finds a new problem, the build can fail before the code reaches production.
GitHub Actions Example
A simple workflow can run:
name: Quality on: pull_request: push: jobs: quality: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: php-version: '8.2' - run: composer install --no-interaction --prefer-dist - run: vendor/bin/phpstan analyse - run: vendor/bin/phpcs - run: vendor/bin/phpunit
The exact PHP version matrix should match the plugin's supported versions.
PHPStan and Multiple PHP Versions
Supporting multiple PHP versions requires more than running PHPStan once.
For example:
PHP 8.1 PHP 8.2 PHP 8.3 PHP 8.4
may require a test matrix depending on the plugin's compatibility policy.
Static analysis should use the appropriate target PHP version configuration.
A CI matrix can combine:
PHP Version + WordPress Version + Test Suite
For compatibility-sensitive plugins, this catches environment-specific problems earlier.
Analyze Only the Right Code
You don't necessarily need to analyze:
vendor/ node_modules/ build/ dist/
as application source.
For example:
parameters: level: 6 paths: - src
Analyze your own source and the areas you intentionally control.
Dependency code should normally be managed by its own maintainers.
WordPress Globals and Legacy Patterns
WordPress plugins may use globals such as:
global $wpdb;
Instead of pretending they don't exist, isolate them.
For example:
Infrastructure ↓ WordPress Database API ↓ Repository ↓ Application Service
This lets the application layer remain more strongly typed while the infrastructure layer handles WordPress-specific details.
PHPStan and Dynamic Properties
Older WordPress and third-party code may rely on dynamic properties.
Modern PHP versions have tightened behavior around dynamic properties.
Rather than weakening analysis globally, isolate legacy behavior and gradually modernize it where practical.
Good strategies include:
Explicit property declarations
Better interfaces
Typed DTOs
Wrapper classes
Updated dependencies
Avoid globally disabling useful checks simply because legacy code produces warnings.
PHPStan Rules for a Growing Plugin
A mature project may gradually introduce stricter rules.
For example:
Level 4 ↓ Level 5 ↓ Level 6 ↓ Level 7+
At each stage:
Analyze ↓ Review Findings ↓ Fix ↓ Add Missing Type Information ↓ Increase Strictness
The appropriate target depends on codebase maturity and project requirements.
Common PHPStan Mistakes
Starting Too Strictly
A massive error list can overwhelm a legacy project.
Ignoring Everything
A green build with thousands of suppressions provides little value.
No WordPress Type Information
Static analysis becomes less useful when important framework APIs are unknown.
No PHPDoc
Complex arrays and collections remain ambiguous.
Running Only Locally
Developers can forget checks. CI provides consistency.
Never Updating the Baseline
Technical debt becomes permanent.
Treating PHPStan as a Test Suite
Static analysis and runtime tests serve different purposes.
Recommended PHPStan Workflow
A practical development cycle is:
Write Code ↓ Run PHPStan ↓ Fix Type / Logic Findings ↓ Run PHPCS ↓ Run PHPUnit ↓ Commit ↓ CI Repeats Checks
This provides multiple layers of quality control.
PHPStan for Modular WordPress Plugins
For modular architecture:
src/ ├── Core/ ├── Commerce/ ├── Analytics/ ├── Notifications/ └── Integrations/
PHPStan can analyze the complete dependency graph.
For example:
Commerce ↓ OrderService ↓ OrderRepositoryInterface
and:
OrderService ↓ CrmInterface ↓ CrmAdapter
Typed interfaces make the relationships easier to validate.
PHPStan and Type-Safe WordPress Architecture
A strong architecture can look like:
WordPress Boundary ↓ Validation ↓ DTO ↓ Typed Service ↓ Typed Interface ↓ Repository / Adapter
PHPStan becomes most valuable in the typed application layer.
This allows dynamic WordPress data to be converted into explicit application contracts.
AI-Assisted PHPStan Improvements
AI tools can help developers work through PHPStan findings.
Useful tasks include:
Explain a static-analysis error
Suggest a type annotation
Generate PHPDoc
Identify nullable values
Suggest DTOs
Create interface contracts
Find inconsistent return types
Draft tests for a reported issue
Group related errors by root cause
A good workflow is:
PHPStan Report ↓ AI Analysis ↓ Candidate Fix ↓ Developer Review ↓ PHPStan ↓ Tests
AI should not simply suppress difficult errors.
The objective is to understand and improve the underlying code.
PHPStan Checklist for WordPress Plugins
Installation
Install PHPStan with Composer
Create configuration
Add WordPress-aware type information
Define analyzed source paths
Type Safety
Type parameters
Type return values
Type properties
Handle nullable values
Document complex arrays
Architecture
Type service interfaces
Type repository contracts
Type adapters
Use dependency injection
Keep WordPress boundaries explicit
CI
Run PHPStan automatically
Fail builds on new errors
Test supported PHP versions
Review baseline changes
Maintenance
Reduce baseline entries
Increase strictness gradually
Review ignored errors
Keep configuration documented
Why Choose ThemeKaddora?
For larger ThemeKaddora WordPress products, PHPStan can become an important part of the engineering quality pipeline.
A product containing WooCommerce, analytics, AI, marketing, automation, and external API integrations can accumulate thousands of lines of PHP.
A strong quality workflow can look like:
Composer ↓ PHPStan ↓ PHP_CodeSniffer ↓ PHPUnit ↓ Integration Tests ↓ Plugin Build ↓ Release
Within the codebase:
WordPress APIs ↓ Listeners / Controllers ↓ Typed DTOs ↓ Services ↓ Interfaces ↓ Repositories / Adapters
This makes static analysis more useful because the application layer has predictable contracts.
For ThemeKaddora plugins, PHPStan can also support safer refactoring as products evolve toward modular architecture, dependency injection, service layers, repositories, and modern PHP practices.
The objective is not to eliminate every warning immediately.
The objective is to steadily increase confidence in the codebase.
Conclusion
PHPStan is a powerful tool for improving WordPress plugin quality through static analysis.
It can detect many problems before they reach production, including:
Type mismatches
Invalid method calls
Undefined properties
Incorrect return values
Nullability issues
Broken dependency contracts
The most effective approach is to introduce PHPStan gradually.
Start with a manageable analysis level.
Add WordPress type information.
Improve native PHP types.
Use PHPDoc for complex structures.
Add a baseline for unavoidable legacy findings.
Run PHPStan in CI.
Then gradually increase strictness and reduce technical debt.
A strong WordPress development pipeline can be summarized as:
Typed PHP + PHPStan + PHPCS + PHPUnit + CI = Higher Development Confidence
PHPStan does not replace testing, code review, or good architecture.
Instead, it adds an automated layer that continuously checks whether the code's assumptions remain consistent.
For small plugins, basic static analysis may be enough.
For large WordPress plugins, SaaS platforms, WooCommerce systems, and API-driven products, PHPStan can become an essential part of a production-ready engineering workflow.
Frequently Asked Questions
What is PHPStan?
PHPStan is a static-analysis tool for PHP that examines source code to identify potential errors, incorrect types, invalid method calls, and other problems without requiring the complete application to execute.
Why should WordPress plugin developers use PHPStan?
PHPStan helps detect type and structural problems early, improves refactoring confidence, and provides stronger feedback as a plugin becomes larger and more complex.
Is PHPStan a replacement for PHPUnit?
No. PHPStan performs static analysis, while PHPUnit executes automated tests. They complement each other.
Does PHPStan work with WordPress?
Yes. WordPress-specific type information and stubs can improve PHPStan's understanding of WordPress functions, classes, and APIs.
What PHPStan level should a WordPress plugin use?
There is no universal best level. Start at a manageable level and increase strictness as the codebase gains type coverage and the team can address findings reliably.
Should I use a PHPStan baseline?
A baseline can be useful for legacy plugins with many existing findings. It allows new problems to be prevented while the existing technical debt is reduced over time.
Is it okay to ignore PHPStan errors?
Some findings may require justified suppression, especially around dynamic third-party behavior. However, broadly ignoring errors removes much of the value of static analysis.
Can PHPStan detect WordPress hook mistakes?
It can help with callback types and contracts, but dynamic hook behavior may require integration tests and accurate PHPDoc or framework type information.
How does PHPStan handle nullable WordPress APIs?
When type information indicates that an API can return null, PHPStan can warn about unsafe property or method access. Your code should explicitly handle the absence case.
Can PHPStan analyze $wpdb code?
Yes, but database operations may require additional type annotations and architecture. Isolating database access behind repositories can make the application layer easier to analyze.
Should I use PHPDoc with PHPStan?
Yes. PHPDoc is particularly useful for array shapes, collections, generic structures, and contracts that native PHP types cannot fully describe.
Can PHPStan improve WordPress plugin type safety?
Yes. It works especially well with parameter types, return types, typed properties, interfaces, DTOs, enums, dependency injection, and explicit repository contracts.
Should PHPStan run in CI?
Yes. Running static analysis in CI ensures that changes are checked consistently and prevents developers from accidentally bypassing local quality checks.
Can PHPStan run across multiple PHP versions?
Yes. CI can use a PHP version matrix to test supported environments. The PHPStan target configuration should match the plugin's compatibility policy.
Does PHPStan improve plugin performance?
Not directly. PHPStan is primarily a correctness and maintainability tool. It may help identify problematic code patterns, but runtime performance requires profiling and measurement.
How should I introduce PHPStan into a legacy WordPress plugin?
Start with a manageable level, add WordPress type information, generate a baseline if necessary, fix high-value issues, and gradually increase analysis strictness.
Can AI help fix PHPStan errors?
Yes. AI can explain findings, suggest type improvements, generate PHPDoc, draft fixes, and create tests. Developers should verify the suggested changes rather than blindly suppressing warnings.
Why choose Themekaddora?
Themekaddora provides lightweight, responsive, SEO-friendly WordPress themes with fast performance, WooCommerce compatibility, flexible customization, accessibility-conscious design, modern templates, regular updates, and professional support—providing a strong foundation for businesses building digital products and product-focused websites.
Comments (0)