Hello WordPress community. With this ticket I would like to get the debate of the last days about how to attract WordPress to developers (or the other way around?) to a concrete discussion on how a class autoloader could look like in WordPress core. So when we start to think about some major improvements like in https://core.trac.wordpress.org/ticket/36292, whe should also take some time and talking about autoloading.
A class autoloader is a basic tool to separate code writing form code organization. It takes care about providing class declaration at the point they are needed. The fact that WordPress lacks of a core autoloader was one point mentioned in the debate on what developers missing most with WordPress. 1
Plugin authors using autoloaders these days. They even use composer for dependency management and ship their plugins with this dependencies. This practice leads to trouble right now 2. I'm convinced that in a long-range plan we even have to talk about how to deal with proper dependency management to overcome collisions. Having an autoloader in core is a precondition for this.
The following proposal follows a concept of separating the file locating and file loading process to avoid a violation
of the single responsibility principle and to gain flexibility. All classes and interfaces are prefixed with WP_Autoload_
to apply a pseudo namespace.
The main instance of this concept is the interface WP_Autolaod_Rule
. A autoload rule is against the client responsible
for locating and loading a a given class. The class is provided by its full qualified name. This leads to this interface
signature:
interface WP_Autoload_Rule {
/**
* @param string $class (A full qualified class name)
*
* @return bool
*/
public function load_class( $class );
}
Implementations could be WP_Autoload_Psr4Rule
, WP_Autoload_Psr0Rule
or WP_Autoload_ClassMapRule
or what ever plugin
and theme authors want to implement for their requirements. Here's a quick example:
class WP_Autoload_Psr4Rule implements WP_Autoload_Rule {
/**
* @var string
*/
private $base_namespace;
/**
* @var string
*/
private $base_directory;
/**
* @var WP_Autoload_FileLoader
*/
private $file_loader;
/**
* @param $base_namespace
* @param $base_directory
*/
public function __construct( $base_namespace, $base_directory, $file_loader = NULL ) {
$this->base_directory = (string) $base_directory;
$this->base_namespace = (string) $base_namespace;
$this->file_loader = $file_loader && is_a( $file_loader, 'WP_Autoload_Fileloader' )
? $file_loader
: new WP_Autoload_IsReadableFileLoader;
}
/**
* @param string $class (A full qualified class name)
*
* @return bool
*/
public function load_class( $class ) {
// performing the psr4 mapping here to get a $file
$file = '/whatever';
return $this->file_loader->load_file( $file );
}
}
Autoloading rules provided by the core should depend on a file loader. The file loader receives a file name and loads it, if it is present.
interface WP_Autoload_FileLoader {
/**
* @param string $file
*
* @return bool
*/
public function load_file( $file );
}
A very simple implementation can just ask for is_readable()
. Looking on performance, another implementation could
glob()
-ing a given directory once to read in all PHP-files and matching the given file against this list. Even
persistent caches are thinkable.
Last but not least WP_Autoload_Autoload
acts as a repository for all possible rules:
interface WP_Autoload_Autoload {
/**
* @param WP_Autoload_Rule $autoload_rule
*
* @return void
*/
public function add_rule( $autoload_rule );
}
The main implementation should be WP_Autoload_AutoloadSpl
which connects to PHP standard library autoload:
class WP_Autoload_SplAutoload implements WP_Autoload_Autoload {
/**
* @var WP_Autoload_Rule[]
*/
private $rules = array();
/**
* @param WP_Autoload_Rule $autoload_rule
*
* @return void
*/
public function add_rule( $autoload_rule ) {
if ( ! in_array( $autoload_rule, $this->rules, TRUE ) )
$this->rules[] = $autoload_rule;
}
/**
* @param string $fqcn (full qualified class name)
*/
public function load_class( $fqcn ) {
foreach ( $this->rules as $rule ) {
if ( $rule->load_class( $fqcn ) )
break;
}
}
}
function wp_setup_autoloader() {
$autoloader = new WP_Autoload_SplAutoload;
spl_autoload_register( array( $autoloader, 'load_class' ) );
/**
* Use the WordPress core autoloader to
* bootstrap your plugin and theme
*
* @param WP_Autoload_Autoload $autoloader
*/
do_action( 'wp_autoload', $autoloader );
}
wp_setup_autoloader()
should then be called early in the WP loading process.
add_action( 'wp_autoload', function( $autoloader ) {
$autoloader->addRule(
new WP_Autoload_Psr4Rule(
'MyPlugin\\'
__DIR__ . '/src'
)
);
} );
- The proposal uses sensible interface names 3 which I prefer over naming interfaces like
WhateverInterfaces
. As WordPress does not provide interfaces right now, this is just a suggestion. - PHP class identifiers are case insensitive. That means
new MySql
andnew MySQL
will both work, if a classmysql
is declared. The autoloader should respect this. Now that Psr4 is very wide-spread and encourage developers to use case sensitive this is a problem 4. How can we deal with this in a performant way? - How should the WordPress core files be organized to work with the autoloader? Is it realistic to rearrange them, if not
how could a corresponding
WP_Autoload_Rule
look like? - What about compatibility with PHP
5.2
. The proposal usesspl_autoload_register
. But before PHP 5.3.0 it is theoretically possible to deactivate the spl extension. In this case another implementation ofWP_Autolad_Autoload
would be necessary and maybe some adaptions to the other interfaces. But is this really the intention?
Finally: Thanks for reading. Feel free to add your concerns, your opinions or even if I'm on a completely wrong train. In fact I'm really interested in critic. To be clear, I don't want to push this proposal but I would like to see a proper autoloader in core some day :)