johnbillion/plugin-infrastructure
Reusable infrastructure for WordPress plugins: a set of classes and utilities covering common plugin needs such as hooks, admin settings, and lifecycle handling, helping developers build cleaner, more maintainable plugins with less boilerplate.
What this actually is: a Composer package of shared, opinionated dev tooling for John Blackbourn's WordPress plugins — test bootstrapping, coding-standard configs, static analysis and build/deploy scaffolding. It is not a runtime library and it is not a general-purpose WP framework. You adopt it to inherit one maintainer's conventions wholesale, then override the small parts that differ per plugin.
Minimal steps:
{
"require-dev": {
"johnbillion/plugin-infrastructure": "^1.0"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}
Run composer install, then read vendor/johnbillion/plugin-infrastructure/README.md first — it is the only authoritative description of the current file layout. Then inspect the shipped config files (PHPCS ruleset, PHPUnit config, any Psalm/static-analysis config) and the bin/ scripts. Those files, not the PHP source, are the real public API of this package.
First use case: wire up linting. Point your plugin's phpcs.xml.dist at the package ruleset, then run vendor/bin/phpcs. It is the lowest-risk, highest-immediate-value entry point:
<?xml version="1.0"?>
<ruleset name="My Plugin">
<rule ref="./vendor/johnbillion/plugin-infrastructure/phpcs.xml.dist"/>
<file>./src</file>
<file>./my-plugin.php</file>
</ruleset>
Where to look first: README.md → shipped config files → bin/ scripts → src/ class (usually a small helper/constant holder). The src/ code is the least interesting part; the configs and scripts are where the day-to-day value lives.
1. Inherit configs by reference, don't copy them. The whole point is that upstream fixes (new WPCS sniffs, PHPUnit polyfills, CI tweaks) arrive via composer update. Copy-paste the config once and you have forked the infrastructure permanently.
<!-- phpcs.xml.dist: extend, then narrow -->
<rule ref="./vendor/johnbillion/plugin-infrastructure/phpcs.xml.dist"/>
<exclude-pattern>./vendor/*</exclude-pattern>
<rule ref="WordPress.WP.I18n">
<properties>
<property name="text_domain" value="my-plugin"/>
</properties>
</rule>
2. Reuse the PHPUnit bootstrap instead of writing your own. The package exists largely so the WP test-suite bootstrap isn't rewritten for every plugin. In your phpunit.xml.dist, point bootstrap at the package's bootstrap and supply the environment it expects:
<phpunit bootstrap="./vendor/johnbillion/plugin-infrastructure/tests/bootstrap.php">
<testsuites>
<testsuite name="unit">
<directory>./tests</directory>
</testsuite>
</testsuites>
</phpunit>
The bootstrap will look for a WordPress test install — set WP_TESTS_DIR (or WP_DEVELOP_DIR) in your local shell and in CI. Locally: bin/install-wp-tests.sh from wp-phpunit/wordpress-develop.
3. Make Composer scripts your single task runner. This is how the infrastructure becomes day-to-day rather than ceremony:
{
"scripts": {
"lint": "phpcs",
"lint:fix": "phpcbf",
"test": "phpunit",
"test:coverage": "phpunit --coverage-html build/coverage",
"check": ["@lint", "@test"]
}
}
Then composer check locally and in CI is one command, and the definition lives in the repo, not in a YAML file nobody reads.
4. Mirror the package's CI shape. Reuse whatever workflow fragments it ships (or the patterns its README documents): a PHP-version × WP-version matrix, MySQL service container, composer install, composer check. Keep the matrix in your own .github/workflows/ but keep the commands identical to your local scripts so CI failures reproduce locally.
5. Override per plugin rather than fork. Every project needs different exclusions, prefix rules, text domains, minimum PHP and WP versions. Express all of that in your own thin config layer that references the package. Rule of thumb: your plugin's infra files should be 10–40 lines, not 300.
6. Build/deploy as a separate concern. For release artifacts, do a clean composer install --no-dev --optimize-autoloader in a staging copy, rsync excluding dev files, and zip. Never let the dev infrastructure end up in the distributed plugin. If the package ships a build script, call it from a composer build alias so contributors don't need tribal knowledge.
7. Keep it current deliberately. Add Renovate/Dependabot for johnbillion/plugin-infrastructure, but review bumps as potentially behavioral: a WPCS or PHPUnit bump changes your lint/test results without a line of your code changing.
require-dev. The most common mistake is adding it to require, which drags dev tooling into production autoload and inflates the plugin zip. If you see it in vendor/ of a shipped plugin, that's a bug.Call to undefined method from the polyfills, bootstrap dying on a missing WP_TESTS_DIR, or tests silently skipped. Check the package README for the PHPUnit major it targets before upgrading your test runner.composer test works in CI but not locally, that's almost always the missing test install, not the package../vendor/... from the project root, and be wary of __DIR__ in copied snippets — it now points into vendor/ and will silently include the wrong files.phpcs -i doesn't list the standards you expect, the composer installer plugin wasn't allowed (see the allow-plugins snippet) or vendor/bin/phpcs is resolving a global install. Run vendor/bin/phpcs -s to see sniff names in output, and -vvv when a ruleset ref fails to load.vendor/. Any tweak you make there disappears on the next composer update. If you need different behavior, add your own config layer upstream of the package's.composer show johnbillion/plugin-infrastructure (confirm version) → read the matching README.md in vendor/ → run the underlying binary directly (vendor/bin/phpunit --debug, vendor/bin/phpcs -vvv) rather than through Composer scripts, so exit codes and output aren't swallowed.How can I help you explore Laravel packages today?