Latest News

How a Conversation About Filterable Autoloader Paths Pleasantly Derailed My Sunday Afternoon

My good friend Gary Kovar posed a really interesting question on Twitter yesterday:

This led to some conversation both on Twitter and separately on Slack, and I began to realize that this dovetails nicely into that series about re-envisioning PHP development for WordPress I’ve been alluding to for months. Consider this post the first in that series.

Dependency Management in WordPress

Here is a real-life problem I encountered on a client project while I was working in agency: the client was using one plugin to save their media uploads to Amazon S3, and they wanted another custom plugin developed that, when creating a new site in WordPress multisite, a hook would be triggered that creates a new bucket on S3 for that site’s media.

The client had one of the most sophisticated build, testing, and deployment setups for WordPress I have encountered yet in my career. They were using Composer for installing their WordPress plugins, and so naturally, when I began building this custom plugin, I created a composer.json file and required the latest version of the Amazon SDK as a dependency, then got to work on building out the functionality to automatically create the buckets.

Everything was going great. That was, at least, until I got to the point where I’d actually attempted to install my plugin alongside all of the other plugins in their multisite installation. It turned out that the plugin that was uploading media to Amazon was using an older version of the SDK, whereas I had coded my plugin against a new version. When their build processes attempted to pull my plugin into the site alongside the other one, everything appeared to work correctly because the other plugin was shipping the Amazon library alongside its code, instead of downloading it using Composer.

If you’re not familiar with Composer, typically what happens when two libraries (or in this case, WordPress plugins) require incompatible versions of the same library, Composer will exit the install process and inform you of the incompatibility. In this case, because the other plugin wasn’t using Composer, I wasn’t aware of the incompatibility.

There’s an additional wrinkle here, too, because in WordPress, the core application does not have a process to check for and resolve all of the dependencies within a plugin or theme. Thus, if one plugin is installing a library via Composer, and another one ships it with the plugin, it’s possible that whichever plugin loads first will load the dependency. In a well-coded plugin, developers guard against loading a library again if it’s already been loaded to avoid throwing errors in PHP, and well written libraries (such as Amazon’s) can avoid throwing errors if there’s good compatibility in their APIs across versions.

This was the case for this particular project. Ultimately, I came to discover that my code wasn’t working correctly because the other plugin’s library was loading first. We wound up forking that library, checking out its beta version (which used the newer version of the SDK and was nearly released), and went on our merry way.

Why is this a WordPress problem?

The short answer is that it’s complicated. Nearly 40% of the top 10 million websites on the internet today are powered by WordPress. Not every WordPress site is maintained by a developer – in many cases, they’re maintained by non-technical people for whom WordPress was an intuitive solution to help them get their vision online. Those folks don’t care about Composer or PHP dependency resolution or namespace collisions. They (rightfully) just want things to work.

For the rest of us, this means we try to come up with innovative solutions to be able to use the tools that make our jobs easier. Because WordPress core doesn’t have a mechanism for resolving a plugin and theme’s dependencies before it loads them, as plugin developers who work in sophisticated ecosystems like that of my former client, we have to rely on alternative solutions like Mozart or Imposter to package our vendor directory with our project and prefix its class namespaces with our own to avoid naming collisions in PHP.

There has to be a better way, but it’s clear that whatever the solution might be, it’s neither easy nor obvious, or as a community, we may have resolved it by now.

Let’s Talk About Extensibility

So, back to Gary’s question and the conversation that followed, which still pertains to workarounds for the dependency management issues in WordPress, but also includes some nuances around class inheritance, interfaces, and incorporating developer guardrails into open-source projects that I find particularly compelling.

In our conversation, we were discussing class property and method visibility in PHP, and Gary shared with me this filter in the MySQL class of SquareOne, an open-source framework developed by the fine folks at Modern Tribe. Though not about autoloading, I’m hoping the correlation here is clear. Let’s look at this snippet (condensed from the link above):

class MySQL implements Backend {
	const DB_TABLE = 'queue';

	private $table_name;

	public function __construct() {
		global $wpdb;

		$table_name = $wpdb->base_prefix . self::DB_TABLE;

		/**
		 * Filter the table name used for queues on this backend.
		 *
		 * @param string $table_name Table name with $wpdb->prefix added
		 */
		$table_name = apply_filters( 'tribe/queues/mysql/table_name', $table_name );

		$this->table_name = $table_name;
	}

There are two important bits in this example:

