Skip to content

Instantly share code, notes, and snippets.

@jwage
Last active August 16, 2024 17:36
Show Gist options
  • Save jwage/221634 to your computer and use it in GitHub Desktop.
Save jwage/221634 to your computer and use it in GitHub Desktop.
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
* <https://www.doctrine-project.org>.
*/
/**
* SplClassLoader implementation that implements the technical interoperability
* standards for PHP 5.3 namespaces and class names.
*
* https://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 https://www.opensource.org/licenses/mit-license.html MIT License
* @author Jonathan H. Wage <[email protected]>
* @author Roman S. Borschel <[email protected]>
* @author Matthew Weier O'Phinney <[email protected]>
* @author Kris Wallsmith <[email protected]>
* @author Fabien Potencier <[email protected]>
*/
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;
}
}
}
@adriengibrat
Copy link

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
Copy link

Thanks for the useful autoloader!

@papa-smurf
Copy link

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

@maskwang
Copy link

Very useful~

@jjok
Copy link

jjok commented Apr 24, 2013

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
Copy link

voleye commented Jul 30, 2013

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 ()*
  • Each namespace must have a top-level namespace ("Vendor Name")
  • Each namespace can have as many sub-namespaces as it wishes.
  • etc...

@SvenRtbg
Copy link

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
Copy link

voleye commented Jul 31, 2013

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
Copy link

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
Copy link

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

@stevegrove
Copy link

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.

@chermsen
Copy link

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
Copy link

bstoots commented Dec 2, 2013

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

@saji89
Copy link

saji89 commented Jan 6, 2014

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

@Zeokat
Copy link

Zeokat commented Mar 1, 2014

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

@morojosa
Copy link

Hi All,

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

Regards

@morojosa
Copy link

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

@richardpq
Copy link

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
Copy link

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
Copy link

keradus commented Aug 8, 2014

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
Copy link

Dabnis commented Dec 20, 2014

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
Copy link

Got it.

@roquie
Copy link

roquie commented Feb 4, 2015

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

@mlefterov
Copy link

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
Copy link

v3u3i87 commented Apr 16, 2015

get class in to on?

@ghorpade-naveen
Copy link

SplClassLoader line 133 is throwing error. File not found. But the file is present in another folder, how to fix this

@Romanzo
Copy link

Romanzo commented Jan 8, 2016

@klaussantana
Copy link

klaussantana commented Oct 4, 2016

Hello there people! New important info forgotten here..

The method loadClass on line 141 must return a boolean value! Recomended:

return class_exists($className, false) || trait_exists($className, false) || interface_exists($className, false);

This is imperative! PHP SPL autoload function suports bubbling effect.. You can register as many autoload functions as necessary, so, you must return a boolean value to indicate if the function loaded the resource or not. If it returns true, the SPL stops casting autoload functions.. if it returns false it keeps goin on the rest of the functions registered through spl_autoload_register.

The second parameter in every *_exists methods refer to autoloading and it must be false inside the autoload function scope to avoid infinite loop (the autoload function casting itself). If you try to load an interface, class_exists will drop false, because it's not a class, but interface_exists will drop true, then the autoload stops trying to load that resource.

Also, it's recommended to use include_once than any of other options. For instance, if you try to load My\Personal\Class\Name and My_Personal_Class_Name as the code is it will drop a fatal error trying to duplicate the resource (class, trait or interface). The fix proposed here will solve this problem.

See ya!

@GraniteConsultingReviews

I am trying this code from last 5 hours and it dint work.

@d4rkne55
Copy link

d4rkne55 commented Jul 5, 2018

Can someone remove the spam comments from 2017 (since first-reviews' comment)? I don't see any possibility to flag them from my side..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment