public
Last active

Add MIT license.

  • Download Gist
SplClassLoader.php
PHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
<?php
 
/*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* This software consists of voluntary contributions made by many individuals
* and is licensed under the MIT license. For more information, see
* <http://www.doctrine-project.org>.
*/
/**
* SplClassLoader implementation that implements the technical interoperability
* standards for PHP 5.3 namespaces and class names.
*
* http://groups.google.com/group/php-standards/web/psr-0-final-proposal?pli=1
*
* // Example which loads classes for the Doctrine Common package in the
* // Doctrine\Common namespace.
* $classLoader = new SplClassLoader('Doctrine\Common', '/path/to/doctrine');
* $classLoader->register();
*
* @license http://www.opensource.org/licenses/mit-license.html MIT License
* @author Jonathan H. Wage <jonwage@gmail.com>
* @author Roman S. Borschel <roman@code-factory.org>
* @author Matthew Weier O'Phinney <matthew@zend.com>
* @author Kris Wallsmith <kris.wallsmith@gmail.com>
* @author Fabien Potencier <fabien.potencier@symfony-project.org>
*/
class SplClassLoader
{
private $_fileExtension = '.php';
private $_namespace;
private $_includePath;
private $_namespaceSeparator = '\\';
 
/**
* Creates a new <tt>SplClassLoader</tt> that loads classes of the
* specified namespace.
*
* @param string $ns The namespace to use.
*/
public function __construct($ns = null, $includePath = null)
{
$this->_namespace = $ns;
$this->_includePath = $includePath;
}
 
/**
* Sets the namespace separator used by classes in the namespace of this class loader.
*
* @param string $sep The separator to use.
*/
public function setNamespaceSeparator($sep)
{
$this->_namespaceSeparator = $sep;
}
 
/**
* Gets the namespace seperator used by classes in the namespace of this class loader.
*
* @return void
*/
public function getNamespaceSeparator()
{
return $this->_namespaceSeparator;
}
 
/**
* Sets the base include path for all class files in the namespace of this class loader.
*
* @param string $includePath
*/
public function setIncludePath($includePath)
{
$this->_includePath = $includePath;
}
 
/**
* Gets the base include path for all class files in the namespace of this class loader.
*
* @return string $includePath
*/
public function getIncludePath()
{
return $this->_includePath;
}
 
/**
* Sets the file extension of class files in the namespace of this class loader.
*
* @param string $fileExtension
*/
public function setFileExtension($fileExtension)
{
$this->_fileExtension = $fileExtension;
}
 
/**
* Gets the file extension of class files in the namespace of this class loader.
*
* @return string $fileExtension
*/
public function getFileExtension()
{
return $this->_fileExtension;
}
 
/**
* Installs this class loader on the SPL autoload stack.
*/
public function register()
{
spl_autoload_register(array($this, 'loadClass'));
}
 
/**
* Uninstalls this class loader from the SPL autoloader stack.
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
}
 
/**
* Loads the given class or interface.
*
* @param string $className The name of the class to load.
* @return void
*/
public function loadClass($className)
{
if (null === $this->_namespace || $this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))) {
$fileName = '';
$namespace = '';
if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator))) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace($this->_namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension;
 
require ($this->_includePath !== null ? $this->_includePath . DIRECTORY_SEPARATOR : '') . $fileName;
}
}
}

The link in the header comment is broken, the document is now located here:

http://groups.google.com/group/php-standards/web/psr-0-final-proposal?pli=1

Maybe it shouldn't use require but include in loadClass()? Or is there a reason to do that?

loadClass() method should be changed to return boolean result because current implementation breaks the logic of calling autoloaders in the stack. Here fix for that (PHP >=5.3.3):

$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension;
$filePath  = stream_resolve_include_path($fileName);
if ($filePath) {
    require $filePath;
}
return $filePath !== false;

Wow, someone had better back it up before it's gone forever and there's no reference.Jonah