  1. The private keyword on the $table_name variable
  2. The call to apply_filters, which provides developers with the ability to override the default value of $table_name

Historically, actions and filters in WordPress have been offered as an easy way to override the default behavior of the code that plugin authors write. Like the end users of WordPress itself, developers who work with the platform have a broad range of experience – especially when it comes to object-oriented programming – and these hooks offer an arguably simpler way of overriding the original programmer’s logic.

A neat thing about PHP, however, is that it also accommodates for changes. Consider this alternative to the snippet above:

class MySQL implements Backend {
    const DB_TABLE = 'queue';

    private $table_name;

    public function __construct() {
        global $wpdb;

        $this->table_name = $wpdb->prefix . self::DB_TABLE;
}

But Jeremy… you just took out the filter.

You got me. :)

In this case, MySQL is instantiated somewhere, and it implements an interface, which means it has a whole set of methods that can be called on it. Instead of filtering just the table name, Modern Tribe could consider allowing for modification of the entire class by filtering the places where it is instantiated, and requiring an interface in that callback.

Here’s a contrived example. Let’s say I’m a developer who wants to extend the MySQL class of SquareOne in my own plugin. Let’s assume, for now, that I only want to override the value of the table name. I could create a class that looks like this:

use Tribe\Libs\Queues_Mysql\Backends\MySQL;

class MyPersonalMySQL extends MySQL {
    protected $table_name;

    public function __construct() {
        global $wpdb;

        parent::__construct();

        $this->table_name = $wpdb->prefix . 'my_personal_queue';
    }
}

Notably, PHP allows you to weaken the access level of class properties and methods for extending classes. Thus, it’s okay here to change the visibility of $table_name from private in the parent class to protected here. Additionally, because I only want to change the table name here, my class can inherit all of the other default behavior from the MySQL class. That said, this does complicate both my approach and Modern Tribe’s. In their case, if they want me to continue to be able to override the class, they have to add a filter to wherever that object is being instantiated. It’d look something like:

$mysql = apply_filters( 'tribe/queues/mysql', new MySQL() );

And I, of course, would need to add the filter myself, AND extend the class, when I could have just filtered the table name in the first place:

add_filter( 'tribe/queues/mysql', new MyPersonalMySQL() );

Versus:

global $wpdb;

add_filter( 'tribe/queues/mysql/table_name', $wpdb->prefix . 'my_personal_queue' );

If extensibility is the goal, then there’s an argument to be made that we as open-source developers could be creating more filters in order to accommodate both procedural and object-oriented developers alike. However, in my mind, the argument for making codebases easier to understand and to raise the bar for WordPress developers alike lies in interfaces.

A Case for Interfaces

In his tweet, Gary asked if there were any open-source examples where the path to a Composer class autoloader is filterable. Using the previous examples as a guide, let’s examine what plugin instantiation might look like from the perspective of a WordPress developer who wants to modify plugins with their own autoloaders. Consider the following plugin:

/**
 * Plugin Name: Incredible Plugin
 * Description: Does something incredible!
 */

namespace JMichaelWard\IncrediblePlugin;

require_once __DIR__ . '/src/IncrediblePlugin.php';

function init( Autoloadable $plugin ) {
    $plugin->init();
    
    return $plugin;
}

add_action( 'plugins_loaded', function() {
    $plugin = apply_filters( 'init_incredible_plugin', new IncrediblePlugin( __FILE__ ) );
    init( $plugin );
} );

The plugin itself might look like this:

namespace JMichaelWard\IncrediblePlugin;

class IncrediblePlugin implements Autoloadable {
    private $plugin_file;

    public function __construct( $plugin_file ) {
        $this->plugin_file = $plugin_file; 
    }

    public function init() {
        $this->autoload();
    }

