fix: give Composer autoloader precedence and optimise PSR-4 loader (#30)

- Swap load_dependencies() before register_autoloader() in the
  constructor so Composer (when a vendor/ install is present) is
  registered on the spl_autoload stack first and therefore acts as
  the primary class resolver, with the custom loader as fallback.
- Add early-exit guard in the autoloader closure: skip the namespace
  map loop entirely for classes that don't share the plugin base
  prefix, avoiding unnecessary iteration on every WordPress class
  lookup.
- Store the namespace map in a static variable so it is allocated
  only once across all invocations rather than on each call.
- Switch from require_once to require inside the autoloader: the
  autoloader is only invoked for classes not yet defined, making the
  redundancy check in require_once unnecessary overhead.

Resolves #29
This commit is contained in:
2026-04-26 11:01:53 +01:00
committed by GitHub
parent 85d55e80fc
commit 4ecccbc107
+32 -11
View File
@@ -76,8 +76,8 @@ class Plugin {
$this->plugin_url = plugin_dir_url($plugin_file); $this->plugin_url = plugin_dir_url($plugin_file);
$this->define_constants(); $this->define_constants();
$this->register_autoloader();
$this->load_dependencies(); $this->load_dependencies();
$this->register_autoloader();
$this->init_components(); $this->init_components();
} }
@@ -104,28 +104,47 @@ class Plugin {
* Maps plugin namespaces to their corresponding directories so that * Maps plugin namespaces to their corresponding directories so that
* new class files are loaded automatically without manual require_once calls. * new class files are loaded automatically without manual require_once calls.
* *
* Optimisations applied:
* - Early exit when the class does not belong to this plugin's namespace,
* avoiding the loop entirely for every foreign class lookup.
* - The namespace map is stored in a static variable so it is allocated
* only once across all invocations rather than on every class lookup.
* - `require` is used instead of `require_once` because the autoloader is
* only called for classes that are not yet defined, making the redundancy
* check in `require_once` unnecessary.
*
* @return void * @return void
*/ */
private function register_autoloader() { private function register_autoloader() {
$plugin_dir = $this->plugin_dir; $plugin_dir = $this->plugin_dir;
spl_autoload_register(function ($class) use ($plugin_dir) { spl_autoload_register(function ($class) use ($plugin_dir) {
// Ordered most-specific prefix first so Admin\ resolves before the root namespace. // Skip immediately if the class is not in this plugin's namespace.
$namespace_map = array( $base_prefix = 'WPALLSTARS\\FixPluginDoesNotExistNotices\\';
'WPALLSTARS\\FixPluginDoesNotExistNotices\\Admin\\' => $plugin_dir . 'admin/lib/', if (strncmp($base_prefix, $class, strlen($base_prefix)) !== 0) {
'WPALLSTARS\\FixPluginDoesNotExistNotices\\' => $plugin_dir . 'includes/', return;
); }
foreach ($namespace_map as $prefix => $base_dir) { // Static map allocated once; ordered most-specific first so Admin\
// resolves before the root namespace.
static $namespace_map = null;
if ($namespace_map === null) {
$namespace_map = array(
'WPALLSTARS\\FixPluginDoesNotExistNotices\\Admin\\' => 'admin/lib/',
'WPALLSTARS\\FixPluginDoesNotExistNotices\\' => 'includes/',
);
}
foreach ($namespace_map as $prefix => $relative_path) {
if (strncmp($prefix, $class, strlen($prefix)) !== 0) { if (strncmp($prefix, $class, strlen($prefix)) !== 0) {
continue; continue;
} }
$relative_class = substr($class, strlen($prefix)); $relative_class = substr($class, strlen($prefix));
$file = $base_dir . str_replace('\\', DIRECTORY_SEPARATOR, $relative_class) . '.php'; $file = $plugin_dir . $relative_path . str_replace('\\', DIRECTORY_SEPARATOR, $relative_class) . '.php';
if (file_exists($file)) { if (file_exists($file)) {
require_once $file; require $file;
return; return;
} }
} }
@@ -135,8 +154,10 @@ class Plugin {
/** /**
* Load dependencies * Load dependencies
* *
* Loads the Composer autoloader when available (vendor installs). Project * Loads the Composer autoloader when available (vendor installs), registering
* classes are resolved by the PSR-4 autoloader registered in register_autoloader(). * it on the spl_autoload stack before the custom PSR-4 loader so that Composer
* acts as the primary resolver. Project classes without a vendor install are
* resolved by the autoloader registered in register_autoloader().
* *
* @return void * @return void
*/ */