psr4 autoloader does not autoload from within a class

The autoloader works when I use it in index.php, but when I create an object within index.php and this object has to create other objects (which are all in the same namespace), it throws the error Uncaught Error: Class 'xxx' not found in (...).

My composer.json looks like this:

{
    "autoload": {
        "psr-4": {
            "pizzaCase\\": "src",
            "Connection\\": "src/Connection/",
            "Elements\\": "src/Elements/"
        }
    },
    "require": {
        "cboden/ratchet": "^0.4"
    }
}

My index.php looks like this:

<?php
    require_once __DIR__. '/vendor/autoload.php';
    require_once __DIR__."/src/config.php";

    use Connection\Database;
    use Elements\Form;
    use Elements\FormElement;
    use Elements\FormElementRadio;
    
    // Database::init();
    $form = new Form();

    $data["options"] = "soemthing, something else";
    $form->addElement("", "pizza", "", "Choose pizza", "radio", $data);
?>

In the addElement method I then create an object which is also within the src/Elements/ namespace, but it throws the error mentioned above.

The body of my addElement method looks like this:

<?php
namespace Elements;

    class Form
    {
        public static $leftSize = 3;
        protected $elements = [];
    
        public function addElement($table, $name, $value, $label=false, $type = false, $data = false) 
        {
            $type = ucfirst($type);
            $class = "FormElement{$type}";
    
            //FAILS HERE
            if(class_exists($class))
            {
                //CLASS EXISTS, CREATE OBJECT FROM RESPECTIVE CLASS
                $form = new $class($table, $name, $value, $label, $type, $data);
    
                $this->elements[$name] = $form;
            }
        }
    }

What am I doing wrong (or missing)? How come the autoloader can autoload it from index.php, but the object I create cannot create other objects without autoloader failing?

Upvotes: 2

Views: 106

Answers (2)

IMSoP
IMSoP

Reputation: 98005

The difference is not to do with where the code is being run; the difference is that the failing code is trying to choose which class to load dynamically.

In PHP, namespaces are essentially a compile-time feature: before any of your code is run, the compiler looks at all references to class names which don't start with \, and prefixes them with the current namespace, or according to rules you've specified with use statements. When the code runs, the current namespace, and use statements, aren't visible at all.

When you specify a class name dynamically, the compiler just sees a string, not a class name, so leaves it alone. Then when the code runs, the class name looked up is assumed to be fully specified, not relative to the current namespace or use statements.

So the solution is simple - specify the full namespace when creating the dynamic class name:

$class = "Elements\FormElement{$type}";

You can also use the magic constant __NAMESPACE__ to have the compiler substitute the current namespace name for you (obviously, this still won't account for any use statements):

$class = __NAMESPACE__ . "\FormElement{$type}";

Alternatively, if you have a specific set of classes you are choosing between, you can use the ::class syntax to generate a string at compile time, based on the current namespace and any use statements in effect:

$mapTypeToClassName = [
   'Radio' => FormElementRadio::class, // expands to 'Elements\FormElementRadio'
   'Select' => FormElementSelect::class,
   // etc
];
$class = $mapTypeToClassName[$type];

Upvotes: 3

John Zwarthoed
John Zwarthoed

Reputation: 1277

It could be because you’re having multiple namespaces for the src directory.

Usually you would just create a namespace for src like this

“psr-4": {
    "PizzaCase\\": "src"
}

And then just use PizzaCase\Elements and PizzaCase\Connections as namespaces

Upvotes: -1

Related Questions