    public function autoload() {
        require_once plugin_dir_path( $this->plugin_file ) . '/vendor/autoload.php';
    }
}

The beauty here is that this code accomplishes a couple of things.

First, the init method in the main plugin file uses a type-hint against the Autoloadable interface. In this scenario, this means that another plugin author who needs to modify this plugin’s primary class in some fashion could do so simply by extending the IncrediblePlugin class (since it already implements Autoloadable), or that – if somehow needed – they could pass in an entirely new plugin object that meets the requirements of the behaviors for the rest of the plugin, so long as it also extends Autoloadable. If Gary simply didn’t want the autoloader to run, he could override the method and simply have his new class do nothing.

Second, what this achieves – and this is notable because no concept like it exists in WordPress core today – is that because the init method in the main plugin file uses a type-hint, if another developer extended it and forgot to implement that interface, PHP will throw a fatal error and warn that developer that they did not meet the requirements to initialize the plugin.

Believe it or not, errors are good. Great, even. Or, I should clarify, that’s the case when they are part of the development process. Errors provide context for what you haven’t done correctly, and through the process of creating interfaces for your classes, you are establishing a contract for what is required to alter that application, whether you’re the one doing the alteration, or if you’ve created a developer API intended for others to extend.

Conclusion

In this post, I covered the following topics:

  • Challenges associated with dependency management in WordPress
  • Proposed approaches for adopting broader filter usage
  • Weakening restrictive property and method visibility during class extension
  • A case for using PHP interfaces

I expect that a few of these topics will surface again in future editions of this series. For instance, the benefits of interface usage in software development are a significant part of the first 5 principles of S.O.L.I.D. Type-hints, too, help ensure that the values we pass into functions throughout our codebases are of the type we expect, and using them can really help us spot errors during the course of developing new features.

I’ll close this inaugural post in the Rethinking PHP Development in WordPress series by thanking Gary once again for the discussion that helped get my wheels turning. As I seek one or more mentors to increase my own knowledge in 2021, I’m reminded that our friends, colleagues, and peers are often the best mentors of all.

Seeking Mentorship

I’ll keep this short and sweet: one of my professional goals for 2021 is to establish formalized mentorships with one or more of my peers in the tech community.

Through the course of my 8-year development career, I have aimed to increase my knowledge and improve my technical skills in a variety of ways. Like others, I’ve read numerous books to learn about new languages, design patterns, tools, and approaches to engineering. I’ve attended conferences and local meetups to build relationships with others in my field, and of course, to attend presentations and discover what additional tools I can add to my skill set.

In July of last year, I joined the product team at Rocketgenius. There, I work on the add-ons crew where I am responsible for developing and maintaining our collection of WordPress plugins that further extend the functionality of the Gravity Forms plugin.

Moving from the agency space into product has long been one of my career goals, because I think it provides me with the opportunity to apply a lot of the “ideal engineering” scenarios that seem prevalent in all that book-learning: test-driven development, continuous delivery and deployment, domain-driven design, agile development, etc. I’ve had exposure to certain aspects of these concepts in my agency work thanks to working with large corporations that adopt them, but because those projects are so fluid, I’ve typically used what had already been established by our clients instead of working to build something of my own.

Needless to say, I recognize that there are shortcomings in my current skill set that I can work to improve upon in 2021, and I think it would be helpful for me to collaborate with one or more folks over the course of the year. In my mind, this collaboration would entail semi-regular (monthly, perhaps?) calls to discuss challenges working in the technical field, and to share resources and ideas around how to grow one’s professional skill set.

Some initial topics that come to mind:

  • Modernizing legacy codebases (in particular, those which can be extended by end users)
  • Containerization
  • Developing and maintaining custom build processes
  • Test-driven development
  • Domain-driven design
  • Engaging with open-source communities
  • Avoiding burnout

If you have expertise in any of these areas and would be interested forming a mentor/mentee relationship, and especially (but not necessarily limited to) if we already know one another, then please get in touch. I think there’s likely a lot we can learn from one another.

2020: The Lost Year

On March 6th of this year, I posted about leaving my role at WebDevStudios is pursuit of new opportunities. It might as well have been a decade ago.

That weekend, I celebrated “funemployment” by hanging out with some of my closest friends at The Lab in St. Paul, a conceptual taproom of sorts where brewers and beverage makers can try out new recipes in small batches that they can sell to those with adventurous palates. I didn’t have a new role lined up just yet, but I was studying and working through online tutorials to teach myself Python. At the time, I was in the middle of an interviewing process with a company where I’d have the opportunity to branch out beyond both WordPress and agency and finally get to experience the world of product development, all while becoming acquainted with a programming language that was both new and yet somehow familiar to me.

Last winter, lots of tech folks in the Twin Cities had started co-working occasionally at BlackStack Brewing, in large part because of 1) the ample taproom space, and 2) BlackStack’s change in business hours and structure; while still a brewery, they’d also begun marketing themselves as a coffeehouse by day. With fast wifi, a low-key environment, and a staff that was friendly to folks settling in and writing code throughout the day, I made plans for it to be the place where I’d spend my first day with some downtime to write some code for fun.

