Skip to content

Commit

Permalink
removed PEAR2 prefix requirement, added test cases and generated clas…
Browse files Browse the repository at this point in the history
…smap capability.
  • Loading branch information
Clay Loveless committed Jun 25, 2011
1 parent 275c162 commit 38078df
Show file tree
Hide file tree
Showing 11 changed files with 2,705 additions and 8 deletions.
3 changes: 2 additions & 1 deletion CREDITS
@@ -1,3 +1,4 @@
;; maintainers of Autoload ;; maintainers of Autoload
Gregory Beaver [cellog] <cellog@php.net> (lead) Gregory Beaver [cellog] <cellog@php.net> (lead)
Brett Bieber [saltybeagle] <saltybeagle@php.net> (lead) Brett Bieber [saltybeagle] <saltybeagle@php.net> (lead)
Clay Loveless [clay] <clay@php.net> (contributor)
7 changes: 7 additions & 0 deletions RELEASE-0.2.4
@@ -0,0 +1,7 @@
- Add optional dynamically-generated class map so full file paths can be
used when loading class files.

- Added a few test cases, along with non-Pyrus .phpt runner to keep us from
trying to load PEAR2\Autoload twice!

- Removed PEAR2\ prefix requirement for loaded classes. Now psr-0 compliant.
9 changes: 9 additions & 0 deletions runphpt
@@ -0,0 +1,9 @@
#!/bin/bash

#
# We need to run these tests outside of pyrus runtime to keep existing
# PEAR2\Autoload from preventing this file from loading
#

export NO_INTERACTION=1
/usr/bin/env php tests/run-tests.php.inc
139 changes: 132 additions & 7 deletions src/PEAR2/Autoload.php
Expand Up @@ -16,6 +16,34 @@ class Autoload
* @var array * @var array
*/ */
protected static $paths = array(); protected static $paths = array();

/**
* Array of classname-to-file mapping
*
* @var array
*/
protected static $map = array();

/**
* Array of class maps loaded
*
* @var array
*/
protected static $maps = array();

/**
* Last classmap specified
*
* @var array
*/
protected static $mapfile = null;

/**
* Array of classes loaded automatically not in the map
*
* @var array
*/
protected static $unmapped = array();


/** /**
* Initialize the PEAR2 autoloader * Initialize the PEAR2 autoloader
Expand All @@ -24,10 +52,11 @@ class Autoload
* *
* @return void * @return void
*/ */
static function initialize($path) static function initialize($path, $mapfile = null)
{ {
self::register(); self::register();
self::addPath($path); self::addPath($path);
self::addMap($mapfile);
} }


/** /**
Expand Down Expand Up @@ -64,6 +93,52 @@ protected static function addPath($path)
} }
} }


/**
* Add a classname-to-file map
*
* @param string $mapfile The filename of the classmap
*
* @return void
*/
protected static function addMap($mapfile)
{
if (! in_array($mapfile, self::$maps)) {

// keep track of specific map file loaded in this
// instance so we can update it if necessary
self::$mapfile = $mapfile;

if (file_exists($mapfile)) {
$map = include $mapfile;
if (is_array($map)) {
// mapfile contains a valid map, so we'll keep it
self::$maps[] = $mapfile;
self::$map = array_merge(self::$map, $map);
}
}

}
}

/**
* Check if the class is already defined in a classmap
*
* @param string $class The class to look for
*
* @return bool
*/
protected static function isMapped($class)
{
if (isset(self::$map[$class])) {
return true;
}
if (isset(self::$mapfile) && ! isset(self::$map[$class])) {
self::$unmapped[] = $class;
return false;
}
return false;
}

/** /**
* Load a PEAR2 class * Load a PEAR2 class
* *
Expand All @@ -73,21 +148,36 @@ protected static function addPath($path)
*/ */
static function load($class) static function load($class)
{ {
if (strtolower(substr($class, 0, 6)) !== 'pear2\\') { // need to check if there's a current map file specified ALSO.
return false; // this could be the first time writing it.
$mapped = self::isMapped($class);
if ($mapped) {
require self::$map[$class];
if (!self::loadSuccessful($class)) {
// record this failure & keep going, we may still find it
self::$unmapped[] = $class;
} else {
return true;
}
} }

$file = str_replace(array('_', '\\'), DIRECTORY_SEPARATOR, $class) . '.php'; $file = str_replace(array('_', '\\'), DIRECTORY_SEPARATOR, $class) . '.php';
foreach (self::$paths as $path) { foreach (self::$paths as $path) {
if (file_exists($path . DIRECTORY_SEPARATOR . $file)) { if (file_exists($path . DIRECTORY_SEPARATOR . $file)) {
require $path . DIRECTORY_SEPARATOR . $file; require $path . DIRECTORY_SEPARATOR . $file;
if (!class_exists($class, false) && !interface_exists($class, false)) { if (!self::loadSuccessful($class)) {
die(new \Exception('Class ' . $class . ' was not present in ' . throw new \Exception('Class ' . $class . ' was not present in ' .
$path . DIRECTORY_SEPARATOR . $file . $path . DIRECTORY_SEPARATOR . $file .
'") [PEAR2_Autoload-@PACKAGE_VERSION@]')); '") [PEAR2_Autoload-@PACKAGE_VERSION@]');
}

if (in_array($class, self::$unmapped)) {
self::updateMap($class, $path . DIRECTORY_SEPARATOR . $file);
} }
return true; return true;
} }
} }

$e = new \Exception('Class ' . $class . ' could not be loaded from ' . $e = new \Exception('Class ' . $class . ' could not be loaded from ' .
$file . ', file does not exist (registered paths="' . $file . ', file does not exist (registered paths="' .
implode(PATH_SEPARATOR, self::$paths) . implode(PATH_SEPARATOR, self::$paths) .
Expand All @@ -101,9 +191,44 @@ static function load($class)
in_array($trace[1]['function'], array('class_exists', 'interface_exists'))) { in_array($trace[1]['function'], array('class_exists', 'interface_exists'))) {
return false; return false;
} }
die ((string) $e); throw $e;
} }