-------- Original Message --------
Subject: Re: gist gist: 221634
From: Artur Bodera
reply@reply.github.com
Date: Tue, September 20, 2011 7:59 am
To: Jonah Dahlquist jonah@nucleussystems.com
None of the links to the PSR-0 proposal work.

Here is a working one on google cache: http://webcache.googleusercontent.com/search?q=cache:XOHGigFVti8J:groups.google.com/group/php-standards/web/psr-0-final-proposal+https://groups.google.com/group/php-standards/web/psr-0-final-proposal&cd=1&hl=en&ct=clnk&gl=pl&lr=lang_pl|lang_en

Reply to this email directly or view it on GitHub:https://gist.github.com/221634

How ridiculous. I put it on my blog in case the cache disappears. http://blog.montmere.com/2011/09/21/psr-0-final-proposal/

Awesome, bookmarked.Jonah

-------- Original Message --------
Subject: Re: gist gist: 221634
From: "Stephen J. Fuhry"
reply@reply.github.com
Date: Wed, September 21, 2011 1:05 pm
To: Jonah Dahlquist jonah@nucleussystems.com

How ridiculous. I put it on my blog in case the cache disappears. http://blog.montmere.com/2011/09/21/psr-0-final-proposal/

Reply to this email directly or view it on GitHub:https://gist.github.com/221634

I've created a gist (md) version of the proposal: https://gist.github.com/1234504

@jonahbron @Thinkscape @till @lisachenko @fuhrysteve Google Groups does not support pages.
The FIG released under a repository, reachable at: https://github.com/php-fig/fig-standards

Cheers,

@till Obviously for the reason that require throws a fatal error (E_COMPILE_ERROR), instead of a warning (E_WARNING) as include does.

@lessthanthree So? You want an E_COMPILE_ERROR from class_exists() calls? I would like to see lisachenko's comment realized. Or maybe another parameter if or not to throw exceptions and errors.

@bxt I usually want to halt the execution whenever a non existent file is imported, that's why I'd favor require over include. This gives me more control (or the illusion of...) over the application process flow. :)

@lessthanthree but you might want to pass to downstram autoloaders, and if a class is not found execution is stopped anyway. Maybe the best solution would be checking if the file exists and then requireing?

Here is my version for PSR-0 proposal https://gist.github.com/1335891. It checks existence of the file and have two more features: relative paths and multiple include paths for single namespace. So you can configure loader like this:

$loader = new SplClassLoader('MyVendor', array('../vendor', '../src'));
$loader->register()

Is anyone likely to actually need to change the namespace separator at runtime using those getters and setters? Perhaps I'm failing to understand what use-case that functionality is intended for, but wouldn't it be better to define that value as a constant instead?

@henry-smith Agree with you. Namespace separator is defined as a constant and definitely won't be changed.

@henry-smith and @lisachenko I think you guys are missing the point of the namespace separator. It's not there because you're going to change the namespace separator at runtime. That would be silly. Instead, it exists to support interoperability between different class naming conventions across vendor libraries in the same project.

For example, let's say you use a class from the original Zend Framework in your project. The ZF1 class naming convention uses underscores and looks like Zend_View_Helper. Meanwhile, in your PHP5.3+ awesomeness you utilize the more modern approach of namespacing your classes like mylib\AwesomeClass. The SplClassLoader is providing you a means to autoload both with the same underlying code (hence the name, Standard):

$myLibLoader = new SplClassLoader('mylib', '/path/to/mylib/src');
$myLibLoader->register();

$zendLoader = new SplClassLoader('Zend', '/path/to/zend/lib');
$zendLoader->setNamespaceSeparator('_');
$zendLoader->register();

Obviously, if you only utilize one namespace in your project and don't reference outside vendor libraries you'll never have need of this functionality.

As far as using require instead of include ...

At first I thought include might be better and that require was overkill. After all, you might not want script execution to die with a fatal E_ERROR if a file isn't found. But the more I considered it, I changed my mind.

First, the class loader is only invoked when a class that doesn't already exist is needed for script execution. Your script should die if you reference a class that doesn't exists and it can't be loaded. A missing class definition isn't something that should be handled. It's incumbent on the programmer to make sure these files make it in at runtime.

As for the argument that a downstream autoloader might exist to load a class not found (necessitating include over require) ... IMHO this would be a poor practice. The point of the SplClassLoader is to streamline autoloading of class files. There's no reason to waste resources invoking multiple autoloaders when the autoloader explicitly defines how class files should be named in the first place. One class loader per namespace is all that's needed; any more than that is unnecessary at best and terribly inefficient at worst.

Ah, nice clarification. I understand now, thanks. The docblock for setNamespaceSeparator() does kind of imply this too:

Sets the namespace separator used by classes in the namespace of this class loader.

The meaning of that would be absolutely clear to anyone who had that need as well. Kind of presumptuous of me to try to second guess a design decision just because I didn't understand it. Sorry about that!

@lisachenko (comment on July 12, 2011), why do you think that loadClass() must return boolean result?

PHP checks after each autoloader if class exists and if it's true it stop autoloaders queue. See http://www.php.net/manual/ru/function.spl-autoload-register.php#96952

line 52
@return void

should be
@return string

Setters could return $this for method chaining. Powerful in 5.4

(new SplClassLoader())->setIncludePath(realpath(dirname(__FILE__) . '/lib'))
                      ->register();

I was trying to load the Slim Framework like this:

$classLoader = new SplClassLoader('Slim', 'libs/Slim');
$classLoader->setNamespaceSeparator('_');
$classLoader->register();

But I wasn't able to, I found the problem was the following in Line 123:

$this->_namespace.$this->_namespaceSeparator === substr($className, 0, strlen($this->_namespace.$this->_namespaceSeparator))

The problem is that the statement looks like Slim_ === Slim so it returns false, It works fine with namespaced classes though

If this class is to insist on require() instead of include & return false, and it's being held up as the gold standard by the authors of PSR, then surely for consistency they should deprecate the class_exists() function?

As has been noted by several people above, the autoloader is not called only when a class is needed; it is also called whenever a class' existence needs to be checked, particularly in frameworks and apps that use a plugin architecture. Autoloaders which ignore this are broken.

I must agree with @parsingphase. A stacked autoloader implementation with require() must only use require if it first checks with either file_exists() or is_readable().

you should use strtr (for a char replace) vs str_replace (more faster) ...

SRP principle : you have forget a resolveFileName($class) function, should not be defined in loadClass function ...

Wouldn't be better if the path matching with the class name was previously converted to lowercase?
That would resolve conflicts with Windows and would normalize the directory structure too:

Example:

class:
framework\common\Request
path:
framework/common/request.php

Why does the constructor accept NULL value for both parameters? That makes no sense in my opinion.

This example doesn't follow PSR-2 coding style. I think PSR should refer to examples which follow PSR. There is at least one issue - private members prefixed with "_".

Isn't it a little strange use a construction like "if (false !== ($lastNsPos = strripos($className, $this->_namespaceSeparator)))[...]", with the false statement first? Looks like a little causative (like master yoda talks)

How about having a static class that do all the work for a generic autoloading

public static function autoRegister($ns = null, $includePath = null){
    $instance = new static($ns, $includePath);
    $instance->register();
}

The comment of @parsingphase is so true!
You have to use include instead of require for 3 reasons:

  • Usage of class_exists, interface_exists & trait_exists are broken
  • Spl autoloading stack may contain other type of loader to support different naming convention (like Pear), using require force the end user to put you loader at the bottom of the stack, else it broke everything
  • Using require will trigger a fatal error at line 133 of file SplClassLoader.php, WTF : how can I find where the missing or mispelled class is in my code, now?

Just in case you need a lightweight solution to autoload PSR-0 and Zend like libs, take a look at this gist.

It's extreme, it's not following PSR-1 / PSR-2 coding standards at all, but it's short, fun and effective :

<?php 
set_include_path(get_include_path().PATH_SEPARATOR.__DIR__);//optional
spl_autoload_register(function($c){
    @include preg_replace('#\\\|_(?!.*\\\)#','/',$c).'.php';
});

