wp-phpstan
Use when configuring, running or fixing PHPStan in a WordPress project that uses Acorn - phpstan.neon, baseline, szepeviktor/phpstan-wordpress stubs, larastan, level raising, "Cannot access property o
SKILL.mdplugins/kivo-wise/skills/wp-phpstan/SKILL.md
GitHubPHPStan for WordPress + Acorn (wrapper)
Full procedure: third-party/wp-phpstan/SKILL.upstream.md (official WordPress skill, GPL-2.0-or-later, see third-party/UPSTREAM.md). Read it and its references/ (configuration.md, wordpress-annotations.md, third-party-classes.md) first. This file adds the Roots specifics: where to point the analysis, how Acorn code differs, and how to run everything through the runner. Paths starting with third-party/ are relative to the kivo-wise plugin root, two levels above this skill's folder (<skill dir>/../../third-party/...), not inside the skill folder. In your answer, point the user to that full procedure by its resolved absolute path, and use the same resolution for any other third-party/ path you name.
When to use
- A project has no
phpstan.neonyet, or the configuration scans the wrong folders. - PHPStan reports errors on Blade composers, service providers, facades, Eloquent models or ACF Composer classes.
- You want to raise the level, generate a baseline or wire PHPStan into CI.
Inputs required
- Profile and app path from the session context (
{{runner.composer}},{{runner.test}}, the detected app path). - Current level and baseline file, if any; Composer constraints for
phpstan/phpstan(PHPStan 2 needs matching extension versions). - Which ACF and plugin classes the code touches (they need stubs or targeted ignores).
Procedure
Commands: write every command with the runner value from the session context filled in (for example lando wp acorn optimize:clear), never a bare wp/composer/php/npm, never the placeholder, never "prefix with lando if you use it". Commands that run in CI or a deploy script go in a separate block labelled with where they run.
Inspect first. Run the official script from the plugin root, with the project as working directory:
node third-party/wp-phpstan/scripts/phpstan_inspect.mjs. It reports the config, the baseline and whether WordPress stubs are installed. It reads files only; it does not run PHPStan.Install through the runner (dev dependencies, in the Composer root that owns the app):
{{runner.composer}} require --dev phpstan/phpstan szepeviktor/phpstan-wordpress phpstan/extension-installer. The extension installer loads the WordPress extension automatically; do not also include it by hand.Point
pathsat the app code, not at the whole site. Never analysewp/,vendor/,node_modules/or uploads.Profile paths:inphpstan.neonsage-themeapp/of the theme (config lives in the theme, or in the repo root with<theme>/app)acorn-mu-pluginapp/of the plugin (for examplewp-content/mu-plugins/<plugin>/app); putphpstan.neonat the repo root when the plugin has no owncomposer.jsonrunnerradicle-appapp/at the project root, plusconfig/if you want it checkedStart at level 1 and raise one level per MR; commit a baseline only for existing errors.
Larastan is optional on Acorn. It teaches PHPStan about facades, the container and Eloquent. Add
larastan/larastanonly when facade or model errors dominate the report, include itsextension.neonfromvendor/, and check its bootstrap requirement against the installed version first: it expects a bootable Laravel application, which Acorn only provides after WordPress has loaded. If it cannot boot, keep plain PHPStan and add PHPDoc on the few models or facades involved.Run through the runner:
{{runner.composer}} exec phpstan -- analyse --memory-limit=1G(Lando:lando composer exec phpstan -- analyse). Generate or update the baseline with--generate-baseline.Fix by cause, in this order: missing stubs or autoload, wrong PHPDoc, real bug, then targeted
ignoreErrorswith a comment.get_field()returnsmixed: narrow it with a type check or a typed wrapper instead of ignoring the whole file.CI: run with
--no-progress --error-format=github(or the format your CI reads) and fail on a baseline that grew.
Verification
{{runner.composer}} exec phpstan -- analyseexits 0 at the chosen level with the baseline applied.- The baseline file only shrinks between MRs; a new entry needs a reason in the MR.
- The command was run through the runner (container PHP), not host PHP, so the PHP version matches production.
Failure modes
| Symptom | Cause | Fix |
|---|---|---|
Function get_option not found |
WordPress stubs not loaded | Install szepeviktor/phpstan-wordpress and the extension installer; remove duplicate manual includes |
Thousands of errors from vendor/ or wp/ |
paths too wide |
Narrow paths to the app folder; use excludePaths for generated code |
Class Illuminate\... not found in a composer or provider |
Analysing the app from a Composer root that lacks the app's vendor | Run from the Composer root that installs Acorn, or add scanDirectories for that vendor |
| Facade or model property errors | No Larastan and no PHPDoc | Add PHPDoc on the model or enable Larastan when it can boot |
| Out of memory | Default memory limit | Pass --memory-limit=1G or raise it in the neon |
| Works locally, fails in CI | Different PHP version or missing dev install | Match the container PHP version in CI |
Escalation
- Raising the level above what the team agreed, or deleting a baseline: ask first.
- A fix that needs a behaviour change (not just a type) belongs in its own change with a test; hand it to
testing-wp. - If Larastan cannot be made to boot against the installed Acorn, stop and report the exact error rather than suppressing it.