Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Prepend, multiple paths, and underscore combined with namespaces. #5

Merged
merged 4 commits into from

2 participants

@boenrobot

I've added some prepend options to allow users to specify custom paths with precendence over the path PEAR2_Autoload is in. This is particularly useful in test suites, where one might want to ensure the code under test is loaded while another "stable" version is still on the system.

Also, multiple paths can be specified at once, separated by PATH_SEPARATOR.

Another change that's part of the same commit, and I realize I may be opening a can of worms with it... if the class is in a namespace, an underscore is not replaced. This allows classes and filenames to have an underscore as part of their actual name, while namespaces will be used for logical separation. A class that's not in a namespace will of course still have its name separated by an underscore.

So, before:

<?php
new MyNamespace\fancy_structure; //Resolves to "MyNamespace/fancy/structure.php"
new MyNamespace_fancy_structure; //Resolves to "MyNamespace/fancy/structure.php"

and now

<?php
new MyNamespace\fancy_structure; //Resolves to "MyNamespace/fancy_structure.php"
new MyNamespace_fancy_structure; //Resolves to "MyNamespace/fancy/structure.php"

Should I maybe add it as an option (with the prepend options being specified as a bitmask, this should be easy)? If so, what should be the default? The current or new one?

@boenrobot

Anything on this?

BTW, now that PHP 5.4 is out, I think supporting traits would be nice.

@saltybeagle
Owner

Changing the way that underscores are treated within the CLASS NAME would make the autoloader incompatible (less compatible than we are already) with PSR-0:

  • Each "_" character in the CLASS NAME is converted to a DIRECTORY_SEPARATOR. The "_" character has no special meaning in the namespace.

The trait additions are good, as well as the prepend options — no objections to that.

@boenrobot boenrobot Made the load PSR-0 compliant;
Changed the require statements to includes to remove the remaining PHPCS issues;
Changed the file_exists() checks into is_file() checks, since folders can't be included.
8761ac4
@boenrobot

Good point. At the time, I didn't saw PSR-0 addressing underscores in namespaced classes in any way. I've now changed the load to be PSR-0 compliant. It's in fact an altered version of the doc :-P .

I've also made some other minor fixes for PHPCS' sake.

@saltybeagle
Owner

Excellent. I think we need to improve the test-coverage, but this is looking really good.

@saltybeagle saltybeagle merged commit dbe02ce into pear2:master
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 8, 2012
  1. @boenrobot
Commits on Mar 14, 2012
  1. @boenrobot
  2. @boenrobot
Commits on Jul 10, 2012
  1. @boenrobot

    Made the load PSR-0 compliant;

    boenrobot authored
    Changed the require statements to includes to remove the remaining PHPCS issues;
    Changed the file_exists() checks into is_file() checks, since folders can't be included.
This page is out of date. Refresh to see the latest.
Showing with 123 additions and 48 deletions.
  1. +123 −48 src/PEAR2/Autoload.php