Much to my surprise, when I showed up to BlackStack that following Monday, I ran into some friends and former colleagues alike. The pandemic hadn’t completely settled here in the U.S., but the news was becoming increasingly scary by the day. Folks were showing up throughout the day, some (such as myself) already very mindful about not shaking hands, but of course, in early March, nobody was wearing masks yet, and a group of us went out for lunch together at The Naughty Greek, a fast casual Mediterranean restaurant just up the street.

Those are probably my last tangible, positive memories of The Before Times, though of course they were already tainted with my experience of working with an epidemiologist for almost 4 years earlier in my career and the worry about what was to come.

Later that week, I hosted my last in-person weekly board game night, something I’d organized for a full four years prior. My friends Nick, Bela, Austyn, and I played Great Western Trail, a game about herding cattle from Texas to Kansas City, where you later ship them off by train. Two days later, on Saturday the 14th, my friend Will was the last invited guest to our home for the entirety of this year. He, my partner Abby, and I played the cooperative game Horrified, which I’d purchased in South Dakota just two months prior when Abby and I visited her folks, oblivious to the fact that a global pandemic was looming.

Of course, we all know the rest. And it’s hardly been just the pandemic that’s resulted in this year being, perhaps inarguably, the most terrible of our lifetimes.

Being Thankful

Employment

The Python job that I was interviewing for in March became suddenly unavailable as a result of the economic downturn from the pandemic. In May, I was fortunate enough to land a position working at a local startup, but it quickly became evident in the subsequent weeks that the culture and development stack wasn’t a right fit for me. I turned in my resignation at the end of June, and starting fresh in my search. That very day, I heard from Steve Henty about an engineering position on the product team at Rocketgenius. After a couple weeks of interviewing with him and various members of the team, and learning more about the company’s mission and vision, I decided that staying in the WordPress space but making the switch over to product was going to be the right fit for me.

Man, am I ever glad I did.

I think I have a hard time articulating sometimes what it is I’m looking for out of my career, or even what I want from an employer. Every company that I’ve worked for in the WordPress agency space has done something smashingly well. Some have given me the opportunity to learn and grow, either by paying for memberships to online tutorial services, or helping sponsor travel to attend or speak at a conference. Some have recognized the benefits and challenges associated with remote work, and have taken great strides to do what they can to make their culture open and inclusive. Some have recognized holidays as official company holidays where others haven’t. Some have granted autonomy and trust to you, as an employee, to make decisions and take actions in the interest of the company, without the need for a special sign-off – making the pitch is usually enough.

I don’t know how they do it, but I’ve been with Rocketgenius now for 5 1/2 months, and somehow they do it all, and I’ve worked at enough places at this point in my career to know not to take this for granted. Every day, I am astounded by the thoughtfulness of my colleagues and leadership, the kind ways with which we interact, and the hard work and care that everyone puts into our products. We plan and communicate and check in with one another in ways that I’ve only seen glimpses of in past roles, and it’s made going to work each day something to look forward to; a welcome distraction from the madness of everything else.

Friendship

Since that festive gathering in the beginning of March, I’ve managed to see my friends a handful of times this year, at extremely social-distanced outings in large backyards, city parks, and the front lawn of the Minnesota State Capitol. I’m grateful for those moments of simply reconnecting with one another, of catching up, of celebrating each other’s wins, and of hoping that we can all hug one another again when we finally get a calendar with a new number on it.

My friends have still managed to make this year special through trivia nights, board game tournaments, scotch nights, meetups, impromptu online gatherings, and the occasional telephone call (hi Josie!).

Love

Above all else, I don’t know what I would have done this year if not for Abby. We celebrated our 12th anniversary at the end of October, and we’ve largely spent the year doing a handful of activities: taking walks around Lake Phalen largely as an excuse to run our car, playing a board game I can talk her into playing, watching a movie she can talk me into watching, finding occasions to use the oven instead of the microwave, and sending each other cute stories we find and as many animal-related pictures and gifs as possible. She supported me by remaining optimstic about my career change, even after it quickly got scarier than I’d expected, and I think what I’ll most cherish about this year is her dedication to getting me out of my comfort zone and encouraging me to take a walk in the neighborhood or go sit in the park for awhile. We started the year playing trivia at our local brewery nearly every Wednesday, and pandemic or not, I’m looking forward to warmer summer nights when we can go hang out in the park some more: the sun for her, the shade for me.