/**
* Check if the requested class was loaded from the specified path
*
* @return bool
*/
protected static function loadSuccessful($class)
{
if (!class_exists($class, false) && !interface_exists($class, false)) {
return false;
}
return true;
}

/**
* If possible, update the classmap file with newly-discovered
* mapping.
*
* @param string $class Class name discovered
*
* @param string $origin File where class was found
*
*/
protected static function updateMap($class, $origin)
{
if (is_writable(self::$mapfile) || is_writable(dirname(self::$mapfile))) {
self::$map[$class] = $origin;
file_put_contents(self::$mapfile,
'<'."?php\n"
. "// PEAR2\Autoload auto-generated classmap\n"
. "return " . var_export(self::$map, true) . ';',
LOCK_EX
);
}
}

/** /**
* return the array of paths PEAR2 autoload has registered * return the array of paths PEAR2 autoload has registered
* *
Expand Down
9 changes: 9 additions & 0 deletions tests/_files/testDir1/Foo.php
@@ -0,0 +1,9 @@
<?php
namespace testDir1;
class Foo
{
public static function sayHello()
{
return "class testDir1\Foo says hi\n";
}
}
10 changes: 10 additions & 0 deletions tests/initialize_basic001.phpt
@@ -0,0 +1,10 @@
--TEST--
Test PEAR2\Autoload initalization
--FILE--
<?php
require __DIR__ . '/../src/PEAR2/Autoload.php';
$paths = PEAR2\Autoload::getPaths();
echo sizeof($paths);
?>
--EXPECT--
1
11 changes: 11 additions & 0 deletions tests/initialize_basic002.phpt
@@ -0,0 +1,11 @@
--TEST--
Test PEAR2\Autoload initalization w/2nd path
--FILE--
<?php
require __DIR__ . '/../src/PEAR2/Autoload.php';
PEAR2\Autoload::initialize(__DIR__.'/_files');
$paths = PEAR2\Autoload::getPaths();
echo sizeof($paths);
?>
--EXPECT--
2
11 changes: 11 additions & 0 deletions tests/initialize_basic003.phpt
@@ -0,0 +1,11 @@
--TEST--
Test PEAR2\Autoload initalization w/2nd path AND loading class.
--FILE--
<?php
require __DIR__ . '/../src/PEAR2/Autoload.php';
PEAR2\Autoload::initialize(__DIR__.'/_files');
echo testDir1\Foo::sayHello();

?>
--EXPECT--
class testDir1\Foo says hi
25 changes: 25 additions & 0 deletions tests/initialize_basic004.phpt
@@ -0,0 +1,25 @@
--TEST--
Test PEAR2\Autoload initalization w/2nd path, loading class & writing map.
--FILE--
<?php
require __DIR__ . '/../src/PEAR2/Autoload.php';
PEAR2\Autoload::initialize(__DIR__.'/_files', __DIR__.'/_files/_files_map.php.inc');
echo testDir1\Foo::sayHello();

$map = include __DIR__.'/_files/_files_map.php.inc';
if (isset($map['testDir1\\Foo'])) {
echo "class mapped\n";
} else {
echo "class not mapped\n";
}

?>
--EXPECT--
class testDir1\Foo says hi
class mapped

--CLEAN--
<?php
// comment this out if you want to review the generated file map!
unlink(__DIR__.'/_files/_files_map.php.inc');
?>
10 changes: 10 additions & 0 deletions tests/initialize_basic005.phpt
@@ -0,0 +1,10 @@
--TEST--
Test PEAR2\Autoload initalization w/2nd path AND FAIL loading non-existent class.
--FILE--
<?php
require __DIR__ . '/../src/PEAR2/Autoload.php';
PEAR2\Autoload::initialize(__DIR__.'/_files');
echo testDir1\UnknownClass::sayHello();
?>
--EXPECTREGEX--
^Fatal error.*

0 comments on commit 38078df

Please sign in to comment.