Search code examples
phpoopxamppnamespacesphp-7

PHP Autoload Classes Is Not Working With Namespaces


I have tried to write the following code but cannot figure out why it will not find the class in spl_autoload_register() with a namespace?

Error I get is:

Warning: require_once(src/test\StringHelper.php): failed to open stream: No such file or directory

Autoloader.php file:

<?php

spl_autoload_register(function($classname){
    require_once "src/$classname.php"; // NOT WORKING DYNAMICALLY

//    require_once "src/StringHelper.php"; // WORKING WHEN HARD CODED
});

$stringHelper1 = new test\StringHelper(); // Class with namespace defined
echo $stringHelper1->hello() . PHP_EOL; // returns text

StringHelper.php inside src folder:

<?php namespace test;

class StringHelper{

    function hello(){
        echo "hello from string helper";
    }
}

I am also using XAMPP if this make a difference.


Solution

  • As already pointed out in the comments, you'll need to strip everything besides the class name, like so:

    $classname = substr($classname, strrpos($classname, "\\") + 1);
    

    Within the context of your autoloading function:

    spl_autoload_register(function($classname){
    
        $classname = substr($classname, strrpos($classname, "\\") + 1);
        require_once "src/{$classname}.php"; 
    });
    

    Let's take this a step further by making use of the fact that an autoload function always receives the qualified namespace as opposed to, for example, the relative namespace:

    <?php
    
    namespace Acme;
    
    $foo = new \Acme\Foo(); // Fully qualified namespace 
    $foo = new Acme\Foo();  // Qualified namespace
    $foo = new Foo();       // Relative namespace
    

    In all three instances, our autoload function is always given Acme\Foo as argument. With this in mind, it's fairly easy to implement an autoloader strategy that maps a namespace and any sub-namespaces to a file system path - especially if we include the top-level namespace (Acme, in this case) in the filesystem hierarchy.

    For example, given these two classes within some project of ours...

    <?php
    
    namespace Acme;
    
    class Foo {}
    

    Foo.php

    <?php
    
    namespace Acme\Bar;
    
    class Bar {}
    

    Bar.php

    ...within this file system layout...

    my-project
    `-- library
        `-- Acme
            |-- Bar
            |   `-- Bar.php
            `-- Foo.php
    

    ...we could implement a simple mapping between a namespaced class and its physical location like so:

    <?php
    
    namespace Acme;
    
    const LIBRARY_DIR = __DIR__.'/lib'; // Where our classes reside
    
    /**
     * Autoload classes within the current namespace
     */
    spl_autoload_register(function($qualified_class_name) {
    
        $filepath = str_replace(
    
            '\\', // Replace all namespace separators...
            '/',  // ...with their file system equivalents
            LIBRARY_DIR."/{$qualified_class_name}.php"
        );
    
        if (is_file($filepath)) {
    
            require_once $filepath;
        }
    });
    
    new Foo();
    new Bar\Bar();
    

    Also note that you can register multiple autoloading functions, for example, to handle different top-level namespaces in different physical locations. In a real-word project, though, you might want to get yourself acquainted with Composer's autoloading mechanism:

    At some point, you might also want to have a look into PHP's autoloading specification: