Skip to content

Instantly share code, notes, and snippets.

@jbroadway
Created February 1, 2012 19:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbroadway/1718897 to your computer and use it in GitHub Desktop.
Save jbroadway/1718897 to your computer and use it in GitHub Desktop.
Autoloader testing for Elefant CMS with and without a class map.
<?php
/**
* Elefant CMS - http://www.elefantcms.com/
*
* Copyright (c) 2011 Johnny Broadway
*
* 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.
*/
/**
* Autoloader for classes. Checks in the following order:
*
* 1. If `\` is found, using namespaces:
* 1.1. Look for `apps/{{app}}/lib/{{class}}.php`
* 1.2. Look for `apps/{{app}}/models/{{class}}.php`
* 1.3. PSR-0 fallback, look in `lib/vendors/`
* 2. Look for `lib/{{class}}.php`
* 3. Glob for `{{models,lib}}/{{class}}.php` in all apps
* 4. Return false
*
* For PSR-0 compatible frameworks, put them in `lib/vendors/`.
*/
function elefant_autoloader ($class) {
if (strpos ($class, '\\') !== false) {
// Namespace is present
list ($app, $class) = explode ('\\', $class, 2);
// Check for app\Class in lib and models folders
if (@file_exists ('apps/' . $app . '/lib/' . $class . '.php')) {
require_once ('apps/' . $app . '/lib/' . $class . '.php');
return true;
} elseif (@file_exists ('apps/' . $app . '/models/' . $class . '.php')) {
require_once ('apps/' . $app . '/models/' . $class . '.php');
return true;
}
// Fall back to PSR-0
if (! empty ($app)) {
$file = 'lib/vendor/' . ltrim ($app, '\\') . '/' . str_replace ('\\', '/', $class) . '.php';
} else {
$file = 'lib/vendor/' . str_replace ('\\', '/', ltrim ($class, '\\')) . '.php';
}
$file = str_replace ('_', '/', $file);
if (@file_exists ($file)) {
require_once ($file);
}
} elseif (@file_exists ('lib/' . $class . '.php')) {
// No namespace, check in lib/ first
require_once ('lib/' . $class . '.php');
return true;
} elseif (@file_exists ('lib/vendor/' . $class . '.php')) {
// No namespace, check in lib/vendor/ next
require_once ('lib/vendor/' . $class . '.php');
return true;
} else {
// No namespace, check in app lib and models folders
$res = glob ('apps/*/{models,lib}/' . $class . '.php', GLOB_BRACE);
if (is_array ($res) && count ($res) > 0) {
require_once ($res[0]);
return true;
}
}
return false;
}
spl_autoload_register ('elefant_autoloader');
?>
<?php
/**
* Elefant CMS - http://www.elefantcms.com/
*
* Copyright (c) 2011 Johnny Broadway
*
* 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.
*/
namespace e;
/**
* Autoloader for classes. Checks in the following order:
*
* 1. If `\` is found, using namespaces:
* 1.1. Look for `apps/{{app}}/lib/{{class}}.php`
* 1.2. Look for `apps/{{app}}/models/{{class}}.php`
* 1.3. PSR-0 fallback, look in `lib/vendors/`
* 2. Look for `lib/{{class}}.php`
* 3. Glob for `{{models,lib}}/{{class}}.php` in all apps
* 4. Return false
*
* For PSR-0 compatible frameworks, put them in `lib/vendors/`.
*/
class Autoloader {
/**
* The class map array. Starts off null until loaded from disk.
*/
public static $class_map = null;
/**
* The file to save the class map to on disk.
*/
public static $cache_file = 'conf/classmap.php';
/**
* This is the autoloader itself.
*/
public static function load ($class) {
$file = self::is_cached ($class);
if ($file) {
return self::load_class ($class, $file);
}
$orig = $class;
if (strpos ($class, '\\') !== false) {
// Namespace is present
list ($app, $class) = explode ('\\', $class, 2);
// Check for app\Class in lib and models folders
if (@file_exists ('apps/' . $app . '/lib/' . $class . '.php')) {
return self::load_class ($orig, 'apps/' . $app . '/lib/' . $class . '.php');
} elseif (@file_exists ('apps/' . $app . '/models/' . $class . '.php')) {
return self::load_class ($orig, 'apps/' . $app . '/models/' . $class . '.php');
}
// Fall back to PSR-0
if (! empty ($app)) {
$file = 'lib/vendor/' . ltrim ($app, '\\') . '/' . str_replace ('\\', '/', $class) . '.php';
} else {
$file = 'lib/vendor/' . str_replace ('\\', '/', ltrim ($class, '\\')) . '.php';
}
$file = str_replace ('_', '/', $file);
if (@file_exists ($file)) {
return self::load_class ($orig, $file);
}
} elseif (@file_exists ('lib/' . $class . '.php')) {
// No namespace, check in lib/ first
return self::load_class ($orig, 'lib/' . $class . '.php');
} elseif (@file_exists ('lib/vendor/' . $class . '.php')) {
// No namespace, check in lib/vendor/ next
return self::load_class ($orig, 'lib/vendor/' . $class . '.php');
} else {
// No namespace, check in app lib and models folders
$res = glob ('apps/*/{models,lib}/' . $class . '.php', GLOB_BRACE);
if (is_array ($res) && count ($res) > 0) {
return self::load_class ($orig, $res[0]);
}
}
return false;
}
/**
* Load a class file, and also add it to the map at the same time.
*/
public static function load_class ($class, $file) {
self::cache_class ($class, $file);
require_once ($file);
return true;
}
/**
* Add a class to the map. Also saves the map to disk. Only updates
* if the class isn't already mapped.
*/
public static function cache_class ($class, $file) {
if (! isset (self::$class_map[$class])) {
self::$class_map[$class] = getcwd () . DIRECTORY_SEPARATOR . $file;
self::save_cache ();
}
}
/**
* Check if a class is in the map yet. Also loads the cache from disk
* if it hasn't been loaded yet.
*/
public static function is_cached ($class) {
if (self::$class_map === null) {
// Cache hasn't been loaded yet
self::load_cache ();
}
if (isset (self::$class_map[$class])) {
// Class is in the cache
return self::$class_map[$class];
}
return false;
}
/**
* Update the cache file.
*/
public static function save_cache () {
if (! file_put_contents (self::$cache_file, serialize (self::$class_map))) {
return false;
}
return chmod (self::$cache_file, 0777);
}
/**
* Load the class map from disk.
*/
public static function load_cache () {
if (file_exists (self::$cache_file)) {
self::$class_map = unserialize (file_get_contents (self::$cache_file));
} else {
self::$class_map = array ();
}
}
}
spl_autoload_register (__NAMESPACE__ . '\Autoloader::load');
?>
<?php
namespace e;
require_once ('lib/Autoloader.php');
class AutoloaderTest extends \PHPUnit_Framework_TestCase {
static function setUpBeforeClass () {
@unlink ('conf/test_classmap.php');
Autoloader::$cache_file = 'conf/test_classmap.php';
Autoloader::$class_map = null;
}
static function tearDownAfterClass () {
unlink ('conf/test_classmap.php');
Autoloader::$cache_file = 'conf/classmap.php';
Autoloader::$class_map = null;
}
function test_load_cache () {
$this->assertEquals (null, Autoloader::$class_map);
Autoloader::load_cache ();
$this->assertEquals (array (), Autoloader::$class_map);
}
function test_cache_class () {
Autoloader::cache_class ('foo\Bar', 'foo/Bar.php');
$this->assertEquals ('foo/Bar.php', Autoloader::$class_map['foo\Bar']);
}
function test_save_and_reload () {
Autoloader::cache_class ('foo\Asdf', 'foo/Asdf.php');
// Wrote more than zero bytes
$this->assertGreaterThan (0, Autoloader::save_cache ());
// Reset class map
Autoloader::$class_map = null;
Autoloader::load_cache ();
$this->assertTrue (is_array (Autoloader::$class_map));
$this->assertGreaterThan (0, count (Autoloader::$class_map));
$this->assertEquals ('foo/Asdf.php', Autoloader::$class_map['foo\Asdf']);
}
function test_is_cached () {
Autoloader::cache_class ('foo\Asdf', 'foo/Asdf.php');
$this->assertFalse (Autoloader::is_cached ('foo\NotCached'));
$this->assertEquals ('foo/Asdf.php', Autoloader::is_cached ('foo\Asdf'));
}
}
?>
<?php
/**
* Generate 10,000 files for each caching method, so we can determine whether
* json_decode(), unserialize() or require() will be faster.
*/
// Real structure based on complete Elefant class map
$struct = array (
'ActiveResource' => 'lib/vendor/ActiveResource.php',
'ExtendedModel' => 'lib/ExtendedModel.php',
'Model' => 'lib/Model.php',
'MongoModel' => 'lib/MongoModel.php',
'Analog' => 'lib/vendor/Analog.php',
'Analog\Analog' => 'lib/vendor/Analog/Analog.php',
'Analog\Handler\File' => 'lib/vendor/Analog/Handler/File.php',
'Cache' => 'lib/Cache.php',
'Controller' => 'lib/Controller.php',
'blog\CsvParser' => 'apps/blog/lib/CsvParser.php',
'Database' => 'lib/Database.php',
'Diff' => 'apps/admin/lib/Diff.php',
'Form' => 'lib/Form.php',
'I18n' => 'lib/I18n.php',
'User' => 'apps/user/models/User.php',
'Lock' => 'apps/admin/lib/Lock.php',
'MongoManager' => 'lib/MongoManager.php',
'Navigation' => 'apps/navigation/lib/Navigation.php',
'Page' => 'lib/Page.php',
'Restful' => 'lib/Restful.php',
'Template' => 'lib/Template.php',
'Versions' => 'apps/admin/models/Versions.php',
'Analog\Handler\FirePHP' => 'lib/vendor/Analog/Handler/FirePHP.php',
'Webpage' => 'apps/admin/models/Webpage.php',
'Product' => 'lib/Product.php',
'Block' => 'apps/blocks/models/Block.php',
'FileManager' => 'apps/filemanager/lib/FileManager.php',
'blog\Post' => 'apps/blog/models/Post.php'
);
// Builds PHP output
function build_array ($struct) {
$o = '';
$sep = '';
foreach ($struct as $k => $v) {
$o .= $sep . '\'' . $k . '\' => \'' . $v . '\'';
$sep = ",\n";
}
return $o;
}
for ($i = 0; $i < 10000; $i++) {
// Save as JSON
file_put_contents ('files/json/test'.$i.'.json', json_encode ($struct));
// Save using serialize()
file_put_contents ('files/php/test'.$i.'.php', serialize ($struct));
// Save as PHP
file_put_contents (
'files/php2/test'.$i.'.php',
"<?php return array (\n".build_array ($struct)."\n); ?>"
);
}
?>
<?php
/**
* Generate 10,000 PHP classes for autoloader testing.
*/
for ($i = 0; $i < 10000; $i++) {
file_put_contents ('lib/Test' . $i . '.php', '<?php class Test' . $i . ' {} ?>');
}
?>
<?php
/**
* Read and parse 10,000 files using json_decode().
*/
$start = microtime (true);
for ($i = 0; $i < 10000; $i++) {
$struct = json_decode (file_get_contents ('files/json/test' . $i . '.json'), true);
}
echo microtime (true) - $start . "\t" . memory_get_peak_usage () . "\n";
//var_dump ($struct);
?>
<?php
/**
* Load 10,000 classes using the new autoloader.
*/
$start = microtime (true);
require_once ('lib/Autoloader.php');
for ($i = 0; $i < 10000; $i++) {
$c = 'Test' . $i;
$t = new $c;
}
echo microtime (true) - $start . "\t" . memory_get_peak_usage () . "\n";
?>
<?php
/**
* Load 10,000 classes using the old autoloader.
*/
$start = microtime (true);
require_once ('lib/Autoloader.old.php');
for ($i = 0; $i < 10000; $i++) {
$c = 'Test' . $i;
$t = new $c;
}
echo microtime (true) - $start . "\t" . memory_get_peak_usage () . "\n";
?>
<?php
/**
* Read and parse 10,000 files using require().
*/
$start = microtime (true);
for ($i = 0; $i < 10000; $i++) {
$struct = require ('files/php/test' . $i . '.php');
}
echo microtime (true) - $start . "\t" . memory_get_peak_usage () . "\n";
//var_dump ($struct);
?>
<?php
/**
* Read and parse 10,000 files using unserialize().
*/
$start = microtime (true);
for ($i = 0; $i < 10000; $i++) {
$struct = unserialize (file_get_contents ('files/serialize/test' . $i . '.php'));
}
echo microtime (true) - $start . "\t" . memory_get_peak_usage () . "\n";
//var_dump ($struct);
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment