Jump to content

Namespace and PSR-4 Autoloader


NotionCommotion

Recommended Posts

The following example is from http://www.php-fig.org/psr/psr-4/examples/

 

<?php
/**
 * An example of a project-specific implementation.
 *
 * After registering this autoload function with SPL, the following line
 * would cause the function to attempt to load the \Foo\Bar\Baz\Qux class
 * from /path/to/project/src/Baz/Qux.php:
 *
 *      new \Foo\Bar\Baz\Qux;
 *
 * @param string $class The fully-qualified class name.
 * @return void
 */
spl_autoload_register(function ($class) {


    // project-specific namespace prefix
    $prefix = 'Foo\\Bar\\';


    // base directory for the namespace prefix
    $base_dir = __DIR__ . '/src/';


    // does the class use the namespace prefix?
    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        // no, move to the next registered autoloader
        return;
    }


    // get the relative class name
    $relative_class = substr($class, $len);


    // replace the namespace prefix with the base directory, replace namespace
    // separators with directory separators in the relative class name, append
    // with .php
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';


    // if the file exists, require it
    if (file_exists($file)) {
        require $file;
    }
});
  1. In $prefix, is the only reason to use double backslashes to prevent escaping trailing single or double quotes or other special characters if using double quotes?
  2. Can/should multiple spl autoloaders be registered in the same file?  If so, please give an example.
  3. Let's say I had the following index.php and MyClass.php file with locations as shown.
    1. If I as a vendor wanted "Michael" to be included in all the script I wrote, it would be considered the top-level namespace name (AKA “vendor namespace”), right?
    2. Is my assignment of $prefix correct?
    3. Assuming I don't wish to modify its source code, where should pimple.php be located and how should spl_autoload_register be set up.
    4. Could/should the magic constant __NAMESPACE__ be used in this autoloader?
    5. Is constant __NAMESPACE__ and keyword namespace basically to means to accomplish the same objective?  Since already in that namespace, won't the same class be evoked and using them provides no new outcome?

file: /var/www/public/index.php

<?php
namespace Michael\MyProject;

spl_autoload_register(function ($class) {
    // project-specific namespace prefix
    $prefix = 'Michael\\';   //Is this correct?

    // base directory for the namespace prefix
    $base_dir = __DIR__ . '/../src/';

    $len = strlen($prefix);
    if (strncmp($prefix, $class, $len) !== 0) {
        return;
    }
    $relative_class = substr($class, $len);
    $file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
    if (file_exists($file)) {
        require $file;
    }
});

$myClass=new MyClass();
$pimple = new \Pimple(); //Old version 1 which doesn't use namespace

$classname='mine()';
$a = __NAMESPACE__ . '\\' . $classname;
$aObj = new $a;

namespace\blah\mine(); 

file: /var/www/src/Bar/MyClass.php

<?php
namespace Foo\Bar;

class MyClass{}

 

Link to comment
Share on other sites

1. No, it's because namespaces use backslashes.

2. Why wouldn't it be possible? It's just a function call. The autoloader doesn't care where the callback is defined. Whether you should is not a question of putting them in the same file but about using multiple autoloaders, so if you need multiple autoloaders (like you have multiple namespace roots) then you should use multiple autoloaders.

3.1. Right.

3.2. Think about what the code is doing and you should be able to answer that question yourself.

3.3. It does not matter where you put the file as long as you have the autoloader pointing at the right location.

3.4. If you are trying to use the class named "Michael\MyProject\mine()" then yes. Now re-read that sentence a couple more times until you realize what the problem with your code is.

3.5. No. They're different things. Please, think about what you're asking. Do you know what __NAMESPACE__ is? Do you know what the namespace keyword does?

Link to comment
Share on other sites

1. No, it's because namespaces use backslashes.

Not asking why a backslash opposed to some other character, but whether there are really two, or just a single escaped one.

 

3.2. Think about what the code is doing and you should be able to answer that question yourself.

Please humor me.  I've looked for the definition of a namespace prefix with limited success.

 

3.3. It does not matter where you put the file as long as you have the autoloader pointing at the right location.

"The" autoloader or one of the registrared autoloaders?  Can it be done with one? 

 

3.4. If you are trying to use the class named "Michael\MyProject\mine()" then yes. Now re-read that sentence a couple more times until you realize what the problem with your code is.

Indicating that it is redundant and unnecessary, right?  Is this incorrect? https://stackoverflow.com/a/3642440/1032531

 

3.5. No. They're different things. Please, think about what you're asking. Do you know what __NAMESPACE__ is? Do you know what the namespace keyword does? 

