Software Engineering in WordPress, PHP, and Backend Development

Tag: WordPress (Page 1 of 219)

Articles, tips, and resources for WordPress-based development.

How To Use Roots Radicle with Laravel Herd

I’ve previously written about using Laravel Herd as my preferred PHP development environment. Outside of my day-to-day work, I’m also working with a couple of friends on a project that includes an iOS app that talks to a REST API via headless WordPress backend.

The web app is built using a set of tools from Roots including Radicle:

Radicle is an opinionated starting point for WordPress projects with the Roots stack.

In short, Radicle allows us to use features of Laravel within WordPress. But one of the challenges with my set up is getting Laravel Herd and Radicle to actually work.

Turns out, the solution isn’t that hard. And if you’re in a similar situation, here’s how to work with Radicle with Laravel Herd.


Roots Radicle with Laravel Herd

Project Set Up

First, I’m operating on the assumption that you have your project is already linked and secured with Herd.

In our case, we’re using a monorepo that contains both the iOS app and the web application. The web app in question is located in projects/monorepo-name/apps/wordpress. So I’ve issued the usual commands add that directly to Herd.

Secondly, I’ve got the .env file configured so that it has all of the necessary information for the database, various WP environmental variables, salts, and other information required to stand up the web app.

The Actual Problem

Third, and this was the most problematic, I had to add a custom driver that would allow Radicle to work with Laravel Herd. The path to custom drivers on your machine may vary but if you’re running the latest version of macOS and haven’t customized the Herd installation then it should look something like this:

/Users/yourname/Library/Application Support/Herd/config/valet/Drivers

Note that if the Drivers directory doesn’t exist, create it. Then touch a file named RadicleValetDriver.php in that directory and add the the following code (you shouldn’t need to change any of it):

<?php

namespace Valet\Drivers\Custom;

use Valet\Drivers\BasicValetDriver;

class RadicleValetDriver extends BasicValetDriver
{
    /**
     * Determine if the driver serves the request.
     */
    public function serves(string $sitePath, string $siteName, string $uri): bool
    {
        return file_exists($sitePath.'/public/content/mu-plugins/bedrock-autoloader.php') &&
               file_exists($sitePath.'/public/wp-config.php') &&
               file_exists($sitePath.'/bedrock/application.php');
    }

    /**
     * Determine if the incoming request is for a static file.
     *
     * @return string|false
     */
    public function isStaticFile(string $sitePath, string $siteName, string $uri)/* : string|false */
    {
        $staticFilePath = $sitePath.'/public'.$uri;
        if ($this->isActualFile($staticFilePath)) {
            return $staticFilePath;
        }

        return false;
    }

    /**
     * Get the fully resolved path to the application's front controller.
     */
    public function frontControllerPath(string $sitePath, string $siteName, string $uri): string
    {
        $_SERVER['PHP_SELF'] = $uri;
        if (strpos($uri, '/wp/') === 0) {
            return is_dir($sitePath.'/public'.$uri)
                            ? $sitePath.'/public'.$this->forceTrailingSlash($uri).'/index.php'
                            : $sitePath.'/public'.$uri;
        }

        return $sitePath.'/public/index.php';
    }

    /**
     * Redirect to uri with trailing slash.
     *
     * @return string
     */
    private function forceTrailingSlash(string $uri)
    {
        if (substr($uri, -1 * strlen('/wp/wp-admin')) == '/wp/wp-admin') {
            header('Location: '.$uri.'/');
            exit;
        }

        return $uri;
    }
}

Note this is running on PHP 8.4 so you may need to adjust your function signatures and other features if you’re running on a significantly lower version (though I didn’t really test thing on anything lower than 8).

Once done, you should be able to load your project in the web browser. If not, restarting Herd’s services should do the trick. And, on the off change you still have an error, the stack trace should be easy enough to follow to see where the problem lies.

Given a vanilla Herd set up and Radicle integration with your WordPress project, this custom driver should be all you need to get everything working with as little effort as possible.

Note: This was adapted directly from the Laravel source code which you can find on GitHub.

WP Privacy, Attestation, Git Updater Lite, and More

For years, I’ve kept track of various resources that I’ve found useful. Having them here makes it easy to refer to them in the future should the need arise (don’t you refer back to your old posts? /s).

It also makes it easy for others to find them if they’re searching for them either in traditional ways or via some of the new ways we have to search (that latter of which is why I find value in still sharing content).

Anyway, over the last two weeks, there have a been four things I’ve found that I hope to look more into in the future. And if not, at least they’re here for posterity.


  • WP API Privacy. The default WordPress installation from wordpress.org automatically transmits extraneous information via various HTTP calls that occur in the admin. Some of this data may be cause for concern from a privacy perspective. This plugin seeks to limit that information, attempting to further protect your privacy in the process (via Duane Storey).
  • WordPress Plugin Attestation. Add this action to your deployment workflow to generate a build provenance attestation of the plugin ZIP file on WordPress.org (via John Blackbourne). For what it’s worth, “attestation” is just the verification that the software comes from where it claims to originate.
  • RAVE for WordPress. RAVE for WordPress is an automated tool which compares the contents of published packages of WordPress with the canonical source code to verify they have not been tampered with (via John Blackbourne).
  • Git Updater Lite. “Since Git Updater already gathers and parses this data, Git Updater Lite only needs to query an update server run by the developer” (via Andy Fragen).

And if you stumble across this post and are interested in anything I’ve written in the past week, you can find that below:

If you’re using WordPress and you’re looking for an extremely quick way to add this functionality to your local installation, add the following code to an mu-plugin …

    Until the next time there’s a backlog of stuff for me to share, that’s it for now.

    Catch Outgoing Emails From WordPress in Laravel Herd

    Earlier this year, I swapped my local development environment over to Herd (along with a couple of other changes such as DBngin which is worth covering in another post).

    There’s a lot to like about it one of which is how easy it is to begin capturing outgoing emails from whatever application you’re using.

    From the docs:

    Herd Pro provides an SMTP mail server on your local machine that catches all outgoing emails instead of sending them to the world. It displays them in Herds own email client and provides rich debugging capabilities for all types of emails.

    Emails From WordPress in Laravel Herd

    If you’re using WordPress and you’re looking for an extremely quick way to add this functionality to your local installation, add the following code to an mu-plugin:

    <?php
    /**
     * Initializes the PHPMailer instance before it is used to send an email.
     *
     * This action hook is used to configure the PHPMailer instance with the necessary
     * SMTP settings, such as the host, authentication, port, username, and password.
     *
     * @param PHPMailer $phpmailer The PHPMailer instance being initialized.
     */
    add_action('phpmailer_init', function ($phpmailer) {
        $phpmailer->isSMTP();
        $phpmailer->Host = '127.0.0.1';
        $phpmailer->SMTPAuth = true;
        $phpmailer->Port = 2525;
        $phpmailer->Username = 'WordPress';
        $phpmailer->Password = '';
    });

    For example, I have a file – herd-mail.php – located in mu-plugins. Once this is added, any outgoing email from WordPress will be immediately captured and funneled to Herd’s email inbox for review.

    Notes

    • PHPMailer is part of WordPress core so there’s no need to install a third-party library).
    • phpmailer_init is a native WordPress hook.
    • It’s also really easy to set up Xdebug in Visual Studio Code to work with Herd. If you’re interested in learning how, review this article.

    Use Static Variables in Plugin Bootstrap Files

    As nice as event-driven programming can be within the context of WordPress’ hook system, one of the challenges is preventing code from executing every single time the hook is called.

    For example, say you’re writing a function that fires during the init action but something happens in WordPress core that triggers the init action to fire again consequently causing your code to fire once again even though it’s unneeded.

    Multiply this across however many callbacks spread across however many files and general functionality registered with the hook and you may end up affecting performance and/or executing code that has no need to be run.


    Static Variables in Plugin Bootstrap Files

    How an LLM thinks this post would look as an image.

    One way this can happen is in a plugin’s bootstrap. Case in point: Say your plugin is registered with the init action and then it sets up a registry which in turn instantiates a set of subscribers that register services. Repeating this every single time init is fired is unnecessary.

    Here’s a way to manage this:

    add_action( 'init', 'tm_acme_function', 100);
    function tm_acme_function() {
      static $initialized = false;
      if ( $initialized ) {
        return;
      }
    
      $initialized = true;
    
      // ... set up the rest of the function.
    }

    If you know how static variables work, you’re may already be doing this, you’re able to follow the above code, or both. And if that’s the case, there’s nothing else to see here.

    But if not, static variables can be useful in this scenario because static variables maintain state between calls whereas regular variables are reinitialized every time the function fires. This means a static variable retains its value across multiple calls to the function.

    Static Variables and Plugins

    So having an static $initialized flag works like this:

    • $initialized starts as false.
    • When the function runs for the first time, the $initialized variable is set to true.
    • On subsequent calls, the condition if ( $initialized ) prevents the rest of the function from executing, effectively short-circuiting it.

    And because of that, this:

    • prevents redundant execution,
    • optimizes performance by avoiding running unnecessary code (especially as it relates to registering duplicate functionality, running multiple queries, or trashing data unintentionally).

    If your plugin’s bootstrap registers a callback with a WordPress hook, considering using static variables to prevent code from being called unnecessarily more than once.

    Fix: Reschedule Event Error for Action Scheduler

    TL;DR: If you’re using WordPress 6.1+ and aren’t able to schedule any type of job with Action Scheduler, this article explains the problem and a potential fix.

    Specifically, this seeks to address the following message appearing in WordPress 6.1+:

    Cron reschedule event error for hook: action_scheduler_run_queue, Error code: invalid_schedule, Error message: Event schedule does not exist., Data: {"schedule":"every_minute","args":["WP Cron"],"interval":60}

    Resolve the Reschedule Event Error

    Introducing More Logging

    Starting in WordPress 6.1, additional logging was added to WordPress core. Specifically, the patch responsible for this includes the following description:

    Rarely and randomly some of my custom cron events have disappeared. From searching around, I’m not the only one with this issue, and no one else was able to figure out why. I also was unable to debug the issue since wp-cron.php doesn’t log any errors nor have any hooks to try handling them. This patch adds those in.

    trac

    Once this patch was introduced, it also resulted in others experiencing issues with cron and cron-related libraries starting from this ticket and then taking place in a specific forum post.

    When you’re trying to create a schedule but it keeps blowing up.

    And sure, these tickets are helpful as are the comments and the rest of the discussion in the forum. But there are times when we’re working on a specific task with a specific set of dependencies and need a specific solution.

    Action Scheduler and Cron Jobs

    My problem was this: I was trying to to register a job using Action Scheduler and the library wasn’t able to register the schedule because of the aforementioned problem.

    So the fix was to add this function in my code:

    /**
     * Additional logging in WordPress 6.1+ that generates the following
     * message regarding cron schedules:
     *
     * Cron reschedule event error for hook:
     * action_scheduler_run_queue,
     * Error code: invalid_schedule,
     * Error message: Event schedule does not exist.,
     * Data: {"schedule":"every_minute","args":["WP Cron"],"interval":60}
     *
     * This function needs to fire prior to loading Action Scheduler as its a
     * pre-requisite for it to schedule our tasks.
     *
     * This filter seeks to manually add the schedule to the list of schedules to
     * address this bug.
     */
    add_filter('cron_schedules', function ($schedules) {
        $schedules['every_minute'] = [
            'interval' => 60,
            'display'  => 'Every Minute',
        ];
        return $schedules;
    });

    A few things about this code:

    • I don’t recommend using this as a permanent fix for every case. It’s a specific solution for a generic warning.
    • If your codebase is going to be distributed to wide audience, avoid anonymous functions. If you have control over the environment in which it will return, it may be fine.

    On the other hand, if you’re using Action Scheduler, WordPress 6.1+, and are trying to register your own jobs and are seeing this message, this will ensure schedules Action Scheduler uses are available.

    « Older posts

    © 2025 Tom McFarlin

    Theme by Anders NorenUp ↑