diff --git a/includes/autoload/DrupalAPCClassLoader.php b/includes/autoload/DrupalAPCClassLoader.php new file mode 100644 index 0000000..fe8f075 100644 --- /dev/null +++ b/includes/autoload/DrupalAPCClassLoader.php @@ -0,0 +1,53 @@ +prefix = $prefix; + } + + /** + * Overrides DrupalClassLoader::loadClass(). + */ + public function loadClass($classname) { + + if (NULL !== $this->prefix) { + $key = $key . $classname; + } else { + $key = $classname; + } + + if ($filename = apc_fetch($key)) { + if ((bool)include $filename) { + return TRUE; + } + else { + // Key exists but file does not, update the cache. + apc_delete($key); + } + } + + if ($filename = $this->findFile($classname) && (bool)include $filename) { + apc_store($key, $filename); + return TRUE; + } + + return FALSE; + } +} diff --git a/includes/autoload/DrupalClassLoader.inc b/includes/autoload/DrupalClassLoader.inc new file mode 100644 index 0000000..70b85fa 100644 --- /dev/null +++ b/includes/autoload/DrupalClassLoader.inc @@ -0,0 +1,205 @@ + + * - Roman S. Borschel + * - Matthew Weier O'Phinney + * - Kris Wallsmith + * - Fabien Potencier + * + * @see https://gist.github.com/3038217 + * For the original code. + * + * This code is highly similar to the Symfony 2.x ClassLoader component. The + * reason we don't use it is to keep PHP 5.2 compatibility. + */ +class DrupalClassLoader { + + /** + * File extension. + * + * @var string + */ + protected $fileExtension = '.php'; + + /** + * Namespace separator. + * + * @var string + */ + protected $namespaceSeparator = '\\'; + + /** + * List of registered namespaces. + * + * @var array + * Keys are namespaces and values are SplClassLoader instances. + */ + protected $namespaces = array(); + + /** + * Should we use include path or not. + * + * @var boolean + */ + protected $useIncludePath = TRUE; + + /** + * Register single namespace for autoloading. + * + * @param string $namespace + * Namespace, e.g. MyModule or My\Module + * @param string $include_path + * (optional) Root path where to find files, if none given will lookup into + * all include_path root. + */ + public function registerNamespace($namespace, $include_path = NULL) { + $this->namespaces[$namespace] = $include_path; + } + + /** + * Register a set of namespaces at once. + * + * @param array $namespaces + * Key/value pairs, keys are namespaces and values are include path for + * each namespace. + * + * @see DrupalClassLoader::registerNamespace() + * For parameters descriptions. + */ + public function registerNamespaces(array $namespaces) { + foreach ($namespaces as $namespace => $include_path) { + $this->registerNamespace($namespace, $include_path); + } + } + + /** + * Unregister a single namespace. + * + * The given namespace must be registered using + * DrupalClassLoader::unregisterdNamespace(). + * + * @param string $namespace + * Namespace to unregister. + */ + public function unregisterNamespace($namespace) { + if (isset($this->namespaces[$namespace])) { + $instance = $this->namespaces[$namespace]; + unset($this->namespaces[$namespace]); + } + } + + /** + * Unregister a set of namespaces at once. + * + * @param array $namespaces + * Array of which values are namespaces names. + */ + public function unregisterNamespaces(array $namespaces) { + foreach ($namespaces as $namespace) { + $this->unregister($namespace); + } + } + + /** + * Tells if the current instance handles namespaces. + * + * @return boolean + * TRUE if at least one namespace has been registered otherwise FALSE. + */ + public function hasNamespaces() { + return !empty($this->namespaces); + } + + /** + * Loads the given class or interface. + * + * @param string $className + * The name of the class to load. + */ + public function loadClass($classname) { + if ($filepath = $this->findFile($classname)) { + return (bool)include $filepath; + } + return false; + } + + /** + * Resolve a file path. + * + * This is the heart of PSR-0 implementation. Credits goes to original PSR-0 + * team, and code is located at https://gist.github.com/3038217 + * + * @param string $className + * The class to load. + * @param string $namespace + * The namespace to find it in. + * @param string $include_path + * The namespace include path. + * + * @return string + * File path or NULL if none found. + */ + protected function resolveFileName($classname, $namespace, $include_path = NULL) { + if (NULL === $namespace || $namespace === substr($classname, 0, strlen($namespace))) { + $filename = ''; + $namespace = ''; + + if (FALSE !== ($lastNsPos = strripos($classname, $this->namespaceSeparator))) { + $namespace = substr($classname, 0, $lastNsPos); + $classname = substr($classname, $lastNsPos + 1); + $filename = str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR; + } + + $filename .= str_replace('_', DIRECTORY_SEPARATOR, $classname) . $this->fileExtension; + + return ($include_path !== NULL ? $include_path . DIRECTORY_SEPARATOR : '') . $filename; + } + } + + /** + * Find class file. + * + * @param string $classname + * The name of the class to find. + * + * @return string + * File path if found otherwise FALSE. + */ + public function findFile($classname) { + foreach ($this->namespaces as $namespace => $include_path) { + if (($filename = $this->resolveFileName($classname, $namespace, $include_path))) { + + if ($this->useIncludePath) { + $filename = stream_resolve_include_path($filename); + } + + if (file_exists($filename)) { + return $filename; + } + } + } + } + + /** + * Installs this class loader on the SPL autoload stack. + */ + public function register() { + spl_autoload_register(array($this, 'loadClass')); + } + + /** + * Uninstalls this class loader from the SPL autoloader stack. + */ + public function unregister() { + spl_autoload_unregister(array($this, 'loadClass')); + } +} diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 1fd497d..64fdfff 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -2293,6 +2293,13 @@ function _drupal_bootstrap_configuration() { timer_start('page'); // Initialize the configuration, including variables from settings.php. drupal_settings_initialize(); + + // Initialize the class loaders that don't depend on database. + $loader = drupal_autoload_get(); + if ($namespaces = variable_get('classload_namespaces')) { + $loader->registerNamespaces($namespaces); + } + $loader->register(); } /** @@ -2995,6 +3002,26 @@ function drupal_get_complete_schema($rebuild = FALSE) { */ /** + * Get core autoloader. + * + * @return DrupalClassLoader + * The autoloader instance. + */ +function drupal_autoload_get() { + $loader = &drupal_static(__FUNCTION__); + if (!isset($loader)) { + require_once DRUPAL_ROOT . '/includes/autoload/DrupalClassLoader.inc'; + if ((!isset($conf['apc_classloader_enable']) || $conf['apc_classloader_enable']) && extension_loaded('apc')) { + require_once DRUPAL_ROOT . '/includes/autoload/DrupalAPCClassLoader.inc'; + $loader = new DrupalAPCClassLoader(); + } else { + $loader = new DrupalClassLoader(); + } + } + return $loader; +} + +/** * Confirms that an interface is available. * * This function is rarely called directly. Instead, it is registered as an diff --git a/modules/simpletest/simpletest.info b/modules/simpletest/simpletest.info index 5583c2f..25bdbca 100644 --- a/modules/simpletest/simpletest.info +++ b/modules/simpletest/simpletest.info @@ -13,6 +13,7 @@ files[] = tests/ajax.test files[] = tests/batch.test files[] = tests/bootstrap.test files[] = tests/cache.test +files[] = tests/classloader.test files[] = tests/common.test files[] = tests/database_test.test files[] = tests/entity_crud_hook_test.test diff --git a/modules/simpletest/tests/classloader.test b/modules/simpletest/tests/classloader.test new file mode 100644 index 0000000..1d2a9db 100644 --- /dev/null +++ b/modules/simpletest/tests/classloader.test @@ -0,0 +1,93 @@ + 'Class loader', + 'description' => 'Drupal core PSR-0 compatible autoloader unit tests.', + 'group' => 'System', + ); + } + + function setUp() { + $include_path = dirname(__FILE__) . '/classloader'; + $namespaces = array( + 'Baz' => $include_path, + 'Mixed_Path' => $include_path, + ); + + if (extension_loaded('apc')) { + $this->apcLoader = new DrupalAPCClassLoader(); + $this->apcLoader->registerNamespaces($namespaces); + } + + $this->loader = new DrupalClassLoader(); + $this->loader->registerNamespaces($namespaces); + + parent::setUp(); + } + + function tearDown() { + unset($this->loader); + + parent::tearDown(); + } + + /** + * Test autoloader register feature. + */ + function testRegister() { + // Ensure no legacy autoloader. + spl_autoload_unregister('drupal_autoload_class'); + spl_autoload_unregister('drupal_autoload_interface'); + + $this->loader->register(); + $this->assertTrue(class_exists('Baz_ClassExists')); + $this->loader->unregister(); + + // Restore legacy autoloader. + spl_autoload_register('drupal_autoload_class'); + spl_autoload_register('drupal_autoload_interface'); + } + + /** + * PHP 5.2 / PEAR coding style tests. + */ + function testLoadFile() { + $this->assertNotNull($this->loader->findFile('Baz_Bar')); + } + + /** + * PHP 5.3 / Namespaces coding style tests. + */ + function testNamespaces() { + // @todo Ensure a check to avoid testing this if PHP 5.2. + $this->assertNotNull($this->loader->findFile('Baz\SomeClass')); + $this->assertNotNull($this->loader->findFile('Mixed_Path\Other')); + $this->assertNotNull($this->loader->findFile('Baz\Some_OtherClass')); + } + + /** + * PHP 5.2 / PEAR coding style tests with APC loader. + */ + function testAPCLoadFile() { + if (!extension_loaded('apc')) { + return; + } + $this->assertNotNull($this->apcLoader->findFile('Baz_Bar')); + } +} + diff --git a/modules/simpletest/tests/classloader/Baz/Bar.php b/modules/simpletest/tests/classloader/Baz/Bar.php new file mode 100644 index 0000000..c72f334 100644 --- /dev/null +++ b/modules/simpletest/tests/classloader/Baz/Bar.php @@ -0,0 +1,7 @@ +