It is my understanding that __NAMESPACE__ is a constant (which changes so it really  isn't that constant) and its value is the current namespace name.  The namespace keyword is to a namespace as self is to a class.  Both ways to uniquely identify a given namespace.  No?
Link to comment
Share on other sites

1.

echo $prefix;
Or I could point you to the documentation for string types which talks about escaping characters in both single- and double-quoted strings.

 

3.2. That's because there is no such thing as a "namespace prefix". I mean look at the code. You have a $prefix variable being used with strlen, strncmp, and substr. Forget the terminology and read what the code is doing. What is happening on each line, even if you don't yet necessarily understand why it's happening.

 

3.3. It does not make sense to have multiple autoloaders for the same namespace. It could make sense to have one autoloader handle multiple namespaces, but whether one actually does that is more a question about code design, complexity, coupling, etc.

So "the" autoloader.

 

3.4. I was trying to get at the name of the class. Is it in the "Michael\MyProject" namespace? Yes? Good. Is it called "mine()"? No. It is called "mine". The parentheses are for passing arguments to the constructor just like a function call. If you tried to run that code the autoloader would fail because it wouldn't be able to find a "mine().php" file containing a "namespace Michael\MyProject" and "class mine() {".

 

3.5. >The namespace keyword is to a namespace as self is to a class

No. "namespace" is to namespaces as "class" is to classes: it declares one. There is no equivalent of "self" because by default any relative name (ie, with a leading backslash) is already resolved to the containing namespace.

Link to comment
Share on other sites

In $prefix, is the only reason to use double backslashes to prevent escaping trailing single or double quotes or other special characters if using double quotes?

More or less. Namespaces use backslashes as the delimiter, which is also the escape character in strings. As such it needs to be escaped to give you a literal backslash.

 

If you're using single-quotes around your namespace string then you only technically need to double up the last one to prevent escaping the closing quote. Doubling up the rest of them is fine, but not necessary.

 

If using double quotes you need to escape all the backslashes or you'll end up with a bad string.

 

Can/should multiple spl autoloaders be registered in the same file? If so, please give an example.

As mentioned, it's just a function call. You can register as many autoloaders in whatever ways you want. Generally you'd have either one per project or one per library though.

 

Back before spl_autoload_register you could infact only have one autoloader, which was a magic function named __autoload. spl_autoload_register and related were created specifically to allow multiple autoloaders so that libraries could implement their own autoloader and you just needed to register that when using said library.

 

 

3.2. Think about what the code is doing and you should be able to answer that question yourself.

Please humor me. I've looked for the definition of a namespace prefix with limited success.

The prefix in this instances represents what part of the namespace is common across all items that this autoloader function should handle. Notice how the if statement a couple lines down checks to see if the class being autoloaded begins with the specified prefix?

 

3.3. It does not matter where you put the file as long as you have the autoloader pointing at the right location.

"The" autoloader or one of the registrared autoloaders? Can it be done with one?

As noted above, you can have just one, or you can have many autoloaders. Just depends on what you need. With composer these days, it's relativly common to have both composer's autoloader for libraries and a project autoloader for your project code.

 

Since your autoloader is just a function, you typically don't need to register a bunch as you can code the function to do whatever you need. For example, one of my older projects has an autoloader setup like:

function ngl__autoload($nm){
    if (strpos($nm, '\\') !== false){
        return ngl__namespaceAutoload($nm);
    }

    $suffixList = array('.class.php', '.interface.php', '.php');
    $paths = array(
        CLASSES_ROOT.DIRECTORY_SEPARATOR,
        CLASSES_ROOT.DIRECTORY_SEPARATOR.'EventListeners'.DIRECTORY_SEPARATOR,
        EXCEPTIONS_ROOT.DIRECTORY_SEPARATOR,
        INTERFACES_ROOT.DIRECTORY_SEPARATOR,
        FRAMES_ROOT.DIRECTORY_SEPARATOR,
        CLASSES_ROOT.DIRECTORY_SEPARATOR.'Emailer'.DIRECTORY_SEPARATOR,
    );

    foreach ($suffixList as $suffix){
        $fname = $nm.$suffix;
        foreach ($paths as $path){
            $path = $path.$fname;
            if (file_exists($path)){
                require_once($path);
                return true;
            }
        }
    }

    return false;
}
function ngl__namespaceAutoload($nm){
    $parts = explode('\\', $nm);
    $class = array_pop($parts);
    $namespace = implode('\\', $parts).'\\';

    $roots = [
        'Workflow\\' => INCLUDE_ROOT.'/classes/Workflow/'
        , 'IC\\PopupAlerts' => INCLUDE_ROOT.'/classes/PopupAlerts/'
        , 'IC\\Email' => INCLUDE_ROOT.'/classes/Email/'
        , 'IC\\Parser' => INCLUDE_ROOT.'/classes/Parser/'
    ];

    foreach ($roots as $prefix=>$root){
        $prefixLength = strlen($prefix);
        if (strncmp($prefix, $namespace, $prefixLength) === 0){
            $path = $root.substr($namespace, $prefixLength);
            $path .= '/'.$class.'.php';

            if (file_exists($path)){
                require_once $path;
                return true;
            }
        }
    }

    return false;
}

spl_autoload_register('ngl__autoload');
require_once './vendor/autoload.php';
Originally it was just function __autoload, then spl_autoload_register came along so it was renamed to add that. Then namespaces/composer came along so those were implemented.

 

Note how the namespace autoloader handles multiple different namespaces. It's not necessary to have one per namespace. Composer does something similar if you look at it's autoloader

 

 

Could/should the magic constant __NAMESPACE__ be used in this autoloader?

If your autoloader is defined in the appropiate namespace, then you could use __NAMESPACE__ as the value of your $prefix variable, sure.

 

 

3.4. If you are trying to use the class named "Michael\MyProject\mine()" then yes. Now re-read that sentence a couple more times until you realize what the problem with your code is.

Indicating that it is redundant and unnecessary, right? Is this incorrect? https://stackoverflow.com/a/3642440/1032531

The code you posted that actually uses __NAMESPACE__ is nonsense though.

$classname='mine()';
$a = __NAMESPACE__ . '\\' . $classname;
$aObj = new $a;
There's absolutely no point in using __NAMESPACE__ there, $aObj = new mine(); is all you need.

 

 

3.5. No. They're different things. Please, think about what you're asking. Do you know what __NAMESPACE__ is? Do you know what the namespace keyword does?

It is my understanding that __NAMESPACE__ is a constant (which changes so it really isn't that constant) and its value is the current namespace name. The namespace keyword is to a namespace as self is to a class. Both ways to uniquely identify a given namespace. No?

namespace is used to tell PHP under what namespace the code following it should be categorized.

__NAMESPACE__ can be used to determine at runtime under what namespace the current line of code has been categorized. It's mainly there just to keep you from having to retype the namespace over again if you need it in string form for some reason (such as for debugging/logging). It lets you avoid hard-coded namespace strings, making it easier to change your namespace in the future if needed.

Link to comment
Share on other sites

3.3. It does not matter where you put the file as long as you have the autoloader pointing at the right location.

I believe it is more than just pointing to the right file location.  The fully qualified class must exist in that file, right?

 

3.4. I was trying to get at the name of the class. Is it in the "Michael\MyProject" namespace? Yes? Good. Is it called "mine()"? No. It is called "mine". The parentheses are for passing arguments to the constructor just like a function call. If you tried to run that code the autoloader would fail because it wouldn't be able to find a "mine().php" file containing a "namespace Michael\MyProject" and "class mine() {".

3.5. >The namespace keyword is to a namespace as self is to a class

No. "namespace" is to namespaces as "class" is to classes: it declares one. There is no equivalent of "self" because by default any relative name (ie, with a leading backslash) is already resolved to the containing namespace.

My interpretation of http://php.net/manual/en/language.namespaces.nsconstants.php is that the keyword "namespace" is interpreted differently based on where it is located and will define the namespace if located at the top of the file but will explicitly fully qualify the class if located within the namespace (kind of like what is possible using  __NAMESPACE__).  Why do you think I am incorrect?

The namespace keyword can be used to explicitly request an element from the current namespace or a sub-namespace. It is the namespace equivalent of the self operator for classes.
 
Example #4 the namespace operator, inside a namespace
 
<?php
namespace MyProject;


use blah\blah as mine; // see "Using namespaces: Aliasing/Importing"


blah\mine(); // calls function MyProject\blah\mine()
namespace\blah\mine(); // calls function MyProject\blah\mine()


namespace\func(); // calls function MyProject\func()
namespace\sub\func(); // calls function MyProject\sub\func()
namespace\cname::method(); // calls static method "method" of class MyProject\cname
$a = new namespace\sub\cname(); // instantiates object of class MyProject\sub\cname
$b = namespace\CONSTANT; // assigns value of constant MyProject\CONSTANT to $b
?>

 

 

The prefix in this instances represents what part of the namespace is common across all items that this autoloader function should handle.

 

Thanks.  That was also how I was interpreting it, but wanted to confirm.
 

The code you posted that actually uses __NAMESPACE__ is nonsense though.

There's absolutely no point in using __NAMESPACE__ there, $aObj = new mine(); is all you need.

 

I agree, however, given that http://php.net/manual/en/language.namespaces.nsconstants.php was the source of the code, was concerned that I was missing something.
 
Last question...  Assume the following:
  1. My top-level namespace name (AKA “vendor namespace”) is Michael.
  2. I am working on a project called "EmbeddedWebserver".
  3. I have a bunch of controllers which will use the same namespace (i.e. Michael\EmbeddedWebserver\Controllers)
  4. I wish to use Pimple.php where a namespace isn't declared in the file, and don't wish to edit its source code to declare a namespace as I am not the files author.
  5. All PHP classes can only be located in /var/www/src or one of its subdirectories.
  6. spl_autoload_register() is called in /var/www/public/index.php.
  7. Either I will put namespace Michael\EmbeddedWebserver\Controllers; at the top of index.php or will add use Michael\EmbeddedWebserver\Controllers; to the scripts (pick one way or the other as it shouldn't matter).
 
How exactly should I modify https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-4-autoloader-examples.md closure example so that it may be used for both the controllers and Pimple.php, where should Pimple.php be located, and how should the Pimple object be created (i.e. $pimple=new Pimple(), new \Pimple(), etc)?
 
Thank you
Link to comment
Share on other sites

I believe it is more than just pointing to the right file location.  The fully qualified class must exist in that file, right?

The autoloader needs to cause the class to exist. 99% of the time the class is in the derived filename, but that's not a requirement. For example, at one point I had two implementations of the same class depending on the environment, located in different files, and the main class file that the autoloader would try to use instead had a bit of code to determine which of the two files to run.

 

My interpretation of http://php.net/manual/en/language.namespaces.nsconstants.php is that the keyword "namespace" is interpreted differently based on where it is located and will define the namespace if located at the top of the file but will explicitly fully qualify the class if located within the namespace (kind of like what is possible using  __NAMESPACE__).  Why do you think I am incorrect?

...Because I didn't know that was a thing? I'm surprised it is. I mean, it doesn't really do much - "namespace\foo" and "foo" will reference the same thing (assuming it exists there).
Link to comment
Share on other sites

Your last question was aimed at kicken so I didn't reply to it.

 

Pimple v1 is one file. Just require_once() it directly and skip the autoloader. Much easier.

 

For your controllers, do what has been said: figure out the namespace prefix you have to strip off to map the class name to a file, then look for the appropriate file. So if Michael\EmbeddedWebserver\Controllers\Foo maps to the /var/www/src/Controllers/Foo.php file then the prefix to test for and remove is "Michael\EmbeddedWebserver\" and the class file is "/var/www/src/" + the remainder of the class name with slashes fixed + ".php".

 

Autoloading has no impact on the code you write - besides removing the need for require_once()ing stuff, of course. To use Pimple you would reference the class as "Pimple" from the global namespace or "\Pimple" from any namespace. You could also "use Pimple" and write just "Pimple" but that's unnecessarily complicated.

Link to comment
Share on other sites

Thanks requnix,

 

As what I had thought was an interim solution, I have been using require_once('../src/Pimple.php'); $c = new \Pimple();, and everything is working perfectly.  It was only when attempting to use the single autoloader for this class did I have questions, and if I shouldn't be attempting to do so, then I have no questions.

Link to comment
Share on other sites

spl_autoload_register(function($classname) {
	if (strncmp($classname, "Pimple\\", 7) == 0) {
		require_once "/path/to/" . strtr(substr($classname, 7), "\\", "/") . ".php";
	}
});
That's the normal approach. Thing is, it won't work. Why? The $classname will be "Pimple". No namespace. (I didn't quite realize that until now.) So the autoloader would be just

spl_autoload_register(function($classname) {
	if ($classname == "Pimple") {
		require_once "/path/to/Pimple.php";
	}
});
If you expect to be using Pimple during most page requests then all that is overkill and a require_once() would be better.
Link to comment
Share on other sites

As mentioned you could just special case it in your loader function by checking if the class to be loaded is "Pimple" then require the file.

 

Since something like Pimple is likely to be used on every request, and v1 is just one file, it makes more sense to just manually require it into your code. Then you avoid the unnecessary overhead of the autoloader. Just throw the require_once line above your spl_autoload_register call, or wherever else makes sense.

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.