Skip to content

@jwage /SplClassLoader.php
Last active

Embed URL

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Add MIT license.
<?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;
}
}
}
@jonahbron

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

@till

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

@lisachenko

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;
@jonahbron
@fuhrysteve

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

@jonahbron
@Thinkscape

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

@guilhermeblanco

@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,

@lessthanthree

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

@bxt
bxt commented

@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.

@lessthanthree

@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. :)

@bxt
bxt commented

@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?

@lisachenko

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()
@hnrysmth

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?

@lisachenko

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

@rdlowrey

@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.

@hnrysmth

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!

@kabanovdmitry

@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

@smallslime

line 52
@return void

should be
@return string

@grisha87

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

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

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

@parsingphase

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.

@shrikeh

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().

@ichiriac

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

@ichiriac

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

@javis

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

@Trainmaster

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

@Krienas

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 "_".

@pauloFernandes

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)

@Faryshta

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();
}
@adriengibrat

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?
@adriengibrat

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';
});
@raymondjplante

Thanks for the useful autoloader!

@papa-smurf

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

@maskwang

Very useful~

@jjok

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;
    }
});
@voleye

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...
@SvenRtbg

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?

@voleye

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

@jairhumberto

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.

@ClemensSahs

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

@stevegrove

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.

@commicon

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 :)

@bstoots

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

@saji89

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

@Zeokat

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

@morojosa

Hi All,

Why declared methods setIncludePath / getIncludePath and not declared methods getNamespace and setNamespace in this SplClassLoader ??

Regards

@morojosa

Download file of this gist don't have white line in the end file. See PSR2

@richardpq

This link
https://gist.github.com/r2pq/aebc8131955ee819086f

Is a fork to this file with the next PHPDoc fixes:

  • The constructor has two parameters, this was not reflected before.
  • The PHPDoc related to getNamespaceSeparator(), had a typo. It said 'seperator' instead of 'separator'.
  • The PHPDoc related to getNamespaceSeparator(), now has a return string instead of return void.
@hollodotme

I think, there is a problem with this implementation or the PSR-0 itself. When - theoretically - using classnames that begin or end with and underscore "", e.g. \Vendor\Namespace_Class_Extension.php

This would end up in something like this:
Vendor/Namespace//Class/Extension/.php and will definitely fail to load.

There is no omission or MUST NOT for leading or trailing underscores in the definition of classnames.

To avoid this, just replace the line:

<?php

$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->_fileExtension;

with

<?php

$fileName .= preg_replace("#([a-z0-9]+)_([a-z0-9]+)#i", "$1" . DIRECTORY_SEPARATOR . "$2", $className) . $this->_fileExtension;

and the example above will end up in:

Vendor/Namespace/_Class/Extension_.php

@keradus

Maybe useful:
https://github.com/keradus/SplClassLoader - taken from PSR and little fixed like docs etc.
If need anything to change just PR.

Btw, consider using PSR4 autoloader:
https://github.com/keradus/Psr4Autoloader

@Dabnis

Here is the file loading solution I use within my autoloader:
REM** $class can be a Class OR an Interface

include $filename;
// Check to see if the include declared the class
if(!class_exists( $class, FALSE )) {
// Check to see if the include declared the interface
if(!interface_exists( $class, FALSE )){
// We do not have the class nor interface : file did not load or did not exist
// Do something : Log error; check other file resources or die gracefully.
}
}

Why I use this:
Loading files is normally the slowest part of any php application. Using file_exists(), even though it does not load the file still takes 'almost' as much time as loading the file itself (assuming a small file which class files tend to be >10Kb). So if you use:

file_exists(fileName){
require fileName;
}

From a application run time perspective its like loading the file twice !!! In addition require will kill your app if it fails where include only throws a warning.

Testing if the class or interface exists after an include will give you options to try something else before YOU gracefully exit your application.

Hope this helps

@donghongya2011

Got it.

@Roquie

Maybe replace 'require' with 'require_once' in loadClass(...) method ?
http://php.net/manual/en/function.require-once.php

@mlefterov

Hi guys, I have trouble.
Using SplClassLoader loosing displaying of parse errors and it is really pain the ass to debug the code manually. I can only catch then with register_shutdown_function(). I have set error_reporting to E_ALL, I did almost everything to display them and nothing.

If I require the classes manually everything works as expected (any errors are displayed). Any suggestions?

@v3u3i87

get class in to on?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.