Skip to content

Adding PHPStan to a WordPress Plugin (Without Losing Your Mind)

7 min read

WordPress and static analysis have a complicated relationship. The WordPress codebase predates modern PHP by a decade, and PHPStan doesn't understand $wpdb->get_results() returns mixed, not a typed array.

Here's how I got PHPStan level 8 running on every plugin at Codexpert.

The Problem

Out of the box, PHPStan will explode on any WordPress plugin:

Function apply_filters() not found. Access to an undefined property WP_Post::$post_type.

WordPress functions aren't in any autoloadable namespace β€” they're global functions defined at runtime.

Step 1: WordPress Stubs

Install php-stubs/wordpress-stubs:

composer require --dev php-stubs/wordpress-stubs

Add to phpstan.neon:

parameters: bootstrapFiles: - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php stubFiles: - vendor/php-stubs/wordpress-stubs/wordpress-stubs.php

Step 2: WooCommerce Stubs

If your plugin uses WooCommerce:

composer require --dev php-stubs/woocommerce-stubs

For Dokan we maintain our own internal stubs β€” the official WC stubs don't cover every filter and action. We ship phpstan-dokan-stubs as an open-source package.

Step 3: Ignore What You Can't Fix

Some WordPress patterns can't be statically typed:

parameters: ignoreErrors: - '#Call to an undefined method WP_Error\:\:#' - '#Unsafe usage of new static\(\)#' message: '#Variable \$wpdb might not be defined#' path: src/

Step 4: Incremental Level Increases

Don't start at level 8. Start at level 1 and fix errors before going to level 2. With a 10k-line plugin you'll have ~50 errors at level 1. That's manageable. Level 8 will show 500 and you'll give up.

We run phpstan analyse --level=max in CI and treat any new error as a build failure. Existing errors live in a baseline file.

# Generate baseline for existing code phpstan analyse --generate-baseline phpstan-baseline.neon

Results

After six months of incremental adoption across our plugin suite:

  • Caught 12 real bugs that would have reached production
  • Eliminated an entire category of null-dereference crashes
  • Onboarding new engineers is faster because the type signatures document intent
Share X / Twitter LinkedIn
A

Al Amin Ahamed

Senior software engineer & AI practitioner. Building things in Laravel, PHP, and TypeScript.

About me β†’

One email a month. No noise.

What I shipped, what I read, occasional deep dive. Unsubscribe anytime.