View
171 src/PEAR2/Autoload.php
@@ -8,14 +8,13 @@
*
* PHP version 5
*
- * @category PEAR2
- * @package PEAR2_Autoload
- * @author Gregory Beaver <cellog@php.net>
- * @author Brett Bieber <saltybeagle@php.net>
- * @copyright 2012 PEAR2
- * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
- * @version GIT: $Id$
- * @link http://pear2.php.net/PEAR2_Autoload
+ * @category PEAR2
+ * @package PEAR2_Autoload
+ * @author Gregory Beaver <cellog@php.net>
+ * @author Brett Bieber <saltybeagle@php.net>
+ * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version GIT: $Id$
+ * @link http://pear2.php.net/PEAR2_Autoload
*/
namespace PEAR2;
if (!class_exists('\PEAR2\Autoload', false)) {
@@ -27,19 +26,48 @@
*
* PHP version 5
*
- * @category PEAR2
- * @package PEAR2_Autoload
- * @author Gregory Beaver <cellog@php.net>
- * @author Brett Bieber <saltybeagle@php.net>
- * @copyright 2012 PEAR2
- * @license http://www.opensource.org/licenses/bsd-license.php
+ * @category PEAR2
+ * @package PEAR2_Autoload
+ * @author Gregory Beaver <cellog@php.net>
+ * @author Brett Bieber <saltybeagle@php.net>
+ * @license http://www.opensource.org/licenses/bsd-license.php
* New BSDLicense
- * @version GIT: $Id$
- * @link http://pear2.php.net/PEAR2_Autoload
+ * @link http://pear2.php.net/PEAR2_Autoload
*/
class Autoload
{
/**
+ * Used at {@link initialize()} to specify that the load function, path
+ * and map should be appended to the respective lists.
+ */
+ const APPEND = 0;
+
+ /**
+ * Used at {@link initialize()} to specify that the load function should
+ * be prepended on the autoload stack, instead of being appended.
+ */
+ const PREPEND_LOAD = 1;
+
+ /**
+ * Used at {@link initialize()} to specify that the path should be
+ * prepended on the list of paths, instead of being appended.
+ */
+ const PREPEND_PATH = 2;
+
+ /**
+ * Used at {@link initialize()} to specify that the map should be
+ * prepended on the list of maps, instead of being appended.
+ */
+ const PREPEND_MAP = 4;
+
+ /**
+ * Used at {@link initialize()} to specify that the load function, path
+ * and map should be prepended on their respective lists, instead of
+ * being appended.
+ */
+ const PREPEND = 7;
+
+ /**
* Whether the autoload class has been spl_autoload_register-ed
*
* @var bool
@@ -80,77 +108,112 @@ class Autoload
* @var array
*/
protected static $unmapped = array();
+
+ /**
+ * Array of functions to be checked in exception traces.
+ *
+ * @var array
+ */
+ protected static $checkFunctions = array(
+ 'class_exists', 'interface_exists'
+ );
/**
* Initialize the PEAR2 autoloader
*
- * @param string $path Directory path to register
+ * @param string $path Directory path(s) to register.
* @param string $mapfile Path to a mapping file to register.
+ * @param int $flags A bitmaks with options for the autoloader.
+ * See the PREPEND(_*) constants for details.
*
* @return void
*/
- static function initialize($path, $mapfile = null)
- {
- self::register();
- self::addPath($path);
- self::addMap($mapfile);
+ static function initialize(
+ $path, $mapfile = null, $flags = self::APPEND
+ ) {
+ self::register(0 !== $flags & self::PREPEND_LOAD);
+ self::addPath($path, 0 !== ($flags & self::PREPEND_PATH));
+ self::addMap($mapfile, 0 !== ($flags & self::PREPEND_MAP));
}
/**
* Register the PEAR2 autoload class with spl_autoload_register
*
+ * @param bool $prepend Whether to prepend the load function to the
+ * autoload stack instead of appending it.
+ *
* @return void
*/
- protected static function register()
+ protected static function register($prepend = false)
{
if (!self::$registered) {
// set up __autoload
$autoload = spl_autoload_functions();
- spl_autoload_register('PEAR2\Autoload::load');
+ spl_autoload_register('PEAR2\Autoload::load', true, $prepend);
if (function_exists('__autoload') && ($autoload === false)) {
- // __autoload() was being used, but now would be ignored, add
- // it to the autoload stack
+ // __autoload() was being used, but now would be ignored,
+ // add it to the autoload stack
spl_autoload_register('__autoload');
}
+ if (function_exists('trait_exists')) {
+ self::$checkFunctions[] = 'trait_exists';
+ }
+ self::$registered = true;
}
- self::$registered = true;
}
/**
* Add a path
*
- * @param string $path The directory to add to the set of PEAR2 paths
+ * @param string $paths The folder(s) to add to the set of paths.
+ * @param bool $prepend Whether to prepend the path to the list of
+ * paths instead of appending it.
*
* @return void
*/
- protected static function addPath($path)
+ protected static function addPath($paths, $prepend = false)
{
- if (!in_array($path, self::$paths)) {
- self::$paths[] = $path;
+ foreach (explode(PATH_SEPARATOR, $paths) as $path) {
+ if (!in_array($path, self::$paths)) {
+ if ($prepend) {
+ self::$paths = array_merge(array($path), self::$paths);
+ } else {
+ self::$paths[] = $path;
+ }
+ }
}
}
/**
* Add a classname-to-file map
*
- * @param string $mapfile The filename of the classmap
+ * @param string $mapfile The filename of the classmap.
+ * @param bool $prepend Whether to prepend the map to the list of maps
+ * instead of appending it.
*
* @return void
*/
- protected static function addMap($mapfile)
+ protected static function addMap($mapfile, $prepend = false)
{
- if (! in_array($mapfile, self::$maps)) {
+ 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)) {
+ if (is_file($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);
+ if ($prepend) {
+ self::$maps = array_merge(
+ array($mapfile), self::$maps
+ );
+ self::$map = array_merge($map, self::$map);
+ } else {
+ self::$maps[] = $mapfile;
+ self::$map = array_merge(self::$map, $map);
+ }
}
}
@@ -188,8 +251,8 @@ static function load($class)
// need to check if there's a current map file specified ALSO.
// this could be the first time writing it.
$mapped = self::isMapped($class);
- if ($mapped) {
- require self::$map[$class];
+ if ($mapped && is_file(self::$map[$class])) {
+ include self::$map[$class];
if (!self::loadSuccessful($class)) {
// record this failure & keep going, we may still find it
self::$unmapped[] = $class;
@@ -198,11 +261,19 @@ static function load($class)
}
}
- $file = str_replace(array('_', '\\'), DIRECTORY_SEPARATOR, $class) .
- '.php';
+ $file = '';
+ $className = $class;
+ if (false !== $lastNsPos = strrpos($class, '\\')) {
+ $namespace = substr($class, 0, $lastNsPos);
+ $className = substr($class, $lastNsPos + 1);
+ $file = str_replace(
+ '\\', DIRECTORY_SEPARATOR, $namespace
+ ) . DIRECTORY_SEPARATOR;
+ }
+ $file .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
foreach (self::$paths as $path) {
- if (file_exists($path . DIRECTORY_SEPARATOR . $file)) {
- require $path . DIRECTORY_SEPARATOR . $file;
+ if (is_file($path . DIRECTORY_SEPARATOR . $file)) {
+ include $path . DIRECTORY_SEPARATOR . $file;
if (!self::loadSuccessful($class)) {
if (count(spl_autoload_functions()) > 1) {
return false;
@@ -215,7 +286,9 @@ static function load($class)
}
if (in_array($class, self::$unmapped)) {
- self::updateMap($class, $path . DIRECTORY_SEPARATOR . $file);
+ self::updateMap(
+ $class, $path . DIRECTORY_SEPARATOR . $file
+ );
}
return true;
}
@@ -230,14 +303,13 @@ static function load($class)
'") [PEAR2_Autoload-@PACKAGE_VERSION@]'
);
$trace = $e->getTrace();
- $checkFunctions = array('class_exists', 'interface_exists');
if (isset($trace[2]) && isset($trace[2]['function'])
- && in_array($trace[2]['function'], $checkFunctions)
+ && in_array($trace[2]['function'], self::$checkFunctions)
) {
return false;
}
if (isset($trace[1]) && isset($trace[1]['function'])
- && in_array($trace[1]['function'], $checkFunctions)
+ && in_array($trace[1]['function'], self::$checkFunctions)
) {
return false;
}
@@ -253,7 +325,10 @@ static function load($class)
*/
protected static function loadSuccessful($class)
{
- return class_exists($class, false) || interface_exists($class, false);
+ return class_exists($class, false)
+ || interface_exists($class, false)
+ || (in_array('trait_exists', self::$checkFunctions, true)
+ && trait_exists($class, false));
}
/**
Something went wrong with that request. Please try again.