Joshua's Docs - WordPress - Misc Notes, Tips, and Tricks

I've written a few advanced posts about WordPress; you can find them at: joshuatz.com/tag/wordpress/

Misc / How Do I...

  • Get the post's hero image / thumbnail
    • Echo out HTML: the_post_thumbnail()
      • You can pass CSS classes (space separated) to the function, like so: the_post_thumbnail('medium',array('class'=>'featuredImage responsive-img'));
      • ⚠ Warning: the HTML that WP generates includes fixed pixel dimensions (i.e. non-responsive)
    • Get raw URL string: get_the_post_thumbnail()
  • Sanitize input?
  • Sanitize / secure output?
  • As a plugin or theme author, how to inject CSS into the Admin part (e.g. post editor, etc.)? Here are some options:
    • Enqueue Hooks: The best practice is usually to hook into the admin_enqueue_scripts hook, which can be used for enqueuing both admin styles and admin scripts.
    • Inline: Simply echo out an inline <style></style> block (or inline style="" attribute) when you are also returning HTML that is supposed to appear in the WP editor (e.g. within the add_meta_box() callback function)
      • Bad: This will violate strict CSPs that disallow inline CSS, clutters up DOM, can end up with styles split across codebase, not really usable, etc.
      • Good: Simple to implement
    • Block Registration: If you are trying to enqueue an editor style for a block editor, you can use the editor_style property of the register_block_type function
  • Avoid a hardcoded domain / use dynamic URLs

Troubleshooting

  • If you are getting a lot of errors like Call to undefined function {WP_Internal_Func} that don't make sense, it might be because an internal WP core file got corrupted; try re-installing WordPress (and/or patching files in place) if you think this is a possibility. I had this happen when a linter touched an internal WP file I had open (while stepping through a stack trace).
  • Do not mix up add_action and do_action!
    • If you are trying to "hook into" things, for example to enqueue styles or scripts, you always want add_action.
    • Using the wrong one can lead to some interesting and hard-to-catch situations where callbacks are not firing, or firing in the wrong order.

Collecting Debug Info

For quickly gathering a lot of information about your WordPress installation and environment, there are a few places and commands to be aware of:

  • The WordPress Site Health pages
    • These were added in version 5.2
    • The Info tab provides an information dump, similar to phpinfo(), and has a handy "copy to clipboard" button to make sharing easier
  • The wp-config.php file, and how certain defined constants change how WP emits debug info
  • For PHP, you can use phpinfo() from a PHP page, or php -i from the command line
  • For system info, things like the lshw command (*nix)

The Loop and Queries

Using Post Data Outside the Loop

When trying to use posts outside the main loop, you have two standard options:

  • The safest is usually to avoid using any template methods that rely on the global $post variable, and instead use the alternative versions of the methods that allow passing in a specific post to get the data for.
    • For example, instead of using the_permalink(), use get_permalink($post)
    • Most template methods have a variant that allows passing in a specific post by argument (usually prefixed by get_ instead of the_)
    • If you have a post object, you can also usually manually retrieve a lot of data yourself
  • The alternative method, which is slightly less safe, is to manually set the global $post variable to the post you want to use, and then cleanup once you are done:
    // Assuming we have our *own* post object, outside loop
    $myPost = $myPosts[0];
    
    // You need to manually alter the global variable
    global $post;
    $post = $myPost;
    
    // You also need to call a special setup function
    setup_postdata($post);
    
    // After you are done, make sure to *reset* data
    wp_reset_postdata();

Child Themes

Key thing to remember (per docs):

Other than the functions.php file (as noted above), any file you add to your child theme will overwrite the same file in the parent theme.


Optimizing Performance

There are a lot of "low-hanging fruit" tasks that can be tackled when optimizing a WordPress installation; some are specific to WordPress, while others are true for most websites.

🔗 web.dev/fast is a good general resource for learning more about web performance.

WordPress Specific:

  • Check for duplicate, unnecessary, or generally un-optimized queries
  • Take advantage of server-side caching
    • LiteSpeed Cache is a popular and durable plugin / system
    • Make sure to actually stress-test the system after setting up; check for hit vs miss headers, etc.
  • Optimize images on upload, or use a CDN that can do so
    • Creating multiple sized versions and/or compressing
  • Keep an eye on TTFB
    • With dynamic websites, it is a little too easy to get carried away with dynamic content generation, plugins, and themes. Make sure to use caching whenever possible, and keep an eye on TTFB.
    • In general, the less plugins the better

General:

  • Make sure to enable client-side caching
  • Minify assets / code when it makes sense
  • Don't block rendering with giant JavaScript blocks
  • Be careful about font optimization
  • Take advantage of how much better performance dev tools have gotten!

WordPress Development Environment and Tooling

