Skip to content

Instantly share code, notes, and snippets.

@Nilpo
Last active August 25, 2023 18:41
Show Gist options
  • Save Nilpo/5de133d2ab7a025bebeb to your computer and use it in GitHub Desktop.
Save Nilpo/5de133d2ab7a025bebeb to your computer and use it in GitHub Desktop.
A simple recursive PHP autoloader using the RecursiveDirectoryIterator.
<?php
class A
{
public function __construct()
{
echo get_class($this);
}
}
// EOF
<?php
/*
* The MIT License (MIT)
*
* Copyright (c) 2014 Rob Dunham
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Simple Recursive Autoloader
*
* A simple autoloader that loads class files recursively starting in the directory
* where this class resides. Additional options can be provided to control the naming
* convention of the class files.
*
* @package Autoloader
* @license http://opensource.org/licenses/MIT MIT License
* @author Rob Dunham <contact@robunham.info>
*/
class Autoloader
{
/**
* File extension as a string. Defaults to ".php".
*/
protected static $fileExt = '.php';
/**
* The top level directory where recursion will begin. Defaults to the current
* directory.
*/
protected static $pathTop = __DIR__;
/**
* A placeholder to hold the file iterator so that directory traversal is only
* performed once.
*/
protected static $fileIterator = null;
/**
* Autoload function for registration with spl_autoload_register
*
* Looks recursively through project directory and loads class files based on
* filename match.
*
* @param string $className
*/
public static function loader($className)
{
$directory = new RecursiveDirectoryIterator(static::$pathTop, RecursiveDirectoryIterator::SKIP_DOTS);
if (is_null(static::$fileIterator)) {
static::$fileIterator = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::LEAVES_ONLY);
}
$filename = $className . static::$fileExt;
foreach (static::$fileIterator as $file) {
if (strtolower($file->getFilename()) === strtolower($filename)) {
if ($file->isReadable()) {
include_once $file->getPathname();
}
break;
}
}
}
/**
* Sets the $fileExt property
*
* @param string $fileExt The file extension used for class files. Default is "php".
*/
public static function setFileExt($fileExt)
{
static::$fileExt = $fileExt;
}
/**
* Sets the $path property
*
* @param string $path The path representing the top level where recursion should
* begin. Defaults to the current directory.
*/
public static function setPath($path)
{
static::$pathTop = $path;
}
}
Autoloader::setFileExt('.php');
spl_autoload_register('Autoloader::loader');
// EOF
<?php
class B
{
public function __construct()
{
echo __CLASS__;
}
}
// EOF
<?php
class C extends A
{
public function __construct()
{
parent::__construct();
}
}
// EOF
<?php
include 'Autoloader.php';
$a = new A;
$b = new B;
$c = new C;
// EOF
@matthew798
Copy link

Does this autoloader overcome the lower case problem with the default autoloader?

EDIT
To answer my own question, yes!

@emresaracoglu
Copy link

Script work thank you but how can I run this script with Namespace?

@Firestorm-Graphics
Copy link

you can iterate through child directories too, just replace the loader function with this:

/** * Autoload function for registration with spl_autoload_register * * Looks recursively through project directory and loads class files based on * filename match. * * @param string $className */ public static function loader($className) { if (is_null(static::$fileIterator)) { static::$fileIterator = new RecursiveIteratorIterator( new RecursiveDirectoryIterator(static::$pathTop, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST, RecursiveIteratorIterator::CATCH_GET_CHILD // Ignore "Permission denied" ); } $filename = $className . static::$fileExt; foreach (static::$fileIterator as $file) { if (strtolower($file->getFilename()) === strtolower($filename)) { if ($file->isReadable()) { include_once $file->getPathname(); } break; } } }

@Raserad
Copy link

Raserad commented Jun 21, 2018

You can use static map for save previous founded class, for a better performance))

@nkmwambs
Copy link

Thanks this script works fine

@knot22
Copy link

knot22 commented Mar 9, 2022

You can use static map for save previous founded class, for a better performance))

@Raserad Can you provide more specifics on how this would be done? I'm experimenting with this code in an app but have run into performance issues. If it could "remember" the files instead of repeating the scan every time an API call is made, it would probably help immensely.

@knot22
Copy link

knot22 commented Mar 9, 2022

@Nilpo This is a really helpful example - glad you shared it. Regarding Autoloader.php - why is the code on lines 117 and 118 outside of the class declaration? At what point are those lines executed?

@Nilpo
Copy link
Author

Nilpo commented Mar 9, 2022

@Nilpo This is a really helpful example - glad you shared it. Regarding Autoloader.php - why is the code on lines 117 and 118 outside of the class declaration? At what point are those lines executed?

Those lines are executed when the PHP file is executed which would generally be at the time the file is imported.

When the Autoloader class is instantiated, it doesn't actually do anything yet. It's loader method does the heavy lifting, but we need to tell PHP to call it whenever an unknown class is encountered in our scripts. After loading the Autoloader class into memory, these two lines set the default file extension to *.php files (we ignore the rest) and then register the loader function as a custom handler for PHP to use when autoloading class files. By placing these lines at the bottom of our class file, we ensure that they are procedurally executed immediately after our class becomes available. It also eliminates the need to remember to do it as an additional step after importing.

@Nilpo
Copy link
Author

Nilpo commented Mar 9, 2022

You can use static map for save previous founded class, for a better performance))

@Raserad Can you provide more specifics on how this would be done? I'm experimenting with this code in an app but have run into performance issues. If it could "remember" the files instead of repeating the scan every time an API call is made, it would probably help immensely.

This is intended to be pretty lightweight. What type of performance issues are you having? Do you have a lot of classes?

@knot22
Copy link

knot22 commented Mar 10, 2022

When sending Postman requests to about 60 endpoints after implementing a version of this code, they took longer to respond than when an autoloader with hardcoded values was used. An approximation would be 1.5 times as long. There are 25 classes in the app.

@elidrissidev
Copy link

elidrissidev commented Oct 19, 2022

Thank you, this works great! One good use case I found for a custom autoloader like this one is when you are using Composer classmaps but don't want to keep doing dump-autoload every time after you add a new class. Instead, I use this recursive autoloader for development, and Composer classmaps for production.

And by the way this is useful mainly for legacy projects, for new ones I'd highly recommend using PSR-4 namespaces ;).

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