5 Ways Underpin Helps You Build Better WordPress Plugins

Posted on:

WordPress is not a framework, it's a management system. When building WordPress plugins, there's a few rough rules on how to use WordPress, but otherwise you're pretty much on your own to build your plugin's architecture. I use Underpin to help me create structure in my plugin. This post talks about 5 of the biggest reasons why I use Underpin, and how it can help you build better WordPress plugins

It Logs Everything It Does

Underpin comes with a robust event logger. This logger allows you to log events that happen in your plugin. There are 8 event types that come with Underpin, and these different event types will do different things when you log to them. Many will write to an error log file, but some will do nothing unless WP_DEBUG is on, or you visit a page with a querystring ?underpin_debug=true.

Underpin is pre-built with several events automatically, but you can (and should!) add more events to your plugin. To do that, you use the logger method that is baked in your plugin's base rfunction, plugin_name()->logger()->add.

If you install the Debug Bar extension, as well as the Query Monitor plugin you can see a log of everything that happened in your plugin. This is really useful when developing your plugin, but it's also helpful when debugging problems on a live site. This also helps a lot when working with things that are hard to debug, such as cron jobs or REST endpoints, because it will write the error log to a file that you can review.

You can also hook directly into the logger and control what it does when an item is logged. This gives you a chance to integrate directly with other things. For example, you could automatically open a GitHub issue if a specific event type was created, or maybe even send an email to a support team.

It Makes Your Entire Plugin Query-able

You're probably familiar with WP_Query. It's a handy WordPress class that allows you to fetch a list of posts from the database as an array of WP_Post objects. Underpin works kind-of like that, only you can query anything used by Underpin. This is because Underpin uses a registry pattern for everything in the plugin. This has [many benefits](), but one of the biggest benefits is that you can access parts of the registry at any time.

For example, let's say you're saving data to the wp_options table, across several keys. Ideally, you would have a way to remove this data from the database should your plugin be uninstalled. Without a registry, you would have to manually add every option you create to this script, and have it removed. However, in Underpin, since everything is in the registry, you can access all of the registered options programmatically, and remove them all.

{
  "name": "acf/snippet",
  "attributes": {
    "id": "block_6105bf49eeb97",
    "name": "acf/snippet",
    "data": {
      "language": "php",
      "_language": "field_60d7f32b1b540",
      "code": "foreach( (array) plugin_name()->options() as $option ){\r\n    $option->delete();\r\n}",
      "_code": "field_60d7f36c1b541"
    },
    "align": "",
    "mode": "edit"
  },
  "innerBlocks": []
}

Not only can this fetch all items, you can also fetch a subset of items using a syntax similar to WP_Query. This is really handy if you need to only get a certain set of registered items. Expanding on the example above, let's imagine that you set all of your registered options to include a parameter called remove_on_unintstall as either true or false. You could use that to filter which options get removed, like so:

{
  "name": "acf/snippet",
  "attributes": {
    "id": "block_6105bf52eeb98",
    "name": "acf/snippet",
    "data": {
      "language": "php",
      "_language": "field_60d7f32b1b540",
      "code": "// Only get options that should be removed on uninstall.\r\n$options_to_remove = plugin_name()->options()->filter( [ 'remove_on_uninstall' => true ] );\r\n\r\nforeach( $options_to_remove as $option_to_remove ){\r\n    $option->delete();\r\n}",
      "_code": "field_60d7f36c1b541"
    },
    "align": "",
    "mode": "edit"
  },
  "innerBlocks": []
}

It Forces a Consistent Syntax

One thing that has always frustrated me about WordPress is just how inconsistent the various APIs are. The process to register a widget is completely different than registering a shortcode, and registering a block is different than registering a custom post type. Creating a role is completely different than creating an admin page, and making a cron job is noting like anything else in WordPress.

Before Underpin, I would find myself always asking "wait…how do I do that again?", then spend some time looking up the various hooks and functions necessary to do whatever it is I was trying to-do. In fact, this frustration was a lot of the reason why I made Underpin in the first place.

With Underpin, everything is done in the exact. same. way. You add an item to the registry, and it handles the actions necessary to actually integrate that into WordPress. You install the loader, and then add it to the registry

To register a custom post type, you do this:

Run composer require underpin/custom-post-type-loader

then use:

{
  "name": "acf/snippet",
  "attributes": {
    "id": "block_6105bf5beeb99",
    "name": "acf/snippet",
    "data": {
      "language": "php",
      "_language": "field_60d7f32b1b540",
      "code": "plugin_name()->custom_post_types()->add( 'post_type_name', [/** Custom post type args **/] );",
      "_code": "field_60d7f36c1b541"
    },
    "align": "",
    "mode": "edit"
  },
  "innerBlocks": []
}

Want to create a custom user role? Run composer require underpin/role-loader

then use:

{
  "name": "acf/snippet",
  "attributes": {
    "id": "block_6105bf76eeb9a",
    "name": "acf/snippet",
    "data": {
      "language": "php",
      "_language": "field_60d7f32b1b540",
      "code": "plugin_name()->roles()->add( 'role_name', [/** role args **/] );",
      "_code": "field_60d7f36c1b541"
    },
    "align": "",
    "mode": "edit"
  },
  "innerBlocks": []
}

This process is identical for any of the ever-growing list of pre-made loaders for Underpin, and if you need a custom loader you can build your own, and enforce this same syntax, as well.

It Makes Your Plugin Extend-able

Remember the option example above? If your plugin has extensions, and those plugins save to the options database too, wouldn't it be nice to be able to delete those items on uninstall as well? This is easy to accomplish with this approach because your extensions would simply register new options against the core plugin. Then, when the plugin is uninstalled, it would be smart enough to know those other options exist.

Underpin also has some handy loaders that allow you to have a little more control over extending, such as the decision list loader, and the ability to add middleware to loaders. These different methods to extend your plugin allows you to have a lot more control over how users extend your plugin. If all-else fails, you'll always have access to do_action and apply_filters, but I've found that I rarely need to use them in my plugins anymore because Underpin's usage pattern tends to accomplish what is necessary without the need for a filter of any sort.

It Simplifies Plugin HTML Templates

If you've ever used more feature-rich plugins like LifterLMS or WooCommerce, you've probably been in a situation where you need to override something the plugin outputs on a page. Most of these plugins allow you to add a file in a specific place in your theme to override different pieces of the markup specific to your theme. Every plugin has their own custom implementation to accomplish this because, and hold on to your hat, WordPress does not have a built-in way to do this.

Underpin has a robust built-in template system that allows you to do exactly this with your plugin. This entire template system is inside of a PHP trait, so it can be added to any class. It's possible to make some templates not change-able by the theme, so you can always disable it if needed.

This helps keep your plugin extend-able, but it also keeps business logic separated from markup. This makes it a little easier to divide up efforts to complete a plugin, since the markup will be in a separate location. This is made even more possible by helper functions, such as get_param, which will get a value that was passed to the template, and provide a default fallback if that value is not set.

Conclusion

Not only will Underpin make your plugin more structured and consistent, it will also fundamentally change how you approach WordPress plugins. Underpin smooths out some of the rough edges in working with WordPress, and makes it feel a bit more like working with modern PHP frameworks, such as Laravel. When used effectively, you will find that your plugins will be easier to debug, easier to extend, and easier to understand.