A local WordPress dev setup: LocalWP, Mailpit, PHPCS

LocalWP, Mailpit in place of MailHog, and PHPCS with the WordPress coding standards

Leave a comment
Risograph illustration of envelopes flying out of a laptop screen and being caught in a butterfly net

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 phpcbf as 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.

text
SMTP : 1025
HTTP : 8025

The 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:

php.iniini
sendmail_path = /usr/local/bin/mailpit sendmail

Now 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:

shell · bash
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.

shell · bash
phpcbf --standard=WordPress .
phpcs -ps . --standard=WordPress

Run 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_Error lives in class-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:
php
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 repository
  • git config -> set user.name and user.email
  • git branch <name> -> create a branch
  • git switch <branch> -> switch to it. git checkout <branch> still works
  • git clone -> copy a remote repository
  • git add . -> stage everything
  • git commit -m "message" -> commit the staged changes
  • git diff -> what is changed but not staged yet
  • git status -> what is staged, unstaged and untracked
  • git log -> history of the current branch
  • git push <remote> <branch> -> send it up
  • git 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.

shell · bash
git log --merge

While 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:

.github/workflows/phpcs.ymlyaml
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=WordPress
  • name -> what the workflow is called
  • on -> the event that triggers the run
  • jobs -> the units of work
  • runs-on -> the runner platform
  • steps -> 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

Seen this one too, or think I got something wrong? Your email is not published, and a first comment waits for me to approve it.