1337noobie
1337noobie

Reputation: 13

Autoload with Namespace - Fatal error: Uncaught Error: Class "MyClass" not found

Using spl_autoload_register() with a class that uses namespace, causes this error message: Fatal error: Uncaught Error: Class "MyClass" not found...

Before this error message, the autoloader does echo "...MyClass.php IS found".

Two files are used: "/index.php" (in the root directory) and "/classes/MyClass.php".

Index.php:

declare(strict_types=1);

define ('ROOT_DIR', __DIR__ . '/');
define ('CLASS_DIR', ROOT_DIR . 'classes/');

spl_autoload_register(function($class) {
    $filepath = CLASS_DIR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
    if (file_exists($filepath)) {
        require_once $filepath;
        echo('<br /><b>' . $filepath . '</b> IS found.<br />');
    } else {
        echo('<br /><b>' . $filepath . '</b> not found.<br />');
        exit;
    }
});

$myclass = new MyClass();

MyClass.php:

declare(strict_types=1);

namespace App\Classes

class MyClass
{
    // ...
}

Does anyone know how to solve this?

According to https://www.php-fig.org/psr/psr-4/, a vendor name is required, so I am using "App" as a top-level namespace name, even though it is not a directory (because my website uses the root of the server).

I also tried adding "use App/classes/Myclass" to index.php before "$myclass = new MyClass()" but this causes even more problems, because the autoloader will look for the directory "/classes/App/Classes"...

With the namespace removed from the class, everything works fine, and I can use the functions of the class through $myclass. But I would like to start using namespaces... Any help would be really appreciated! <3

Conclusion: Either the line "$myclass = new App\Classes\MyClass();" or "use App\Classes\MyClass;" should be used.

So it is not possible to use the root of the server while also having a top-level namespace name ("App") with this autoload function. The function has to be expanded to allow for this possibility. And the "classes" directory will be renamed to "Classes". I will post my solution when it is ready!

For more details, read the comments below the answer by @IMSoP (Thank you very much for your help!)

Solution:

declare(strict_types=1);

namespace App;

define ('ROOT_DIR', $_SERVER['DOCUMENT_ROOT'] . '/');
define ('BASE_DIR', __DIR__ . '/');
define ('TOP_LEVEL_NAMESPACE_NAME', __NAMESPACE__ . '/');

spl_autoload_register(function($class) {
    if (BASE_DIR == ROOT_DIR . TOP_LEVEL_NAMESPACE_NAME) {
        $filepath = ROOT_DIR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
    } else {
        $filepath = BASE_DIR . str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
        $filepath = str_replace(TOP_LEVEL_NAMESPACE_NAME, '', $filepath);
    }
    if (file_exists($filepath)) {
        require_once $filepath;
    } else {
        echo('Class <b>' . end(explode('\\', $class)) . '.php</b> was not found.');
        exit;
    }
});

use App\Classes\MyClass;

$myclass = new MyClass();

This solution works whether the application is in the directory with the same name as the top-level namespace name, or anywhere else!

Upvotes: 0

Views: 993

Answers (1)

IMSoP
IMSoP

Reputation: 97658

You might want to read through the manual pages on autoloading and namespaces to make sure you understand the key concepts.

You have declared a class called MyClass inside the namespace App\Classes; that means that its fully qualified name is App\Classes\MyClass - that's the name you need to call it by from outside that namespace. There could simultaneously be a different class whose fully-qualified name was just MyClass, because it wasn't in any namespace, and any number of others in other namespaces, like App\Security\MyClass, App\UI\MyClass, etc.

Then you've attempted to reference a class in index.php called MyClass, which triggers the autoloader. The autoloader translates it to a path like .../classes/MyClass.php, and loads the right file; but that file defines your namespaced class. So after the autoloader has finished, there is no class called MyClass, only App\Classes\MyClass and the code fails.

If instead you write new App\Classes\MyClass, you'll get the opposite problem: the string passed to your autoloader is 'App\Classes\MyClass' and you translate that to a file path like '.../classes/App/Classes/MyClass.php' - but that's not where your file is. (Adding use App\ClassesMyClass does the same thing - use statements are just compiler assistance to avoid writing out the fully-qualified name as often.)

What you need to do is both:

  • Consistently use fully-qualified class names (or alias them with use)
  • Lay out your files to match your namespace structure, so that your autoloader can find them, which generally means a directory per namespace

Upvotes: 2

Related Questions