A local WordPress setup that catches mistakes before review does: LocalWP for the site, a mail catcher so test mail never reaches a real inbox, and PHPCS with the WordPress standards. This covers the toolchain, the install commands, what changed since the 2022 notes, and the standards worth memorising.
The site and the editor
LocalWP spins up a WordPress site in a few clicks, which is the whole reason to use it.
The editor extensions that matter for PHP work:
- PHP Intelephense -> completion and go-to-definition
- a PHPCS extension -> violations inline as we type, and
phpcbfas the formatter - PHP DocBlocker -> the docblock skeleton
- PHP Debug -> step debugging through Xdebug
Deliberately not the WordPress-specific extensions on day one. Core PHP first, then the framework on top of it.
MailHog, and why it is Mailpit now
A mail catcher runs two servers, an SMTP listener and a web panel. It accepts mail and shows it in the panel instead of delivering it.
SMTP : 1025
HTTP : 8025The 2022 notes used MailHog. Its last release is v1.0.1, from August 2020, and the Mailpit README describes it as no longer maintained, with no security updates for years. Mailpit uses the same two ports by default, so it is a drop-in swap. LocalWP made the same swap: by Local 8.3, in early 2024, its built-in mail tool was Mailpit.
For mail sent through PHP’s own mail() rather than SMTP, mhsendmail is not needed any more. Mailpit can stand in for sendmail itself, with the path pointing wherever the binary is installed:
sendmail_path = /usr/local/bin/mailpit sendmailNow a plugin that fires 400 password resets during a migration test fires them at us.
PHPCS and the WordPress standards
PHPCS reports violations of a coding standard. phpcbf, the other half of the same tool, repairs every violation it knows how to repair. The project moved from Squizlabs to the PHPCSStandards organisation, and the Composer package is still squizlabs/php_codesniffer.
The 2022 notes installed PHPCS on its own, with composer global require "squizlabs/php_codesniffer=*". That now gets PHPCS 4, released September 2025, and WordPress Coding Standards 3.4.1 still requires PHPCS 3.13. Install WPCS instead and let it pull the PHPCS it supports:
composer global config allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
composer global require --dev wp-coding-standards/wpcs:"^3.0"Composer is the only supported install since WPCS 3.0, and its installer plugin registers the WordPress rulesets with PHPCS, so it has to be allowed first.
phpcbf --standard=WordPress .
phpcs -ps . --standard=WordPressRun phpcbf first, then phpcs, and what is left is the list of things that need a human. On WordPress VIP, PHPCS ships with the repo already configured.
The standards worth memorising
The rules that come up in review over and over, from the WordPress PHP coding standards:
- multi-line PHP inside HTML -> the open and close tags go on lines of their own
- no short tags.
<? ?>is out, and so is<?= - single quotes unless the string is evaluating something
- lowercase with underscores for variables, functions, actions and filters, never camelCase. Class names are
Capitalized_Words - class files are
class-plus the class name, underscores turned into hyphens ->WP_Errorlives inclass-wp-error.php - braces always, even where the language does not need them, so no single-statement inline control structures
- long array syntax,
array() - spaces inside parentheses:
my_function( $param1, $param2 );
if ( ! empty( $string ) ) {
}- validate and sanitise input, escape output. Never trust input
That last one is not a formatting rule sitting in a formatting list by accident. It is the only one where getting it wrong is a security bug rather than a review comment.
Git, and the merge conflict
The commands that cover most days:
git init-> start a repositorygit config-> setuser.nameanduser.emailgit branch <name>-> create a branchgit switch <branch>-> switch to it.git checkout <branch>still worksgit clone-> copy a remote repositorygit add .-> stage everythinggit commit -m "message"-> commit the staged changesgit diff-> what is changed but not staged yetgit status-> what is staged, unstaged and untrackedgit log-> history of the current branchgit push <remote> <branch>-> send it upgit pull-> fetch and merge
A merge conflict happens when two branches change the same lines, or when one deletes a file the other edited. Git will not guess which side wins, so it stops and asks.
git log --mergeWhile the conflict is unresolved, that shows only the commits that touched the conflicted files. A faster way in than reading the file and hoping.
The fork and pull request flow
- fork the official repository, which gives us a server-side copy
- clone our copy locally
- add a remote pointing at the official repository
- branch, change, commit
- push the branch to our own copy
- open a pull request from that branch against the official repository
- it gets reviewed, approved and merged
GitHub Actions
Workflows are event driven, defined in YAML files under .github/workflows/. The obvious first one runs PHPCS on every push, so the standards above stop being something anyone has to remember. This assumes WPCS is in the project’s own require-dev:
name: PHPCS
on: [push]
jobs:
phpcs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install --no-progress
- run: vendor/bin/phpcs -ps . --standard=WordPressname-> what the workflow is calledon-> the event that triggers the runjobs-> the units of workruns-on-> the runner platformsteps-> executed in order
The 2022 notes pinned actions/checkout@v3. GitHub’s own quickstart shows @v6 now, and the latest release is v7. Four major versions in four years. wp scaffold plugin-tests will also write a CI config to start from.
What this does not cover
Xdebug configuration, and wp-env or other Docker-based setups.

Leave a comment