Assorted WP Dev Tools

These tools don't neatly fit into a category, or maybe I haven't yet tried them but think they look helpful:

  • Duplicator / migration tools:
  • Local (All-In-One local WP development platform)

WordPress CLI / WP-CLI

The WordPress CLI, often referred to as just WP-CLI, is a very powerful tool for automating parts of WordPress development, as well as orchestrating your local dev environment.

There are a lot of great resources on how to use the WP-CLI, including these:

Docker and wp-env

There are lots of different ways to setup a local Wordpress installation for testing and development, but a new entrant that is making things a lot more streamlined is wp-env, which is really a handy CLI and set of scripts to wrap a WordPress Docker image.

You can find more about wp-env by visiting the official docs. Also, this great guide by Hunziker.

Code Formatting

WordPress has established coding standards that it recommends developers follow. Many employers, open-source projects, and existing tooling will also either recommend or require that these rules be adhered to.

How you integrate these standards into your developer toolchain depends on the programming language:

  • PHP Coding Standards
    • These are designed to be parsed and used by the PHP_CodeSniffer tool, which can be used standalone (e.g. CLI, CI/CD), and/or integrated into your IDE of choice.
      • To use it with an IDE, you will usually need to install an extension specific to your IDE. For example, for VSCode, you can use PHP Sniffer & Beautifier.
    • Most projects will use composer to pull in both the rule sets and the sniffer as dependencies.
    • Ideally, you should set up lint and fix commands in your composer (or, could be orchestrated through package.json), which can be used by both devs and CI/CD.

Also, here is a quick tip / gotcha about one of the WP formatting rules:

  • If you get the error: Missing parameter comment, but already are using @param, make sure you have the descriptive text after the type and argument name
    • E.g. change @param number $post_id to @param number $post_id The Post ID.

💡 For some more general notes on formatting PHP code, and using the language with VSCode, make sure to refer to my PHP doc page.

Loading Fake Sample Data

If you need to load a bunch of data into WordPress to test your theme / plugin / etc, you have a bunch of options:

  • XML Files
    • WordPress allows you to posts and content through XML files
    • You could either generate these files yourself, or download pre-built test XML files (example: Codex, motopres)
    • For an official page and test data set, see Codex: Theme Unit Test
  • SQL Insertion Scripts
    • If you are comfortable with SQL / databases, you could easily prepare a SQL insertion file, that when ran, inserts all the sample data directly into the database
    • This could be stored as a prepared insert statement, an exported CSV, or even JSON, that you use something like node to import with a little scripting
    • Pro: Runs faster than XML import, more flexibility in scripting, highly portable
    • Cons: Requires database info, SQL familiarity, etc.
  • Dummy Content Plugins
    • There are multiple WordPress plugins already out there that will insert a bunch of dummy data into your site when installed.
    • One of the more popular ones seems to be FakerPress
  • WordPress APIs

🚨 Warning: No matter which import method you choose, be careful running or importing files you find on the internet.

💡 The WP-CLI Tool has commands to help with both importing XML / WXR files, as well as running stored SQL statements.

Here is a quick example showing how to use the WP-CLI to speed up these import process, and using the official codex theme unit test data:

# Might want to check below repo first, to make sure this is indeed the most up-to-date data set
curl -o wp-test-data.xml https://raw.githubusercontent.com/WPTT/theme-unit-test/HEAD/themeunittestdata.wordpress.xml
# You can skip below line if this plugin is already installed and activated
wp plugin install wordpress-importer --activate
wp import wp-test-data.xml --authors=create

Debugging Queries

One of the most popular methods for debugging and evaluating WP queries is with the Query Monitor plugin. Query evaluation is also included in the Debug Bar plugin.

However, if you want to debug through your IDE, there are a ton of WordPress hooks that you can use as callbacks to a debug or print statement.

For example, if we wanted to see every SQL query that flows through, we could use the query hook:

add_filter('query', function($query) {
	// $query will be a SQL string
	// below line calls the XDebug extension to trigger a breakpoint, whether or not one is set in the IDE
	xdebug_break();
});

OOP in WordPress

Using OOP with Callbacks and Registrations

A lot of WP hooks and various functions will take a function that you specify, as a callback to be called later. Many guides and docs will show this argument being the type of a string, set to the literal function name. E.g.:

// Implementation
function myplugin_enqueue_scripts() {
	// ...
}
// Hook - uses function name as literal string
add_action('wp_enqueue_scripts', 'myplugin_enqueue_scripts');

However, here are some alternative OOP-based approaches:

External class, non-instantiated, where method is static:

// The only way (AFAIK) is to use the class name, as a string
do_action( 'ACTION_NAME', array( 'My_Ext_Class', 'my_static_method' ) );

