wp-phpstan
Use when configuring, running, or fixing PHPStan static analysis in WordPress projects (plugins/themes/sites): phpstan.neon setup, baselines, WordPress-specific typing, and handling third-party plugin classes.
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install wordpress-agent-skills-wp-phpstan
Repository
Skill path: skills/wp-phpstan
Use when configuring, running, or fixing PHPStan static analysis in WordPress projects (plugins/themes/sites): phpstan.neon setup, baselines, WordPress-specific typing, and handling third-party plugin classes.
Open repositoryBest for
Primary workflow: Ship Full Stack.
Technical facets: Full Stack, Integration.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: WordPress.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install wp-phpstan into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/WordPress/agent-skills before adding wp-phpstan to shared team environments
- Use wp-phpstan for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: wp-phpstan
description: "Use when configuring, running, or fixing PHPStan static analysis in WordPress projects (plugins/themes/sites): phpstan.neon setup, baselines, WordPress-specific typing, and handling third-party plugin classes."
compatibility: "Targets WordPress 6.9+ (PHP 7.2.24+). Requires Composer-based PHPStan."
---
# WP PHPStan
## When to use
Use this skill when working on PHPStan in a WordPress codebase, for example:
- setting up or updating `phpstan.neon` / `phpstan.neon.dist`
- generating or updating `phpstan-baseline.neon`
- fixing PHPStan errors via WordPress-friendly PHPDoc (REST requests, hooks, query results)
- handling third-party plugin/theme classes safely (stubs/autoload/targeted ignores)
## Inputs required
- `wp-project-triage` output (run first if you haven't)
- Whether adding/updating Composer dev dependencies is allowed (stubs).
- Whether changing the baseline is allowed for this task.
## Procedure
### 0) Discover PHPStan entrypoints (deterministic)
1. Inspect PHPStan setup (config, baseline, scripts):
- `node skills/wp-phpstan/scripts/phpstan_inspect.mjs`
Prefer the repo’s existing `composer` script (e.g. `composer run phpstan`) when present.
### 1) Ensure WordPress core stubs are loaded
`szepeviktor/phpstan-wordpress` or `php-stubs/wordpress-stubs` are effectively required for most WordPress plugin/theme repos. Without it, expect a high volume of errors about unknown WordPress core functions.
- Confirm the package is installed (see `composer.dependencies` in the inspect report).
- Ensure the PHPStan config references the stubs (see `references/third-party-classes.md`).
### 2) Ensure a sane `phpstan.neon` for WordPress projects
- Keep `paths` focused on first-party code (plugin/theme directories).
- Exclude generated and vendored code (`vendor/`, `node_modules/`, build artifacts, tests unless explicitly analyzed).
- Keep `ignoreErrors` entries narrow and documented.
See:
- `references/configuration.md`
### 3) Fix errors with WordPress-specific typing (preferred)
Prefer correcting types over ignoring errors. Common WP patterns that need help:
- REST endpoints: type request parameters using `WP_REST_Request<...>`
- Hook callbacks: add accurate `@param` types for callback args
- Database results and iterables: use array shapes or object shapes for query results
- Action Scheduler: type `$args` array shapes for job callbacks
See:
- `references/wordpress-annotations.md`
### 4) Handle third-party plugin/theme classes (only when needed)
When integrating with plugins/themes not present in the analysis environment:
- First, confirm the dependency is real (installed/required).
- Prefer plugin-specific stubs already used in the repo (common examples: `php-stubs/woocommerce-stubs`, `php-stubs/acf-pro-stubs`).
- If PHPStan still cannot resolve classes, add targeted `ignoreErrors` patterns for the specific vendor prefix.
See:
- `references/third-party-classes.md`
### 5) Baseline management (use as a migration tool, not a trash bin)
- Generate a baseline once for legacy code, then reduce it over time.
- Do not “baseline” newly introduced errors.
See:
- `references/configuration.md`
## Verification
- Run PHPStan using the discovered command (`composer run ...` or `vendor/bin/phpstan analyse`).
- Confirm the baseline file (if used) is included and didn’t grow unexpectedly.
- Re-run after changing `ignoreErrors` to ensure patterns are not masking unrelated issues.
## Failure modes / debugging
- “Class not found”:
- confirm autoloading/stubs, or add a narrow ignore pattern
- Huge error counts after enabling PHPStan:
- reduce `paths`, add `excludePaths`, start at a lower level, then ratchet up
- Inconsistent types around hooks / REST params:
- add explicit PHPDoc (see references) rather than runtime guards
## Escalation
- If a type depends on a third-party plugin API you can’t confirm, ask for the dependency version or source before inventing types.
- If fixing requires adding new Composer dependencies (stubs/extensions), confirm it with the user first.
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### references/third-party-classes.md
```markdown
# Third-party classes and ignore patterns
When PHPStan reports legitimate classes as missing (e.g. because WordPress or a plugin is not installed in the analysis environment), prefer fixing discovery first and only then add targeted ignores.
## Before adding `ignoreErrors`
- Confirm the dependency is real (installed/required in this environment).
- Prefer stubs/extensions already used by the repo.
- Prefer a narrow ignore for the vendor prefix over a broad ignore.
## Recommended stub packages
Stubs are useful when the analysis environment does not include WordPress (or a plugin API) but you still want real type checking (instead of blanket ignores).
Common packages:
```bash
composer require --dev szepeviktor/phpstan-wordpress
composer require --dev php-stubs/wordpress-stubs
composer require --dev php-stubs/woocommerce-stubs
composer require --dev php-stubs/acf-pro-stubs
```
When stubs are useful (and sometimes necessary):
- Running PHPStan in a plugin/theme repo without a full WordPress checkout.
- PHPStan reports unknown WordPress core functions (e.g. `add_action()`, `get_option()`).
- Integrations with optional plugins (WooCommerce, ACF Pro) that are not installed during analysis.
- You want method/property existence checks and accurate return types instead of `ignoreErrors`.
Notes:
- Prefer stubs that match the runtime versions; mismatches can cause false positives.
- Adding Composer dependencies changes the repo; confirm it is acceptable for the task.
## Ensure stubs are loaded
Installing stubs is not enough if PHPStan does not scan them. Add stub paths in `phpstan.neon`.
```neon
parameters:
bootstrapFiles:
- %rootDir%/../../php-stubs/woocommerce-stubs/woocommerce-stubs.php
scanFiles:
- %rootDir%/../../php-stubs/wordpress-stubs/wordpress-stubs.php
- %rootDir%/../../php-stubs/acf-pro-stubs/acf-pro-stubs.php
- %rootDir%/../../woocommerce/action-scheduler/functions.php
```
## Targeted ignore patterns (examples)
```neon
parameters:
ignoreErrors:
# Admin Columns Pro
- '#.*(unknown class|invalid type|call to method .* on an unknown class) AC\\ListScreen.*#'
# Elementor
- '#.*(unknown class|invalid type|call to method .* on an unknown class) Elementor\\.*#'
# Yoast SEO
- '#.*(unknown class|invalid type|call to method .* on an unknown class) WPSEO_.*#'
```
Pattern creation rules:
- Cover error variations: `unknown class`, `invalid type`, `call to method .* on an unknown class`.
- Keep patterns specific enough to target only intended classes.
- Add a short comment naming the plugin/theme.
- Group related patterns for the same dependency.
When to add exceptions:
- Only for legitimate third-party dependencies your code integrates with.
- Document each pattern with a comment.
- Re-run PHPStan to ensure the ignore does not hide unrelated issues.
```
### references/configuration.md
```markdown
# PHPStan configuration (WordPress)
This reference documents a minimal, WordPress-friendly PHPStan setup and baseline workflow.
## Minimal `phpstan.neon` template
Use the repo’s existing layout. The example below is intentionally conservative and should be adapted to the project’s actual directories.
```neon
# Include the baseline only if the file exists.
includes:
- phpstan-baseline.neon
parameters:
level: 5
paths:
- src/
- includes/
excludePaths:
- vendor/
- vendor-prefixed/
- node_modules/
- tests/
ignoreErrors:
# Add targeted exceptions only when necessary.
```
Guidelines:
- Prefer analyzing first-party code only.
- Exclude anything generated or vendored.
- Keep `ignoreErrors` patterns narrow and grouped by dependency.
## Baseline workflow
Baselines help you adopt PHPStan in legacy code without accepting new regressions.
```bash
# Generate a baseline (explicit filename)
vendor/bin/phpstan analyse --generate-baseline phpstan-baseline.neon
# Update an existing baseline (defaults)
vendor/bin/phpstan analyse --generate-baseline
```
Best practices:
- Avoid adding new errors to the baseline; fix the new code instead.
- Treat baseline changes like code changes: review in PRs.
- Chip away at the baseline gradually (remove entries as you fix root causes).
```
### references/wordpress-annotations.md
```markdown
# WordPress-specific type annotations
These patterns help PHPStan understand WordPress code where runtime behavior and dynamic typing make inference difficult.
## REST API request typing
PHPStan cannot infer valid request parameters from REST API schemas. Provide explicit type hints for request params.
```php
/**
* Handle REST API request.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, error on failure.
*
* @phpstan-param WP_REST_Request<array{
* post?: int,
* orderby?: string,
* meta_key?: string,
* per_page?: int,
* status?: array<string>
* }> $request
*/
public function get_items( $request ) {
$post_id = $request->get_param( 'post' );
// PHPStan now knows $post_id is int|null.
}
```
For complex schemas, define reusable types.
```php
/**
* @phpstan-type PostRequestParams array{
* title?: string,
* content?: string,
* status?: 'publish'|'draft'|'private',
* meta?: array<string, mixed>
* }
*
* @phpstan-param WP_REST_Request<PostRequestParams> $request
*/
```
## Hook callbacks
```php
/**
* Handle status transitions.
*
* @param string $new_status
* @param string $old_status
* @param WP_Post $post
*/
function handle_transition( string $new_status, string $old_status, WP_Post $post ): void {
// ...
}
add_action( 'transition_post_status', 'handle_transition', 10, 3 );
```
## Database and iterables
```php
/**
* @return array<WP_Post> WP_Post objects.
*/
function get_custom_posts(): array {
$posts = get_posts( [ 'post_type' => 'custom_type', 'numberposts' => -1 ] );
return $posts;
}
/**
* @return array<object{id: int, name: string}> Database results.
*/
function get_user_data(): array {
global $wpdb;
$results = $wpdb->get_results( "SELECT id, name FROM users", OBJECT );
return $results ?: [];
}
```
## Hooks (`apply_filters()` and `do_action()`)
Docblocks for `apply_filters()` and `do_action()` are validated. The type of the first `@param` is definitive.
If a third party returns the wrong type for a filter, a PHPStan error is expected and does not require defensive code.
```php
/**
* Allows hooking into formatting of the price.
*
* @param string $formatted The formatted price.
* @param float $price The raw price.
* @param string $locale Locale to localize pricing display.
* @param string $currency Currency symbol.
*/
return apply_filters( 'autoscout_vehicle_price_formatted', $formatted, $price, $locale, $currency );
```
## Action Scheduler argument shapes
```php
/**
* Process a scheduled email.
*
* @param array{user_id: int, email: string, data: array<string, mixed>} $args
*/
function process_scheduled_email( array $args ): void {
$user_id = $args['user_id'];
// ...
}
as_schedule_single_action(
time() + 3600,
'process_scheduled_email',
[
'user_id' => 123,
'email' => '[email protected]',
'data' => [ 'key' => 'value' ],
]
);
```
```