I've just begun using autoloader lazy loading in my app, and I'm running afoul of namespacing. The autoloader is trying to load things like new DateTime() and failing. Is there a trick to making my autoloader spcific to only my own namespaced classes?
Here is the code I have currently. I suspect it is a mess, but I'm not seeing just how to correct it:
<?php namespace RSCRM;
class Autoloader {
static public function loader($className) {
$filename = dirname(__FILE__) .'/'. str_replace("\\", '/', $className) . ".php";
if (file_exists($filename)) {
include_once($filename);
if (class_exists($className)) {
return TRUE;
}
}
return FALSE;
}
}
spl_autoload_register('\RSCRM\Autoloader::loader');
Happy to RTM if someone can point to a solid example.
What I use is actually adapted from the autoloader used to Unit Test a few of the AuraPHP libraries:
<?php
spl_autoload_register(function ($class) {
// a partial filename
$part = str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php';
// directories where we can find classes
$dirs = array(
__DIR__ . DIRECTORY_SEPARATOR . 'src',
__DIR__ . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'src',
__DIR__ . DIRECTORY_SEPARATOR . 'install' . DIRECTORY_SEPARATOR . 'src',
);
// go through the directories to find classes
foreach ($dirs as $dir) {
$file = $dir . DIRECTORY_SEPARATOR . $part;
if (is_readable($file)) {
require $file;
return;
}
}
});
Just make sure the array of '$dirs' values point to the root of your namespaced code.
You can also take a look at the PSR-0 example implementation (http://www.php-fig.org/psr/psr-0/).
You might also want to look an into existing autoloader, like Aura.Autoload or the Symfony ClassLoader Component, although those might be overkill depending on what your requirements are.
I hope this helps.
I have placed the following code in my functions file (MyFunctions.php):
spl_autoload_register
(
function( $Class )
{
$Class = str_replace ( "..", "", $Class );
include $_SERVER['DOCUMENT_ROOT'] . '/phpsb/class library/' . $Class . '.php';
}
);
But when I reference an class/object from my script, spl_autoload_register searches in the functions file in which it is defined, rather than class library directory:
Warning: include(C:/xampp/htdocs/phpsb/class library/FormClass.php): failed to open stream: No such file or directory in C:\xampp\htdocs\sandbox\function library\MyFunctions.php
Try this:
spl_autoload_register(function ($className) {
$className = (string) str_replace('\\', DIRECTORY_SEPARATOR, $className);
include_once(dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR . $className . '.php');
});
This code supports namespaces and i'm supposing MyFunctions.php is in the same directory of your dinamically loaded PHP files.
I'm trying to build my own framework for internal usage.
I got structure like this:
index.php
boot /
booter.php
application /
controllers /
indexcontroller.php
core /
template.class.php
model.class.php
controller.class.php
cache /
memcached.php
something /
something.php
Booter.php contains: (it's currently working only with files located in core directory):
class Booter
{
private static $controller_path, $model_path, $class_path;
public static function setDirs($controller_path = 'application/controllers', $model_path = 'application/models', $classes_path = 'core')
{
self::$controller_path = $controller_path;
self::$model_path = $model_path;
self::$class_path = $classes_path;
spl_autoload_register(array('Booter', 'LoadClass'));
if ( DEBUG )
Debugger::log('Setting dirs...');
}
protected static function LoadClass($className)
{
$className = strtolower($className);
if ( file_exists(DIR . '/' . self::$model_path . '/' . $className . '.php') )
{
require(DIR . '/' . self::$model_path . '/' . $className . '.php');
}
else if ( file_exists(DIR . '/' . self::$class_path . '/' . $className . '.class.php') )
{
require(DIR . '/' . self::$class_path . '/' . $className . '.class.php');
}
else if ( file_exists(DIR . '/' . self::$controller_path . '/' . $className . '.php') )
{
require(DIR . '/' . self::$controller_path . '/' . $className . '.php');
}
if ( DEBUG )
Debugger::log('AutoLoading classname: '.$className);
}
}
My application/controllers/indexcontroller looks like this:
<?
class IndexController extends Controller
{
public function ActionIndex()
{
$a = new Model; // It works
$a = new Controller; //It works too
}
}
?>
And here comes my questions:
[Question 1]
My code is working currently like this:
$a = new Model; // Class Model gets included from core/model.class.php
How I can implement including files by classes with namespaces? For example:
$a = new Cache\Memcached; // I would like to include file from /core/CACHE/Memcached.php
$a = new AnotherNS\smth; // i would like to include file from /core/AnotherNS/smth.php
and so on. How I can produce the handling of the namespace?
[Question 2]
Is it a good practice to use single autoload for Classes, Controllers and models or I should define 3 different spl_autoload_register with 3 different methods and why?
I normally have a bootstrap.php file inside a folder conf that is in the application root. My code is normally located inside an src folder, also located in the root, so, this works fine for me:
<?php
define('APP_ROOT', dirname(__DIR__) . DIRECTORY_SEPARATOR);
set_include_path(
implode(PATH_SEPARATOR,
array_unique(
array_merge(
array(
APP_ROOT . 'src',
APP_ROOT . 'test'
),
explode(PATH_SEPARATOR, get_include_path())
)
)
)
);
spl_autoload_register(function ($class) {
$file = sprintf("%s.php", str_replace('\\', DIRECTORY_SEPARATOR, $class));
if (($classPath = stream_resolve_include_path($file)) != false) {
require $classPath;
}
}, true);
You can generalize this into your "Booter" class and append directories to the include path. If you have well defined namespace names, there will be no problems with colisions.
Edit:
This works if you follow PSR-1.
Question 1:
In your autoloader, change the \ (for the namespace) into DIRECTORY_SEPARATOR. This should work:
protected static function LoadClass($className)
{
$className = strtolower($className);
$className = str_replace('\\', DIRECTORY_SEPARATOR, $className);
...
}
Always use DIRECTORY_SEPARATOR, especially if the software has the potential to be used on other platforms.
Question 2:
I would use one and separate your classes by namespace. However, I think that comes down to how you want to structure your framework and how you separate your code. Someone else may be able to better answer that question.
I have a tiny application that i need an autoloader for. I could easily use the symfony2 class loader but it seems like overkill.
Is there a stable extremely lightweight psr-0 autloader out there?
You ask extremely lightweight, let's do so ;)
Timothy Boronczyk wrote a nice minimal SPL autoloader : http://zaemis.blogspot.fr/2012/05/writing-minimal-psr-0-autoloader.html
I condensed the code like this:
function autoload1( $class ) {
preg_match('/^(.+)?([^\\\\]+)$/U', ltrim( $class, '\\' ), $match ) );
require str_replace( '\\', '/', $match[ 1 ] )
. str_replace( [ '\\', '_' ], '/', $match[ 2 ] )
. '.php';
}
Then compare (minified versions of) this [autoload3] with short #Alix Axel code [autoload4] :
function autoload3($c){preg_match('/^(.+)?([^\\\\]+)$/U',ltrim($c,'\\'),$m);require str_replace('\\','/',$m[1]).str_replace(['\\','_'],'/',$m[2]).'.php';}
function autoload4($c){require (($n=strrpos($c=ltrim($c,'\\'),'\\'))!==false?str_replace('\\','/',substr($c,0,++$n)):null).str_replace('_','/',substr($c,$n)).'.php';}
autoload3 is the shortest !
Let's use stable & extremely lightweight (175b !) autoloader file :
<?php spl_autoload_register(function ($c){preg_match('/^(.+)?([^\\\\]+)$/U',ltrim($c,'\\'),$m);require str_replace('\\','/',$m[1]).str_replace(['\\','_'],'/',$m[2]).'.php';});
Maybe i'm crazy but you Asked for extreme, no?
EDIT: Thanks to Alix Axel, i've shorten the code (only 100b !) and used include instead of require in case you have various autoloading strategy for old libs (and then various autoloader in spl autoload stack...).
<?php spl_autoload_register(function($c){#include preg_replace('#\\\|_(?!.+\\\)#','/',$c).'.php';});
If you want to make it shorter / better, please use this gist.
The PSR-0 specification document has an examplary compatible autoloader function that is already pretty short:
function autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strripos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
require $fileName;
}
It's usage is pretty straight forward:
spl_autoload_register('autoload');
The shortcoming with it is, that you need to configure base-directories it works on with the include_path directive. To support a hybrid PSR-0 autoloaders leaned onto SPL semantics, the following one supportss include path and spl autoload extensions:
$spl_autoload_register_psr0 = function ($extensions = null)
{
$callback = function ($className, $extensions = null)
{
if (!preg_match('~^[a-z0-9\\_]{2,}$~i', $className)) {
return;
}
null !== $extensions || $extensions = spl_autoload_extensions();
$extensions = array_map('trim', explode(',', $extensions));
$dirs = array_map('realpath', explode(PATH_SEPARATOR, get_include_path()));
$classStub = strtr($className, array('_' => '/', '\\' => '/'));
foreach ($dirs as $dir) {
foreach ($extensions as $extension) {
$file = sprintf('%s/%s%s', $dir, $classStub, $extension);
if (!is_readable($file)) {
continue;
}
include $file;
return;
}
}
};
return spl_autoload_register($callback);
};
The The Symfony2 ClassLoader Component has the benefit to allow more configuration here. You can install it easily via Pear or Composer (symfony/class-loader on Packagist). It is a component on it's own that is used by many and fairly well tested and supported.
SplClassLoader seems like a right choice. It's an implementation proposed by PSR-0 itself.
An exact equivalent of the answer #hakre provided, just shorter:
function autoload($class) {
$path = null;
if (($namespace = strrpos($class = ltrim($class, '\\'), '\\')) !== false) {
$path .= strtr(substr($class, 0, ++$namespace), '\\', '/');
}
require($path . strtr(substr($class, $namespace), '_', '/') . '.php');
}
You can also set the base directory by changing $path = null; to another value, or just do like this:
$paths = array
(
__DIR__ . '/vendor/',
__DIR__ . '/vendor/phunction/phunction.php',
);
foreach ($paths as $path)
{
if (is_dir($path) === true)
{
spl_autoload_register(function ($class) use ($path)
{
if (($namespace = strrpos($class = ltrim($class, '\\'), '\\')) !== false)
{
$path .= strtr(substr($class, 0, ++$namespace), '\\', '/');
}
require($path . strtr(substr($class, $namespace), '_', '/') . '.php');
});
}
else if (is_file($path) === true)
{
require($path);
}
}
The doctrine classloader is another good choice. You can easily install it with composer
This is not a direct answer to the question, but I found that the above answers worked great on standalone PHP scripts, but were causing issues when used in certain frameworks, such as Joomla.
For anyone using Joomla, it turns out that there is a compatible autoloader already built into the framework, therefore you won't need to use the above functions. In that instance, just call JLoader::registerNamespace().... for example:
JLoader::registerNamespace('Solarium', JPATH_LIBRARIES . DS . 'solarium-3.2.0' . DS . 'library');
function autoload($fullClassName) {
$name_elems = explode('\\', $fullClassName);
require __DIR__.DIRECTORY_SEPARATOR.implode(DIRECTORY_SEPARATOR, $name_elems).'.php';
}
This even supports things like:
$transformerContstraint = new \Recurr\Transformer\Constraint\AfterConstraint(new DateTime());
Just put it in /vendor/Recurr/Transformer/Constraint/AfterConstraint.php
I have the following php code:
index.php
<?php
spl_autoload_extensions(".php");
spl_autoload_register();
use modules\standard as std;
$handler = new std\handler();
$handler->delegate();
?>
modules\standard\handler.php
<?php
namespace modules\standard {
class handler {
function delegate(){
echo 'Hello from delegation!';
}
}
}
?>
Under Windows 7, running WAMP, the code produces the message "Hello from Delegation!" however under Linux, I get the following:
Fatal error: spl_autoload(): Class modules\standard\handler could not be loaded in /var/www/index.php on line 15
Windows is running PHP 5.3.0 under WAMP, and Linux is running the 5.3.2 dotdeb package under Ubuntu 9.10.
Is this a configuration issue on my linux box, or just a difference in the way namespaces and autoloading is handled on the different operating systems
The SPL autoloader is extremely primitive - it has no knowledge of namespaces, so it tries to load a file with \ in it's name while on Linux/Unix the path separator is / not .
Herman Radtke says he has submitted a patch :
http://www.hermanradtke.com/blog/hidden-features-with-spl_autoload-and-namespaces/
:s
I'm hoping it'll be implemented soon.
For now I use this workaround :
<?php
set_include_path( './classes/' . PATH_SEPARATOR . get_include_path() );
spl_autoload_extensions( '.php , .class.php' );
spl_autoload_register();
function linux_namespaces_autoload ( $class_name )
{
/* use if you need to lowercase first char *
$class_name = implode( DIRECTORY_SEPARATOR , array_map( 'lcfirst' , explode( '\\' , $class_name ) ) );/* else just use the following : */
$class_name = implode( DIRECTORY_SEPARATOR , explode( '\\' , $class_name ) );
static $extensions = array();
if ( empty($extensions ) )
{
$extensions = array_map( 'trim' , explode( ',' , spl_autoload_extensions() ) );
}
static $include_paths = array();
if ( empty( $include_paths ) )
{
$include_paths = explode( PATH_SEPARATOR , get_include_path() );
}
foreach ( $include_paths as $path )
{
$path .= ( DIRECTORY_SEPARATOR !== $path[ strlen( $path ) - 1 ] ) ? DIRECTORY_SEPARATOR : '';
foreach ( $extensions as $extension )
{
$file = $path . $class_name . $extension;
if ( file_exists( $file ) && is_readable( $file ) )
{
require $file;
return;
}
}
}
throw new Exception( _( 'class ' . $class_name . ' could not be found.' ) );
}
spl_autoload_register( 'linux_namespaces_autoload' , TRUE , FALSE );
?>
function __autoload($class_name) {
$paths[] = dirname(__FILE__) . "/../libs/misc/";
$paths[] = dirname(__FILE__) . "/../../libs/misc/";
$paths[] = dirname(__FILE__) . "/../../libs/helpers/";
$paths[] = dirname(__FILE__) . "/../../libs/simpleimage/";
foreach($paths as $path)
{
if(file_exists($path.strtolower($class_name).'.class.php')){
require_once($path.strtolower($class_name).'.class.php');
}
}
}
function __autoload($class_name)
{
$class_name = strtolower(str_replace('\\', DIRECTORY_SEPARATOR, $class_name));
include $class_name . '.php';
}
The srttolower is needed on Apache because it is (contrary to IIS) case sentive.
This is a common problem occurs when autoloading. The fix is to use DIRECTORY_SEPARATOR constant in the autoload function.
So your autoload function will look like following
<?php
spl_autoload_register(function($className) {
$className = str_replace("\", DIRECTORY_SEPARATOR, $className);
include_once $_SERVER['DOCUMENT_ROOT'] . '/class/' . $className . '.php';
});
If you need to learn more on namespace/class autoloading visit here
Thanks.