0
votes

I am using classes with Wordpress and I am trying to autoload them in my functions.php file:

spl_autoload_register(function($class) {
    include('classes/'.$class.'.php');
});

This is what my classes directory looks like:

  • classes/
    • project/
      • Application.php
      • Core.php
      • Site.php
      • Helpers.php
    • utils/
      • Helpers.php
      • Twig.php
    • views/
      • Layout.php
      • Modules.php
      • pages/
        • Home.php

Each class is namespaced based on the directory it is in. For example:

$homeClass = new \views\pages\Home();

When I autoload the classes, I get this error:

PHP Warning: include(classes/project\Application.php): failed to open stream: No such file or directory

Obviously the backslashes that are part of the namespacing don't work in the path. I could update my function to replace the backslashes with forward slashes like this:

spl_autoload_register(function($class) {
    include('classes/'.str_replace("\\", "/", $class).'.php');
});

But it seems odd that that would be required. Am I missing something?

4
Why does it seem odd? If this is a Unix/Linux host, backslash isn't a directory separator, so you can't use it to separate directories in a path. - IMSoP
It seems odd to me because spl_autoload_register() is built-in to PHP and yet it requires an additional function (str_replace()) to accommodate namespaces. I would have assumed that spl_autoload_register() would have been able to achieve this on it's own. - Michael Lynch
spl_autoload_register knows nothing about file paths at all. It doesn't even run include for you, it just gives you a fully-qualified class name, and it's up to you to do whatever you want to make that class come into existence. So it's not at all odd that you have to manipulate one string (the class name) to make it into another string (the path on disk according to your naming convention). - IMSoP

4 Answers

1
votes

I have an example here.
Basically a better version of spl_autoload_register since it only tries to require the class file whenever you initializes the class.
Here it automatically gets every file inside your class folder, requires the files and initializes it. All you have to do, is name the class the same as the file.
index.php

<?php
require_once __DIR__ . '/app/autoload.php';

$loader = new Loader(false);

User::dump(['hello' => 'test']);

autoload.php

<?php
class Loader 
{

    public static $library;

    protected static $classPath = __DIR__ . "/classes/";

    protected static $interfacePath = __DIR__ . "/classes/interfaces/";

    public function __construct($requireInterface = true) 
    {
        if(!isset(static::$library)) {
            // Get all files inside the class folder
            foreach(array_map('basename', glob(static::$classPath . "*.php", GLOB_BRACE)) as $classExt) {
                // Make sure the class is not already declared
                if(!in_array($classExt, get_declared_classes())) {
                    // Get rid of php extension easily without pathinfo
                    $classNoExt = substr($classExt, 0, -4); 
                    $file = static::$path . $classExt;

                    if($requireInterface) {
                        // Get interface file
                        $interface = static::$interfacePath . $classExt;
                        // Check if interface file exists
                        if(!file_exists($interface)) {
                            // Throw exception
                            die("Unable to load interface file: " . $interface);
                        }

                        // Require interface
                        require_once $interface;
                        //Check if interface is set
                        if(!interface_exists("Interface" . $classNoExt)) {
                            // Throw exception
                            die("Unable to find interface: " . $interface);
                        }
                    }

                    // Require class
                    require_once $file;
                    // Check if class file exists
                    if(class_exists($classNoExt)) {
                        // Set class        // class.container.php
                        static::$library[$classNoExt] = new $classNoExt();
                    } else {
                        // Throw error
                        die("Unable to load class: " . $classNoExt);
                    }

                }
            }
        }
    }

    /*public function get($class) 
    {
        return (in_array($class, get_declared_classes()) ? static::$library[$class] : die("Class <b>{$class}</b> doesn't exist."));
    }*/
}

You can easily manage with a bit of coding, to require classes in different folders too.
Hopefully this can be of some use to you.

0
votes

It's not odd at all!

Registering your autoloaders using namespaces as a means to denote a directory structure is fairly common practice. I would recommend following PSR-4 conventions, but if you you're too far in to refactor your naming conventions, then I would recommend using the DIRECTORY_SEPARATOR constant to help PHP decide whether to use '/' or '\', as Windows servers use the latter, and Linux servers use the former.

0
votes

I've made a class for this requirement, compatible with PSR-4.

You can reach it here: https://github.com/pablo-pacheco/wp-namespace-autoloader

The explanation is all there but basically it's a composer dependency. You just have to require it in your project:

"require": {    
    "pablo-pacheco/wp-namespace-autoloader": "dev-master"
}

And then call the class

<?php
new \WP_Namespace_Autoloader( array(    
    'directory'   => __DIR__,       // Directory of your project. It can be your theme or plugin. __DIR__ is probably your best bet.    
    'namespace'   => __NAMESPACE__, // Main namespace of your project. E.g My_Project\Admin\Tests should be My_Project. Probably if you just pass the constant __NAMESPACE__ it should work     
    'classes_dir' => 'src',         // (optional). It is where your namespaced classes are located inside your project. If your classes are in the root level, leave this empty. If they are located on 'src' folder, write 'src' here 
) );

I appreciate any kind of feedback!

0
votes