Looking Ahead

There’s a whole lot of stuff that’s going to be the same when we wake up tomorrow morning as it was through most of this year. Still, I think there is reason for optimism and hope, and for the new year, I’m planning on a small set of goals to keep me focused and motivated:

  • First and foremost, giving more than I did last year. I wrote a bit about some of my charitable giving over the summer. I’ve given more since then, both via official and unofficial channels, but I recognize that there’s more that I can do and now that my situation has thankfully stabilized a bit, I’m seeking opportunities to do more of that. One thing I’m considering is a monthly donation to Second Harvest Heartland beginning in January.
  • Reading more. A lot more. I’ve fallen out of the habit, and things were already sufficiently dire before 2020. I only managed to finish three books this year, and two of them I’d started in 2019: We Were Eight Years in Power: An American Tragedy by Ta-Nehisi Coates, Someone Who Will Love You in All Your Damaged Glory by Raphael Bob-Waksberg, and The Answer Is… Reflections on My Life by Alex Trebek. I’m not setting a hard goal, but more non-fiction, non-memoir I think is a good place to start. That said, I’m still trying to get through Exploded View by Sam McPheeters (made all the more difficult since it’s a police story) and The First Bad Man by Miranda July.
  • Writing more. Amazingly, this is my 16th blog post this year! I even got my first-ever reader e-mail the other day, related to my post about Xdebug 3. I’m hoping to write more about technical topics, and in particular, I want to write more about the process of learning. I’ve been less motivated to spend additional time in front of a screen that isn’t something mindless like video games or engaging like playing a board game with my friends, and I feel that my tech exploration has suffered as a result. I’m hopeful that we might start to see things normalize at some point this year, and that I’ll regain that excitement to learn and share what I’m learning more.

That last bullet sorta dovetails into some professional goals I have, too. In a particular, I’m working goals at work to open-source some tools, and a plan for sharing/presenting information about those efforts in some fashion. More on that at another date, I suppose.

To sum things up: so long, 2020. You were terrible, but I’m thankful for the good stuff.

Add Featured Images to Old Posts Using “Set Post Thumbs” for WP-CLI

Hey y’all! While I’ve admittedly been delaying my series about rethinking PHP development in WordPress first because of this global pandemic and all the election nonsense, and now more recently because of sheer procrastination because I’m not entirely sure how to start it, I wanted to take a minute to let you know that I made a thing!

Early this year, I got together with my buddy and former bandmate, Matt Semke, to talk about his website, Cats Will Eat You. For a number of years that I’m sure I should know by now and will surely get wrong (14, I think?), Matt has been publishing new art on his website every single day. But, even though his site was getting a lot of love content-wise, it wasn’t getting much attention from a web development and maintenance standpoint.

Matt and I got together over a couple of beers to talk about updating his website from WordPress 3.4(!) to the latest version, getting him onto a better hosting plan, and brainstorming some ideas about how to improve his site overall and make it better for both him and his visitors. This is one of my favorite (and only, at the moment!) freelance projects for a couple reasons: it means I get to help out a good friend who makes cool art, and it’s a low-impact obligation on my time, because I pretty much check in on it periodically and work on it when I have the interest and energy.

That said, there’s one other cool thing I love about this project: it gives me the opportunity to generalize stuff I work on specifically for Matt, and open-source the reusable bits. This weekend is the first such instance of that.

The Problem

With over 14 years of every-day art, Matt’s got thousands of posts on his site. He’s been using WordPress for a very long time; so long, in fact, that the content on his site predates the existence of features we take for granted today, like custom post types, featured images, the REST API, and so on.

In reviewing his theme over the weekend, I realized that I wanted to move some of his data around, get rid of some of the cruft, and, most importantly, set a featured image on each of his posts. So, I spent some time working on a solution, and then as I realized, “hey, some other folks might like this, too”, I started extracting out the parts that weren’t specific to his site, but to anyone’s. And then I made a repo. And then I connected that repo to Packagist. And then I learned how to make it installable using WP-CLI, because even after all this time, I’ve still never done that.

The Solution: Set Post Thumbs

In short, you can now do these two things:

  1. Run wp package install jmichaelward/set-post-thumbs from the command line of your WordPress project.
  2. Run wp thumbnail set --all, and WordPress will query all of the posts on your site that do not have a featured image, and then check whether there are images in your post. If there are, it’ll attach the first one.