Thanks for the useful autoloader!

This class should be updated to match the the PSR-2 standard

This is the way I do it. It only requires the file if its on the include path, so you get your error messages in the right place and multiple autoloaders can be used, like in @adriengibrat's comment.

<?php
spl_autoload_register(function($class) {
    $parts = explode('\\', $class);

    # Support for non-namespaced classes.
    $parts[] = str_replace('_', DIRECTORY_SEPARATOR, array_pop($parts));

    $path = implode(DIRECTORY_SEPARATOR, $parts);

    $file = stream_resolve_include_path($path.'.php');
    if($file !== false) {
        require $file;
    }
});

Current implementation implicitly shows that there is only one include path possible for one Vendor Name

But let`s imagine I have the following structure of the code
folder1/Vendor Name 1/Namespace 1/...
folder2/Vendor Name 1/Namespace 1/..
folder2/Vendor Name 1/Namespace 2/...

There is no one rule from PSR-0 has conflict with this case:

  • A fully-qualified namespace and class must have the following structure <Vendor Name>()*
  • Each namespace must have a top-level namespace ("Vendor Name")
  • Each namespace can have as many sub-namespaces as it wishes.
  • etc...

You cannot have the same namespace in two folders, like your Vendor1. You might be lucky and have Subnamespaces below that Namespace1, and can then use this loader.

Otherwise, you'd have to solve the question of "how do you know where the file for a class is", and if the answer is "I start looking in folder1, and if nothing is found, continue with folder2", that might work, but is is not really PSR-0 compliant.

And it isn't feasible as well. What if the same class exists in both folders?

What do you think is it make sense to add this rule item into PSR-0 standards?
P.S. I guess here is an answer https://github.com/php-fig/fig-standards/blob/f4d92e6b4003f42c20403e078e9d5bbde2e984cc/proposed/autoloader.md

I think "include" is more logical, after all the method class loader utilises to load a class should be encapsulated. No one needs to know about that, so using "file_exists" is the best.

If the class is not found with your class load the php will keep searching until it reaches the last class loader, you don't need to worry about boolean return either. A fatal error will be generated if PHP not found the class anyway. Don't forget that some libraries use their own class loaders and using "require" will break them.

pls add a licence ( LGPL / MIT / BSD / GPL / AGPL )

Am I missing something here (I'm a namespace noob)?

Using a namespaced class such as Zend\XmlRpc\Server.php (use Zend\XmlRpc) and stepping through the code, the function :

tryLoadClassByPath($className, $unresolvedFilePath) 

always returns false for namespaced classes because the $className parameter is the bare
class name without its namespace prefix so fails the test.

$isFound = class_exists($className, false);

If I modify the calling code to use the namespaced class like this:

public function loadClass($className)
{
        $originalClassName = $className;

... and use this later ...

                $isFound = $this->tryLoadClassByPath($originalClassName, $unresolvedFilePath);

then it returns true as expected. The class file is successfully loaded in any case as the file path is correctly setup so you don't actually see much happen even if the function does return false but I am fairly certain it should return true.

There was a problem with constants I'd defined. My BASE_PATH CONST was defined like this

define('BASE_PATH', dirname(realpath(__FILE__)) . '/');

So when I instantiated your class like this

$classLoader = new SplClassLoader('App\Controllers\Content', BASE_PATH);

The trailing Slash from my BASE_PATH Const was the causefor an error.
To modify this class by adding a rtrim() to the __construct method worked for me:

public function __construct($ns = null, $includePath = null)
{
    $this->_namespace = $ns;
    $this->_includePath = rtrim($includePath, '/');
}

Perhaps something for the next Version.....no one needs trailing slashes :)

As ClemensSahs mentioned I would also like to know the license for this code.

Please remove the http://groups.google.com/group/php-standards/web/final-proposal link in the file, it leads to a 404 page.

Zeokat very usefull piece of code. I need to know the license because i thinking into using it.
Thanks.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.