Posted on March 1, 2015
The other day, I tweeted out that I believe there is hesitancy in the WordPress world to use autoloaders. Don’t believe me?
I feel like there’s hesitancy in the WP world w/ autoloaders. spl_autoload_register is your friend. Namespaces NOT required.
— Justin Sternberg (@Jtsternberg) February 25, 2015
I think there is hesitancy mostly because of lack of knowledge, but I also think there’s a bit of misinformation/misunderstanding, so I’m going to do my best to clear some things up in this post. To be clear, this is an advanced developer topic, and if you’re just getting started with OOP (Object Oriented Programming), then I’d suggest heading over to this post instead.
But if you’re just past that OOP n00b stage, or even if you’ve been around the block, there’s a decent chance you haven’t yet taken advantage of an autoloader. At best, you’re require
-ing all you’re other files, and at worst, you’re stuffing all your functionality into one god-class (say it ain’t so!).
One common file-load pattern is the ‘include all-the-things’ pattern. It looks like this. You could make the argument that it gets the job done, but you’re not earning any DRY style points in the process, and you’re definitely not optimizing, but loading all-the-things indescriminately. A similar-yet-more-DRY method is to use something like glob
to loop through and include all the files in a directory.
Ok, so you recognize the downsides of the first pattern, so instead you opt to only include your files as you need them. That can look like this:
class My_Main_Stuff { public function get_table( $table_arguments ) { require_once 'includes/table.class.php'; $table = new My_Table_Stuff( $table_arguments ); return $table; } }
So in this example, at least you’re only loading the file when you need to get an object. But if you wanted to use My_Table_Stuff
anywhere else outside of My_Main_Stuff
, it could/would break unless you again do your require_once
. Again, you’re losing DRY style points, and cluttering up your code.
In PHP, there are as many ways to load files as there are ways to create and use classes and objects. But thanks to our soon-to-be new friend, spl_autoload_register
, we can optimize, and DRY our loading completely. From the php manual, spl_autoload_register
is defined:
spl_autoload_register — Register given function as __autoload() implementation
wat? Thanks, but no thanks php manual.
Basically, what spl_autoload_register
allows you to do is set a callback that will listen in whenever you call a class that isn’t yet included. You get to step in JUST BEFORE you get the dreaded
“Fatal error: Class ‘Forgot_To_Include’ not found in your-path/your-file.php on line X”
This allows you to include your file just-in-time, and only include it if the class actually gets called. This is super-duper handy, and setting up my class/file-loader is usually the first thing I do when starting up a new WordPress plugin. But there are definitely some caveats to keep in mind when creating your callback, and we’ll discuss those next.
The first thing to keep in mind when using an autoloader is that you will need to make sure your classes each belong to their own files. This is a good practice to follow anyway, so hopefully we’re preaching to the choir here. The reason for this is so that the autoloader can include ONLY that file ONLY when it’s needed. If you do it any other way, you’re pretty much defeating the point of using an autoloader.
A standard practice that I follow is to have an includes
folder in my plugin, and put my class files in there. You can obviously name that whatever you want. Once you have a standard location for these files, you need to pick a standard file-naming practice. If you want to make it super-easy, You can give your files the exact same name as your class, so using our example above, you would have an includes/My_Table_Stuff.php
file. Since WordPress still supports php 5.2, I use class prefixes as a pseudo-namespacing technique. While My
is NOT a good prefix to use, that’s the example i’m going with, so all my classes would be prefixed with My_
. If I want, I can drop that prefix from my file names, making my example includes/Table_Stuff.php
. Since we’re modifying file names, let’s go ahead and make them all lowercase, and replace the underscores with hyphens. This leaves us with includes/table-stuff.php
. That works for me. It’s a good descriptive file name without the redundant prefix.
Now that we’ve determined our file structure, we have the basic ‘rules’ our autoloader should follow. They are:
- The class must not already be loaded/exist. This rule is auto-followed by the autoloader. It only fires for undefined classes.
- The class we want to load must have start with the
My
prefix. - The class file must be located in the includes folder.
- The class file will have the same name as the class except the prefix will be removed, it will be lowercased, and the underscores will be replaced with hyphens.
Let’s create our autoloader callback.
/** * Autoloads files when requested * * @since 1.0.0 * @param string $class_name Name of the class being requested */ function my_class_file_autoloader( $class_name ) { /** * If the class being requested does not start with our prefix, * we know it's not one in our project */ if ( 0 !== strpos( $class_name, 'My_' ) ) { return; } $file_name = str_replace( array( 'My_', '_' ), // Prefix | Underscores array( '', '-' ), // Remove | Replace with hyphens strtolower( $class_name ) // lowercase ); // Compile our path from the current location $file = dirname( __FILE__ ) . '/includes/'. $file_name .'.php'; // If a file is found if ( file_exists( $file ) ) { // Then load it up! require( $file ); } }
That snippet is heavily commented, but in summary, we’re following the rules specified above, and if they are met, including our class-file just before it actually gets used. This means I can safely call new My_Table_Stuff()
anywhere in my code and have it automatically included (if it’s not already). Super handy, right?!
To actually hook that up, you would call spl_autoload_register
with your callback:
spl_autoload_register( 'my_class_file_autoloader' );
So what are the downsides? Well, as far as I have been able to tell, there are none, and only benefits Although spl_autoload_register
was introduced in version 5.1.2, the SPL extension isn’t guaranteed to be available until 5.3 (this may not be true, if this patch makes it into core). There are other ways to accomplish autoloading (like using composer), but this is the cleanest, most-reliable, and proven method. The CMB2 plugin/library has an autoloader and is used by thousands, and I have yet to hear any issues with files not loading properly.
If this article gave you a light-bulb moment, or if you know better than me, or even if if this article was super-confusing and useless to you, please sound off in the comments below!
If you’re not tired of reading about php autoloaders yet, here are some additional resources:
- Benchmarking Autoload Performance In PHP
- More benchmarking
- “If you do it right – with a callback function – you can use spl_autoload_register() without negative side-effects.”
- php.net page on autoloading
spl_autoload_register
manual
Post has been updated thanks to the following awesome feedback from other developers:
@Jtsternberg Nice write-up. The autoloader only tries to load undefined classes, so there shouldn't be a need for the class_exists check.
— Brady Vercher (@bradyvercher) March 2, 2015
@Jtsternberg I think you could change false in false === strpos( $class_name, 'My_' ) to 0 !== to insure the class *starts* with My_
— Sal Ferrarello (@salcode) March 2, 2015
Thanks to webucator for the video version of this post. Check them out!
The potential downside is that although spl_autoload_register() was introduced in version 5.1.2, the SPL extension isn’t guaranteed to be available until 5.3. Prior to 5.3 PHP can be built without it.
I’d say it’s unlikely, but it’s possible,
References:
http://php.net/manual/en/spl.installation.php
See also:
https://core.trac.wordpress.org/ticket/21300#comment:1
Ah there it is. I searched a bit for info like that but fell short. Thank you for your insight and I will update the post.