Wp Phpstan
WordPressOfficialPublished by WordPress
- Skill
- Tool integration
- Software development
- GPL-2.0-or-later
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides a structured approach to implementing and managing PHPStan static analysis within WordPress projects. It covers the configuration of analysis environments, the use of WordPress-specific stubs to resolve core and third-party dependencies, and best practices for baseline management. The workflow emphasizes fixing type errors through accurate PHPDoc annotations rather than relying on broad ignore patterns, ensuring a cleaner and more maintainable codebase.
What it does
- Inspects existing PHPStan configurations and entrypoints.
- Configures WordPress-specific stubs to resolve core and third-party function calls.
- Provides templates for minimal and effective phpstan.neon files.
- Guides the use of PHPDoc annotations to improve type safety in REST endpoints and hooks.
- Manages baseline files to track and reduce technical debt in legacy code.
- Offers strategies for handling third-party plugin classes via targeted ignore patterns.
When to use it
- When setting up PHPStan for the first time in a WordPress plugin or theme.
- When encountering high volumes of 'unknown class' or 'unknown function' errors.
- When integrating third-party plugin APIs that are not present in the analysis environment.
- When you need to clean up or reduce a large PHPStan baseline file.
- When improving type safety for WordPress-specific patterns like REST requests or action hooks.
Advantages
- Reduces false positives by correctly identifying WordPress core and plugin functions.
- Promotes better code quality through explicit type documentation.
- Prevents the accumulation of new errors while managing legacy technical debt.
- Provides a clear, repeatable process for configuring static analysis.
Usage examples
- “Help me set up PHPStan for my WordPress plugin.”
- The agent will inspect your current project structure, suggest a minimal phpstan.neon configuration, and guide you through installing necessary WordPress stubs.
- “I have too many errors in my legacy plugin, how do I start using PHPStan?”
- The agent will help you generate a baseline file to suppress existing errors, allowing you to focus on fixing new code while gradually reducing the baseline over time.
- “PHPStan is complaining about missing classes from WooCommerce, what should I do?”
- The agent will advise on installing the appropriate WooCommerce stubs or creating targeted ignore patterns if the dependency is optional.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/wordpress/wp-phpstan/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip wp-phpstan.zip -d .agents/skills
Documentation by WordPress
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.distgenerating or updating
phpstan-baseline.neonfixing 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-triageoutput (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)
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.dependenciesin 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
pathsfocused on first-party code (plugin/theme directories).Exclude generated and vendored code (
vendor/,node_modules/, build artifacts, tests unless explicitly analyzed).Keep
ignoreErrorsentries 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
@paramtypes for callback argsDatabase results and iterables: use array shapes or object shapes for query results
Action Scheduler: type
$argsarray 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
ignoreErrorspatterns 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 ...orvendor/bin/phpstan analyse).Confirm the baseline file (if used) is included and didn’t grow unexpectedly.
Re-run after changing
ignoreErrorsto 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, addexcludePaths, 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.
Files
- ATTRIBUTION.mdAdded by AutoGineer884 B
- LICENSELicence file added by AutoGineer750 B
- SKILL.md4.0 KiB
- references/configuration.md1.3 KiB
- references/third-party-classes.md2.8 KiB
- references/wordpress-annotations.md3.0 KiB
- scripts/phpstan_inspect.mjsScript7.8 KiB
scripts/phpstan_inspect.mjs
import fs from "node:fs";
import path from "node:path";
const TOOL_VERSION = "0.1.0";
/**
* Reads and parses JSON from a file path.
*
* Returns null when parsing fails so the caller can provide user-facing
* guidance without crashing.
*
* @param {string} filePath Absolute path to a JSON file.
* @returns {any|null} Parsed JSON object.
*/
function readJsonSafe(filePath) {
try {
return JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch {
return null;
}
}
/**
* Reads a UTF-8 text file.
*
* Returns null when reading fails so callers can surface missing configs
* without crashing.
*
* @param {string} filePath Absolute path to a text file.
* @returns {string|null} File contents.
*/
function readTextSafe(filePath) {
try {
return fs.readFileSync(filePath, "utf8");
} catch {
return null;
}
}
/**
* Checks whether a path exists and is a regular file.
*
* @param {string} filePath Absolute or relative file path.
* @returns {boolean} True when the path exists and is a file.
*/
function isFile(filePath) {
try {
return fs.statSync(filePath).isFile();
} catch {
return false;
}
}
/**
* Normalizes Composer script entries into a flat list of commands.
*
* Composer allows scripts to be strings or arrays. This helper provides a
* consistent format for analysis.
*
* @param {unknown} value Composer script value.
* @returns {string[]} Command list.
*/
function normalizeComposerScript(value) {
if (typeof value === "string") return [value];
if (Array.isArray(value)) return value.filter((x) => typeof x === "string");
return [];
}
/**
* Detects which Composer scripts invoke PHPStan.
*
* This helps the agent prefer the repo's own invocation (memory limits,
* config, bootstrap files) instead of guessing.
*
* @param {Record<string, unknown>} scripts Composer scripts block.
* @returns {Array<{name: string, commands: string[]}>} Matching script entries.
*/
function findPhpstanScripts(scripts) {
if (!scripts || typeof scripts !== "object") return [];
const matches = [];
for (const [name, raw] of Object.entries(scripts)) {
const commands = normalizeComposerScript(raw);
const invokesPhpstan = commands.some((cmd) => {
if (typeof cmd !== "string") return false;
return cmd.includes("phpstan") || cmd.includes("vendor/bin/phpstan");
});
if (!invokesPhpstan) continue;
matches.push({ name, commands });
}
return matches;
}
/**
* Chooses a recommended command for running PHPStan in the current repo.
*
* The intent is to prefer an existing Composer script (often has correct
* config, bootstrap, and memory limits), falling back to vendor binaries.
*
* @param {Array<{name: string, commands: string[]}>} phpstanScripts Matching Composer scripts.
* @param {{binaryRelPath: string|null, configRelPath: string|null}} fallbackInfo Fallback discovery.
* @returns {{command: string|null, rationale: string}} Suggested command and why.
*/
function suggestCommand(phpstanScripts, fallbackInfo) {
const preferred = phpstanScripts.find((s) => s.name === "phpstan");
if (preferred) {
return {
command: `composer run ${preferred.name}`,
rationale: "Uses the repo's Composer script (preferred for consistent config).",
};
}
if (phpstanScripts.length > 0) {
return {
command: `composer run ${phpstanScripts[0].name}`,
rationale: "Uses the repo's Composer script that invokes PHPStan.",
};
}
if (!fallbackInfo.binaryRelPath) {
return {
command: null,
rationale: "No PHPStan binary detected under vendor/bin and no Composer script found.",
};
}
const configArg = fallbackInfo.configRelPath ? ` -c ${fallbackInfo.configRelPath}` : "";
return {
command: `${fallbackInfo.binaryRelPath} analyse${configArg}`,
rationale: "Falls back to vendor/bin/phpstan with an explicit config when needed.",
};
}
/**
* Extracts lightweight hints from a phpstan.neon config.
*
* This does not parse NEON. It only checks for common directive tokens so the
* agent can quickly see whether scan directives are in use.
*
* @param {string} configText Raw phpstan config contents.
* @returns {{mentionsScanDirectories: boolean, mentionsScanFiles: boolean}} Hints.
*/
function buildConfigHints(configText) {
const t = configText.toLowerCase();
return {
mentionsScanDirectories: t.includes("scandirectories"),
mentionsScanFiles: t.includes("scanfiles"),
};
}
/**
* Extracts stub-like package references from a PHPStan config.
*
* The PHPStan config usually references stubs via vendor paths (for example,
* "vendor/php-stubs/wordpress-stubs"), so this helper focuses on composer-style
* "vendor/package" tokens containing "stubs".
*
* @param {string} configText Raw phpstan config contents.
* @returns {string[]} Unique, lowercased composer-style package references.
*/
function extractStubPackageReferences(configText) {
const matches = configText
.toLowerCase()
.match(/\b[a-z0-9_.-]+\/[a-z0-9_.-]*stubs[a-z0-9_.-]*\b/g);
if (!matches) return [];
return [...new Set(matches)].sort();
}
/**
* Builds a JSON report describing the current repository's PHPStan setup.
*
* @returns {object} A stable, machine-readable inspection report.
*/
function buildReport() {
const repoRoot = process.cwd();
const composerPath = path.join(repoRoot, "composer.json");
const composer = isFile(composerPath) ? readJsonSafe(composerPath) : null;
const phpstanConfigFiles = ["phpstan.neon", "phpstan.neon.dist"].filter((f) =>
isFile(path.join(repoRoot, f))
);
const phpstanBaselineFiles = ["phpstan-baseline.neon", "phpstan-baseline.neon.dist"].filter((f) =>
isFile(path.join(repoRoot, f))
);
let configRelPath = null;
if (phpstanConfigFiles.includes("phpstan.neon")) configRelPath = "phpstan.neon";
else if (phpstanConfigFiles.includes("phpstan.neon.dist")) configRelPath = "phpstan.neon.dist";
const configAbsPath = configRelPath ? path.join(repoRoot, configRelPath) : null;
const configText = configAbsPath ? readTextSafe(configAbsPath) : null;
const binaryRelPath = isFile(path.join(repoRoot, "vendor", "bin", "phpstan")) ? "vendor/bin/phpstan" : null;
const composerScripts = composer?.scripts && typeof composer.scripts === "object" ? composer.scripts : null;
const phpstanScripts = composerScripts ? findPhpstanScripts(composerScripts) : [];
const composerDependencies = [
...Object.keys(composer?.require ?? {}),
...Object.keys(composer?.["require-dev"] ?? {}),
].sort();
const referencedDependencies = configText ? extractStubPackageReferences(configText) : [];
const configHints = configText ? buildConfigHints(configText) : null;
const suggested = suggestCommand(phpstanScripts, {
binaryRelPath,
configRelPath: configRelPath === "phpstan.neon" ? null : configRelPath,
});
const notes = [];
if (!composer) notes.push("No composer.json found; PHPStan is usually installed via Composer.");
if (phpstanConfigFiles.length === 0) notes.push("No phpstan.neon or phpstan.neon.dist found at repo root.");
if (!binaryRelPath && phpstanScripts.length === 0) notes.push("No PHPStan entrypoint detected (Composer script or vendor/bin/phpstan).");
return {
tool: { name: "phpstan_inspect", version: TOOL_VERSION },
repoRoot,
composer: {
exists: Boolean(composer),
path: isFile(composerPath) ? "composer.json" : null,
phpstanScripts,
dependencies: composerDependencies,
},
phpstan: {
configFiles: phpstanConfigFiles,
baselineFiles: phpstanBaselineFiles,
config: {
primary: configRelPath,
hints: configHints,
referencedDependencies,
},
binary: {
vendorBin: binaryRelPath,
},
},
suggested,
notes,
};
}
/**
* CLI entrypoint for printing the inspection report.
*/
function main() {
const report = buildReport();
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
}
main();
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
The author says: Targets WordPress 7.0+ (PHP 7.4.0+). Requires Composer-based PHPStan.
Security
What our scanner noticed· Low risk
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- WordPress
- Publisher
- WordPress (Official)
- Source
- WordPress/agent-skills · skills/wp-phpstan · commit 7374e6f
- Licence
- GNU General Public License v2.0 or later (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by WordPress or WordPress.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download wp-phpstan.zip