The utility comes with a couple of additional commands to report back on which posts had more than one images, and which posts couldn’t set a featured image because it didn’t have any. And then you can run a cleanup command at the end to clear out all the excess meta.

It’s a little thing. It does a fairly specific task, and not everyone might need it. Additionally, I’ve tried to set it up so that it’s extensible, so that you can make modifications to the default way the tool works, in case your featured image lookup requirements differ.

What I really enjoyed about working on this is that I could take something I was building for a friend and open-source it to the broader community. Drop me a line if you wind up trying it and if you have any questions or run into any issues. I recommend using it only on a development environment where you have the opportunity to review the changes you’ve made. But, if you’ve got a site that has lots of posts without featured images, and you need a way to attach something to those posts, this should do the job for you.

Xdebug 3 is out, and you’ll need to update your settings

As a PHP developer (and particularly as a user of PhpStorm), I’m a huge fan of using Xdebug for step debugging my applications. If you’ve worked with me for any length of time, I’ve already evangelized to you about these tools, most likely ad nauseum. For the rest of you, perhaps that’s a tale for another time: the short answer is, Xdebug makes it easy to work with PHP applications and understand data state during runtime, and PhpStorm itself makes it easy to connect to Xdebug.

For newer developers, or developers who use a different IDE, or developers (like me) who sometimes struggle a bit to read the docs, configuration can be a challenge. And sometimes, troubleshooting a configuration even more so.

All that said, there are loads of examples on the web for a basic step debugging configuration for Xdebug. They typically look something like this:

[Xdebug]
zend_extension=/path/to/your/xdebug.so

xdebug.default_enable=1
xdebug.remote_enable=1
xdebug.remote_autostart=1
xdebug.idekey=PHPSTORM

# For configurations that require a different default port
xdebug.remote_port=9001

Configuration for Xdebug 3

Almost every line in the above configuration has changed with Xdebug 3. As such, when you upgrade, you’ll also need to update your php.ini file (or xdebug.ini, or wherever it is that you store your PHP module configurations). The equivalent to the previous example is:

# Configuration for Xdebug 3
[Xdebug]

xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.idekey=PHPSTORM

# For configurations that require a different default port
xdebug.client_port=9001

# Note: Xdebug 3's default port is now 9003 instead of 9000,
# so even if you don't use this setting, you'll still 
# need to update your IDE settings to get it to connect.

Derick Rethans has published a guide to upgrading from Xdebug 2 to 3 that details the many changes between versions. Importantly, it simplifies a lot of the configuration, and some of the options are now grouped into a single value. For instance, if you want to enable step debugging, but you also want to continue seeing stack traces, you can pass in a comma-separated set of values to xdebug.mode:

xdebug.mode=debug,develop

As usual, there are many different options that I’m not going to cover in this very short post. Importantly, however, there are enormous performance benefits to this new release, and it’s backward-compatible to PHP 7.2. If that meets your needs and you’re already a user of Xdebug, I highly encourage you to upgrade today, and to consider donating to the continued development of the project.

Update: November 30th

I’ve discovered at least one item of interested since publishing this post last Thursday:

Setting xdebug.start_with_request to yes is the equivalent to always having Xdebug running. With Xdebug 3, this will also generate a PHP warning in your logs with every request you make that doesn’t have a client listening on your debug port. This might be less-than-ideal for development scenarios when you only want to trigger Xdebug when you’re actually debugging. In this case, you should set the value to trigger instead.

The trigger setting for xdebug.start_with_request changes the behavior slightly: it requires that you pass a value to your $_GET or $_POST request in the browser to get Xdebug to listen (or to activate a browser extension such as Xdebug Helper for Firefox) in addition to triggering debugging in your client, such as PhpStorm.

On the WordPress development side, I’m still tinkering with Xdebug 3 to understand how to correctly trigger the debugger from the CLI. The upgrade guide documentation seems clear about what needs to happen (e.g., setting an environment variable before running a PHP script), but it doesn’t seem to be triggering for me. I’ll possibly update this post again and or create a new post with additional findings once I get that sorted.

In the short-term, setting xdebug.start_with_request to yes does in fact make it easy to trigger breakpoints easily in either the browser or the CLI, but until I find a way to disable the warnings to my PHP error logs, it’s not my preferred approach.