// If the class is namespaced, make sure to prepare the string correctly
do_action( 'ACTION_NAME', array( 'My_Namespace/My_Ext_Class', 'my_static_method' ) );

// This does NOT work
do_action( 'ACTION_NAME', array( My_Ext_Class::class, 'my_static_method' ) );

External class, instantiated, public method:

$my_handler = new My_Ext_Class();
do_action( 'ACTION_NAME', array( $my_handler, 'my_method' ) );

Additionally, some WP methods (such as the add_filter function) will actually accept an in-line function (across many programming languages, often call a lambda, or anonymous function):

// Instead of this...
add_filter('the_title', 'make_bold');
function make_bold($title) {
	return '<i>' . $title . '</i>';
}

// You can use:
add_filter('the_title', function ($title) {
	return '<i>' . $title . '</i>';
});

Registering Hooks in the Constructor

Tempting, but it seems as though the consensus is that it is not a wise idea.

  • It violates the role of the constructor (should be concerned with internals, preparing the object for use, and is instead talking to WP)
  • Hides implementation details
  • Makes testing harder (Class is now tightly coupled with WP logic, hard to decouple)

A much better place is usually a public member of that same class, such as init() or register_hooks().


WordPress Plugin Development

Some relevant resources

Gutenberg Block Development

Please refer to my separate doc page.

Plugin File Structure

Even if you have a /src directory, you are always required to have a YOUR_PLUGIN_NAME.php in the root folder. Additionally, this root PHP file is the one that tells WordPress the meta information about your plugin that is displayed to the user under their plugins panel - controlled through the required header comments.

Why an Includes Folder Outside Src?

You might notice that a lot of plugins use both /src and /includes for PHP files. I can't find any official guidance from WordPress on best-practices here, but it seems like the norm is for /includes to be used for classes, especially if they are namespaced. This also makes sense given the folder name; you are often pulling these files in via include (or require).

In contrast, /src often contains the actual code that gets called right away by the main plugin entry point ({my-plugin}/my-plugin.php), and is responsible for things like initialization of classes, registering hooks, and in general, making sure things get put into place.

Some plugins also will do things like auto-load (with PSR-4) the entire /includes directory, but that is a whole other can of worms.

includes vs inc?

Up to you; there doesn't seem to be one agreed upon standard, even among big and popular plugins. For example, Yoast uses inc, whereas UpdraftPlus and CoBlocks both use includes, and classic-editor doesn't even use a namespaces or includes folder at all.

As far as I can tell, maybe the ecosystem is moving towards includes as the standard?

Namespaces

At this point, pretty much all WordPress environments should be supporting a version of PHP that in turn supports namespaces. With that in mind, I would highly recommend using them.

Why? Ever notice how plugins and themes, especially older ones, will have class names and functions with long prefixes? Like geo_target_pro_load_scripts() instead of just load_scripts()? Often, this has been because (by default) PHP doesn't really treat each file as a separate isolated scope; instead, if two PHP files declare a function with the same function name, and both end up getting evaluated, they will conflict and one will override the other.

Namespaces are a great way to avoid this issue outright. They let you scope areas of code under a specific name, and don't pollute the global scope.

🔗 Here is a blog post on using PHP namespaces with WordPress.

You are not required to use namespaces, but they are a good idea. You can still find lots of plugins that don't use namespaces, or in the case with Classic Editor, use a single ~1K LOC PHP file. However, these types of setups are probably out of necessity - namespaces made it into PHP in 2009 (v5.3), but WP has been around since 2003!

To Autoload or Not Autoload with Composer

If your plugin grows to a certain size, you might find it tedious to manually require all the namespaced classes inside of a directory. Some developers will choose (or be tempted to choose) in this situation to use Composer, with PSR-4 autoloading.

However... this gets really tricky when it comes to WordPress. The users of your plugin should not be expected to have Composer installed, nor does WordPress have a system setup in place to support a composer-based dependency system.

I don't want to get too deep into this topic, so my general perception of where things are at is:

  • WordPress, at this time, is simply not an ideal environment to use Composer autoloading with. Even if you deliver the generated vendor folder, you can still run into conflicts with other plugins.
  • It is really not that difficult to manually manage PHP includes / requires, and you can find many examples of large plugins doing this and avoiding the whole issue
  • If you really need to use Composer and the /vendor folder, such as when using external dependencies, make sure you take the proper steps to avoid conflicts with other plugins

And here are some write-ups on the complexities of using Composer with WordPress:

Markdown Source Last Updated:
Mon Jul 19 2021 01:41:46 GMT+0000 (Coordinated Universal Time)
Markdown Source Created:
Sun Sep 13 2020 12:38:57 GMT+0000 (Coordinated Universal Time)
© 2024 Joshua Tzucker, Built with Gatsby
Feedback