diff --git a/composer.json b/composer.json index 7d460ed..9b41c2d 100644 --- a/composer.json +++ b/composer.json @@ -42,5 +42,8 @@ "config": { "vendor-dir": "core/vendor", "preferred-install": "dist" + }, + "scripts": { + "post-autoload-dump": "Drupal\\Core\\Autoload\\GeneratorScript::postAutoloadDump" } } diff --git a/core/autoload.drupal.php b/core/autoload.drupal.php new file mode 100644 index 0000000..e665445 --- /dev/null +++ b/core/autoload.drupal.php @@ -0,0 +1,23 @@ +add('Drupal\\' . $name, DRUPAL_ROOT . '/' . $path . '/lib'); + $loader->addPsr4('Drupal\\' . $name . '\\', array( + DRUPAL_ROOT . '/' . $path . '/lib/Drupal/' . $name, + DRUPAL_ROOT . '/' . $path . '/lib', + )); } /** diff --git a/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php index b9236be..559163d 100644 --- a/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php +++ b/core/lib/Drupal/Component/Annotation/Plugin/Discovery/AnnotatedClassDiscovery.php @@ -98,7 +98,6 @@ public function getDefinitions() { // Search for classes within all PSR-0 namespace locations. foreach ($this->getPluginNamespaces() as $namespace => $dirs) { foreach ($dirs as $dir) { - $dir .= DIRECTORY_SEPARATOR . str_replace('\\', DIRECTORY_SEPARATOR, $namespace); if (file_exists($dir)) { $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS) diff --git a/core/lib/Drupal/Core/Autoload/ClassLoader.php b/core/lib/Drupal/Core/Autoload/ClassLoader.php new file mode 100644 index 0000000..8b5f03e --- /dev/null +++ b/core/lib/Drupal/Core/Autoload/ClassLoader.php @@ -0,0 +1,526 @@ + array('Drupal\Core\\' => 12), + * 'Dm' => array( + * 'Drupal\Component\\' => 17, + * 'Drupal\number\\' => 14, + * ), + * 'S' => array('Symfony\\' => 8), + * ) + */ + private $prefixLengthsPsr4 = array(); + + /** + * @var array + * Namespaces mapped to PSR-4 directories. + * + * Namespaces are represented with trailing namespace separator, but without + * a preceding namespace separator. + * + * Directories for each namespace are represented as a numeric array. + * The loader is designed to work whether or not the directories have a + * trailing directory separator. + * + * E.g. a possible value of this variable could be: + * + * array( + * 'Drupal\Core\\' => array(DRUPAL_ROOT . '/core/lib/Drupal/Core'), + * 'Drupal\Component\\' => array(DRUPAL_ROOT . '/core/lib/Drupal/Component'), + * ) + */ + private $prefixDirsPsr4 = array(); + + /** + * @var array + * PSR-4 directories to use if no matching namespace is found. + */ + private $fallbackDirsPsr4 = array(); + + /** + * @var array + * Prefixes mapped to PSR-0 directories. + * + * The array has a nested structure, where prefixes are grouped by their + * first character. + * + * E.g. a possible value of this variable could be: + * + * array( + * 'D' => array( + * 'Drupal\Core\\' => array(DRUPAL_ROOT . '/core/lib'), + * 'Drupal\Component\\' => array(DRUPAL_ROOT . '/core/lib'), + * 'Drupal\system\\' => array(DRUPAL_ROOT . '/core/modules/system/lib'), + * ), + * 'S' => array( + * 'Symfony\Component\Routing\\' => array(..), + * 'Symfony\Component\Process\\' => array(..), + * ), + * ), + */ + private $prefixesPsr0 = array(); + + /** + * @var array + * PSR-0 directories to use if no matching prefix is found. + */ + private $fallbackDirsPsr0 = array(); + + /** + * @var bool + * TRUE, if the autoloader uses the include path to check for classes. + */ + private $useIncludePath = FALSE; + + /** + * @var array + * Specific classes mapped to specific PHP files. + */ + private $classMap = array(); + + /** + * @const + * Position in a fully-qualified class name where a character should be + * picked to build the index for $prefixLengthsPsr4. + * The position [9] has been chosen because it provides a good distribution + * of the typical namespaces in a Drupal project. + */ + const PREDICTOR_INDEX = 9; + + /** + * Gets the registered prefixes for PSR-0 directories. + * + * @return array + * Registered prefixes mapped to PSR-0 directories. + */ + public function getPrefixes() { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + /** + * Gets the registered namespaces for PSR-4 directories. + * + * @return array + * Namespaces mapped to PSR-4 directories. + */ + public function getPrefixesPsr4() { + return $this->prefixDirsPsr4; + } + + /** + * Gets the PSR-0 fallback directories. + * + * @return array + * PSR-0 directories to use if no matching prefix is found. + */ + public function getFallbackDirs() { + return $this->fallbackDirsPsr0; + } + + /** + * Gets the PSR-4 fallback directories. + * + * @return array + * PSR-0 directories to use if no matching prefix is found. + */ + public function getFallbackDirsPsr4() { + return $this->fallbackDirsPsr4; + } + + /** + * Gets the class map. + * + * @return array + * Specific classes mapped to specific PHP files. + */ + public function getClassMap() { + return $this->classMap; + } + + /** + * Adds a class map. + * + * @param array $classMap + * Specific classes mapped to specific PHP files. + */ + public function addClassMap(array $classMap) { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } + else { + $this->classMap = $classMap; + } + } + + /** + * Adds a set of PSR-0 directories for a given prefix. + * + * The directories will be appended or prepended to the ones previously set + * for this prefix, depending on the $prepend parameter. + * + * @param string $prefix + * The prefix. + * @param array|string $paths + * The PSR-0 root directories. + * @param bool $prepend + * (optional) Whether to prepend the directories. + */ + public function add($prefix, $paths, $prepend = FALSE) { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } + else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } + else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Adds a set of PSR-4 directories for a given namespace. + * + * The directories will be appended or prepended to the ones previously set + * for this prefix, depending on the $prepend parameter. + * + * @param string $prefix + * The prefix/namespace, with trailing '\\'. + * @param array|string $paths + * The PSR-0 base directories. + * @param bool $prepend + * (optional) Whether to prepend the directories. + * + * @throws \Exception + * Throws an exception if the prefix does not end with a trailing namespace + * separator. + */ + public function addPsr4($prefix, $paths, $prepend = FALSE) { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } + else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } + elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \Exception("A non-empty PSR-4 prefix must end with a namespace separator."); + } + if ($length > self::PREDICTOR_INDEX) { + // The namespace is long enough to have a character at position [9]. + $predictor = $prefix[0] . $prefix[self::PREDICTOR_INDEX]; + $this->prefixLengthsPsr4[$predictor][$prefix] = $length; + } + else { + // The namespace is too short to have a character at position [9]. + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + } + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } + else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Sets/Overwrites the PSR-0 directories for a given prefix. + * + * This will replace any directories that were previously set for this prefix. + * + * @param string $prefix + * The prefix. + * @param array|string $paths + * The PSR-0 base directories. + */ + public function set($prefix, $paths) { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } + else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Sets/Overwrites the PSR-4 directories for a given prefix. + * + * This will replace any directories that were previously set for this prefix. + * + * @param string $prefix + * The prefix/namespace, with trailing '\\' + * @param array|string $paths + * The PSR-4 base directories + * + * @throws \Exception + * Throws an exception if the prefix does not end with a trailing namespace + * separator. + */ + public function setPsr4($prefix, $paths) { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } + else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \Exception("A non-empty PSR-4 prefix must end with a namespace separator."); + } + if ($length > self::PREDICTOR_INDEX) { + $predictor = $prefix[0] . $prefix[self::PREDICTOR_INDEX]; + $this->prefixLengthsPsr4[$predictor][$prefix] = $length; + } + else { + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + } + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() { + return $this->useIncludePath; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend + * (optional) Whether to prepend the autoloader or not + */ + public function register($prepend = FALSE) { + spl_autoload_register(array($this, 'loadClass'), TRUE, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class + * The name of the class. + * + * @return bool|NULL + * TRUE if loaded, NULL otherwise. + */ + public function loadClass($class) { + if ($file = $this->findFile($class)) { + include $file; + + return TRUE; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class + * The name of the class. + * + * @return string|FALSE + * The path if found, FALSE otherwise. + */ + public function findFile($class) { + // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731. + if ('\\' == $class[0]) { + $class = substr($class, 1); + } + + // class map lookup. + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + + // PSR-4 lookup. + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; + + // Check if the class is in any of the namespaces registered for PSR-4, + // and that are long enough to have a character at the predictor index. + $first = $class[0]; + if (isset($class[self::PREDICTOR_INDEX])) { + $predictor = $first . $class[self::PREDICTOR_INDEX]; + if (isset($this->prefixLengthsPsr4[$predictor])) { + foreach ($this->prefixLengthsPsr4[$predictor] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + } + + // Check if the class is in any of the namespaces registered for PSR-4, + // that are too short to have a character at the predictor index. + if (isset($this->prefixLengthsPsr4[$first])) { + foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach ($this->prefixDirsPsr4[$prefix] as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs. + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup. + if (FALSE !== $pos = strrpos($class, '\\')) { + // namespaced class name. + $logicalPathPsr0 + = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR) + ; + } + else { + // PEAR-like class name. + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; + } + + // Check if the class matches any of the prefixes registered for PSR-0. + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs. + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + // Remember that this class does not exist. + return $this->classMap[$class] = FALSE; + } + +} diff --git a/core/lib/Drupal/Core/Autoload/DrupalAutoloaderInit.php b/core/lib/Drupal/Core/Autoload/DrupalAutoloaderInit.php new file mode 100644 index 0000000..3f63a17 --- /dev/null +++ b/core/lib/Drupal/Core/Autoload/DrupalAutoloaderInit.php @@ -0,0 +1,116 @@ +setPsr4('Drupal\Core\\', $baseDir . '/core/lib/Drupal/Core'); + $loader->setPsr4('Drupal\Component\\', $baseDir . '/core/lib/Drupal/Component'); + $loader->setPsr4('Drupal\Driver\\', $baseDir . '/drivers/lib/Drupal/Driver'); + + // Register the remaining PSR-0 namespaces - mostly for vendor libraries. + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + // Register the class map in the class loader. + $classMap = require $composerDir . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + + // Register the class loader on the SPL autoload stack. + $loader->register(true); + + // Include specific php files, for packages that use the + // 'autoload' > 'files' directive in composer.json. + $includeFiles = require $composerDir . '/autoload_files.php'; + foreach ($includeFiles as $file) { + require $file; + } + + return $loader; + } + +} diff --git a/core/lib/Drupal/Core/Autoload/GeneratorScript.php b/core/lib/Drupal/Core/Autoload/GeneratorScript.php new file mode 100644 index 0000000..f0457ff --- /dev/null +++ b/core/lib/Drupal/Core/Autoload/GeneratorScript.php @@ -0,0 +1,36 @@ +moduleList = isset($module_list['enabled']) ? $module_list['enabled'] : array(); } $module_filenames = $this->getModuleFileNames(); - $this->registerNamespaces($this->getModuleNamespaces($module_filenames)); + $this->registerNamespacesPsr4($this->getModuleNamespacesPsr4($module_filenames)); // Load each module's serviceProvider class. foreach ($this->moduleList as $module => $weight) { @@ -250,7 +250,6 @@ public function discoverServiceProviders() { return $serviceProviders; } - /** * {@inheritdoc} */ @@ -411,8 +410,8 @@ protected function initializeContainer() { // All namespaces must be registered before we attempt to use any service // from the container. $container_modules = $this->container->getParameter('container.modules'); - $namespaces_before = $this->classLoader->getPrefixes(); - $this->registerNamespaces($this->getModuleNamespaces($container_modules)); + $namespaces_before = $this->classLoader->getPrefixesPsr4(); + $this->registerNamespacesPsr4($this->getModuleNamespacesPsr4($container_modules)); // If 'container.modules' is wrong, the container must be rebuilt. if (!isset($this->moduleList)) { @@ -425,9 +424,9 @@ protected function initializeContainer() { // registerNamespaces() performs a merge rather than replace, so to // effectively remove erroneous registrations, we must replace them with // empty arrays. - $namespaces_after = $this->classLoader->getPrefixes(); + $namespaces_after = $this->classLoader->getPrefixesPsr4(); $namespaces_before += array_fill_keys(array_diff(array_keys($namespaces_after), array_keys($namespaces_before)), array()); - $this->registerNamespaces($namespaces_before); + $this->registerNamespacesPsr4($namespaces_before); } } @@ -503,14 +502,15 @@ protected function buildContainer() { $container->setParameter('container.modules', $this->getModuleFileNames()); // Get a list of namespaces and put it onto the container. - $namespaces = $this->getModuleNamespaces($this->getModuleFileNames()); + $namespaces = $this->getModuleNamespacesPsr4($this->getModuleFileNames()); // Add all components in \Drupal\Core and \Drupal\Component that have a // Plugin directory. foreach (array('Core', 'Component') as $parent_directory) { $path = DRUPAL_ROOT . '/core/lib/Drupal/' . $parent_directory; + $parent_namespace = 'Drupal\\' . $parent_directory; foreach (new \DirectoryIterator($path) as $component) { if (!$component->isDot() && is_dir($component->getPathname() . '/Plugin')) { - $namespaces['Drupal\\' . $parent_directory . '\\' . $component->getFilename()] = DRUPAL_ROOT . '/core/lib'; + $namespaces[$parent_namespace . '\\' . $component->getFilename()] = $path . '/' . $component->getFilename(); } } } @@ -632,7 +632,11 @@ protected function storage() { } /** - * Returns the file name for each enabled module. + * Gets the file name for each enabled module. + * + * @return array + * Array where each key is a module name, and each value is a path to the + * respective *.module or *.profile file. */ protected function getModuleFileNames() { $filenames = array(); @@ -645,18 +649,67 @@ protected function getModuleFileNames() { } /** - * Gets the namespaces of each enabled module. + * Gets the PSR-4 base directories for module namespaces. + * + * @param array $module_file_names + * Array where each key is a module name, and each value is a path to the + * respective *.module or *.profile file. + * + * @return array + * Array where each key is a module namespace like 'Drupal\system', and each + * value is an array of PSR-4 base directories associated with the module + * namespace. + */ + protected function getModuleNamespacesPsr4($module_file_names) { + $namespaces = array(); + foreach ($module_file_names as $module => $filename) { + // @todo Remove lib/Drupal/$module, once the switch to PSR-4 is complete. + $namespaces["Drupal\\$module"][] = DRUPAL_ROOT . '/' . dirname($filename) . '/lib/Drupal/' . $module; + $namespaces["Drupal\\$module"][] = DRUPAL_ROOT . '/' . dirname($filename) . '/lib'; + } + return $namespaces; + } + + /** + * Gets the PSR-0 base directories for module namespaces. + * + * @param array $module_file_names + * Array where each key is a module name, and each value is a path to the + * respective *.module or *.profile file. + * + * @return array + * Array where each key is a module namespace like 'Drupal\system', and each + * value is a PSR-0 base directory associated with the module namespace. */ - protected function getModuleNamespaces($moduleFileNames) { + protected function getModuleNamespaces($module_file_names) { $namespaces = array(); - foreach ($moduleFileNames as $module => $filename) { + foreach ($module_file_names as $module => $filename) { $namespaces["Drupal\\$module"] = DRUPAL_ROOT . '/' . dirname($filename) . '/lib'; } return $namespaces; } /** - * Registers a list of namespaces. + * Registers a list of namespaces with PSR-4 directories for class loading. + * + * @param array $namespaces + * Array where each key is a namespace like 'Drupal\system', and each value + * is either a PSR-4 base directory, or an array of PSR-4 base directories + * associated with this namespace. + */ + protected function registerNamespacesPsr4(array $namespaces = array()) { + foreach ($namespaces as $prefix => $paths) { + $this->classLoader->addPsr4($prefix . '\\', $paths); + } + } + + /** + * Registers a list of namespaces with PSR-0 directories for class loading. + * + * @param array $namespaces + * Array where each key is a namespace like 'Drupal\system', and each value + * is either a PSR-0 base directory, or an array of PSR-0 base directories + * associated with this namespace. */ protected function registerNamespaces(array $namespaces = array()) { foreach ($namespaces as $prefix => $path) { diff --git a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php index ffafdf6..0192ec3 100644 --- a/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php +++ b/core/lib/Drupal/Core/Plugin/Discovery/AnnotatedClassDiscovery.php @@ -15,17 +15,23 @@ class AnnotatedClassDiscovery extends ComponentAnnotatedClassDiscovery { /** - * The subdirectory within a namespace to look for plugins. + * A suffix to append to each PSR-4 directory associated with a base + * namespace, to form the directories where plugins are found. * - * If the plugins are in the top level of the namespace and not within a - * subdirectory, set this to an empty string. + * @var string + */ + protected $directorySuffix = ''; + + /** + * A suffix to append to each base namespace, to obtain the namespaces where + * plugins are found. * * @var string */ - protected $subdir = ''; + protected $namespaceSuffix = ''; /** - * An object containing the namespaces to look for plugin implementations. + * A list of base namespaces with their PSR-4 directories. * * @var \Traversable */ @@ -47,7 +53,13 @@ class AnnotatedClassDiscovery extends ComponentAnnotatedClassDiscovery { */ function __construct($subdir, \Traversable $root_namespaces, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin') { if ($subdir) { - $this->subdir = str_replace('/', '\\', $subdir); + // Prepend a directory separator to $subdir, + // if it does not already have one. + if ('/' !== $subdir[0]) { + $subdir = '/' . $subdir; + } + $this->directorySuffix = $subdir; + $this->namespaceSuffix = str_replace('/', '\\', $subdir); } $this->rootNamespacesIterator = $root_namespaces; $plugin_namespaces = array(); @@ -106,11 +118,28 @@ protected function getProviderFromNamespace($namespace) { */ protected function getPluginNamespaces() { $plugin_namespaces = array(); - foreach ($this->rootNamespacesIterator as $namespace => $dir) { - if ($this->subdir) { - $namespace .= "\\{$this->subdir}"; + if ($this->namespaceSuffix) { + foreach ($this->rootNamespacesIterator as $namespace => $dirs) { + // Append the namespace suffix to the base namespace, to obtain the + // plugin namespace. E.g. 'Drupal\Views' may become + // 'Drupal\Views\Plugin\Block'. + $namespace .= $this->namespaceSuffix; + foreach ((array) $dirs as $dir) { + // Append the directory suffix to the PSR-4 base directory, to obtain + // the directory where plugins are found. + // E.g. DRUPAL_CORE . 'core/modules/views/lib' may become + // DRUPAL_CORE . 'core/modules/views/lib/Plugin/Block'. + $plugin_namespaces[$namespace][] = $dir . $this->directorySuffix; + } + } + } + else { + // Both the namespace suffix and the directory suffix are empty, + // so the plugin namespaces and directories are the same as the base + // directories. + foreach ($this->rootNamespacesIterator as $namespace => $dirs) { + $plugin_namespaces[$namespace] = (array) $dirs; } - $plugin_namespaces[$namespace] = array($dir); } return $plugin_namespaces; diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index bd50676..2157130 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -456,21 +456,26 @@ function simpletest_test_get_all($module = NULL) { ); } foreach ($all_data as $name => $data) { - // Build directory in which the test files would reside. - $tests_dir = DRUPAL_ROOT . '/' . dirname($data->uri) . '/lib/Drupal/' . $name . '/Tests'; + $extension_dir = DRUPAL_ROOT . '/' . dirname($data->uri); + + // Build directories in which the test files would reside. + $tests_dirs = array( + $extension_dir . '/lib/Drupal/' . $name . '/Tests', + $extension_dir . '/lib/Tests', + ); + + $namespace = 'Drupal\\' . $name . '\Tests\\'; + // Scan it for test files if it exists. - if (is_dir($tests_dir)) { - $files = file_scan_directory($tests_dir, '/.*\.php/'); - if (!empty($files)) { - $basedir = DRUPAL_ROOT . '/' . dirname($data->uri) . '/lib/'; - foreach ($files as $file) { - // Convert the file name into the namespaced class name. - $replacements = array( - '/' => '\\', - $basedir => '', - '.php' => '', - ); - $classes[] = strtr($file->uri, $replacements); + foreach ($tests_dirs as $tests_dir) { + if (is_dir($tests_dir)) { + $files = file_scan_directory($tests_dir, '/.*\.php/'); + if (!empty($files)) { + $strlen = strlen($tests_dir) + 1; + // Convert the file names into the namespaced class names. + foreach ($files as $file) { + $classes[] = $namespace . str_replace('/', '\\', substr($file->uri, $strlen, -4)); + } } } } diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php index d2fba9b..52bd303 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/AnnotatedClassDiscoveryTest.php @@ -57,7 +57,13 @@ public function setUp() { 'provider' => 'plugin_test', ), ); - $namespaces = new \ArrayObject(array('Drupal\plugin_test' => DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib')); + $namespaces = new \ArrayObject(array( + 'Drupal\plugin_test' => array( + // @todo Remove lib/Drupal/$module, once the switch to PSR-4 is complete. + DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test', + DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib', + ), + )); $this->discovery = new AnnotatedClassDiscovery('Plugin/plugin_test/fruit', $namespaces); $this->emptyDiscovery = new AnnotatedClassDiscovery('Plugin/non_existing_module/non_existing_plugin_type', $namespaces); } diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/CustomAnnotationClassDiscoveryTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/CustomAnnotationClassDiscoveryTest.php index d61c013..17bbfa5 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/CustomAnnotationClassDiscoveryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/CustomAnnotationClassDiscoveryTest.php @@ -41,7 +41,13 @@ protected function setUp() { 'provider' => 'plugin_test', ), ); - $root_namespaces = new \ArrayObject(array('Drupal\plugin_test' => DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib')); + $root_namespaces = new \ArrayObject(array( + 'Drupal\plugin_test' => array( + // @todo Remove lib/Drupal/$module, once the switch to PSR-4 is complete. + DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test', + DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib', + ), + )); $this->discovery = new AnnotatedClassDiscovery('Plugin/plugin_test/custom_annotation', $root_namespaces, 'Drupal\plugin_test\Plugin\Annotation\PluginExample'); $this->emptyDiscovery = new AnnotatedClassDiscovery('Plugin/non_existing_module/non_existing_plugin_type', $root_namespaces, 'Drupal\plugin_test\Plugin\Annotation\PluginExample'); diff --git a/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php b/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php index 52d4fea..49ae502 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Plugin/Discovery/CustomDirectoryAnnotatedClassDiscoveryTest.php @@ -70,7 +70,13 @@ protected function setUp() { 'provider' => 'plugin_test', ), ); - $namespaces = new \ArrayObject(array('Drupal\plugin_test' => DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib')); + $namespaces = new \ArrayObject(array( + 'Drupal\plugin_test' => array( + // @todo Remove lib/Drupal/$module, once the switch to PSR-4 is complete. + DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib/Drupal/plugin_test', + DRUPAL_ROOT . '/core/modules/system/tests/modules/plugin_test/lib', + ), + )); $this->discovery = new AnnotatedClassDiscovery('', $namespaces); $empty_namespaces = new \ArrayObject(); $this->emptyDiscovery = new AnnotatedClassDiscovery('', $empty_namespaces); diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist index 35ff254..5d4aec5 100644 --- a/core/phpunit.xml.dist +++ b/core/phpunit.xml.dist @@ -12,7 +12,7 @@ ./drush/tests - ./modules/config/tests/config_test/lib/Drupal/config_test + ./modules/config/tests/config_test/lib diff --git a/core/scripts/switch-psr4.sh b/core/scripts/switch-psr4.sh new file mode 100644 index 0000000..c1403df --- /dev/null +++ b/core/scripts/switch-psr4.sh @@ -0,0 +1,195 @@ +#!/bin/php +isDot()) { + // do nothing + } + elseif ($fileinfo->isDir()) { + process_candidate_dir($fileinfo->getPathname()); + } + } +} + +/** + * Recursively scans a directory for Drupal extensions, and runs + * process_extension() for each one that it finds. + * + * @param string $dir + * A directory that could be a Drupal extension directory. + */ +function process_candidate_dir($dir) { + /** + * @var \DirectoryIterator $fileinfo + */ + foreach (new \DirectoryIterator($dir) as $fileinfo) { + if ($fileinfo->isDot()) { + // Ignore "." and "..". + } + elseif ($fileinfo->isDir()) { + // It's a directory. + switch ($fileinfo->getFilename()) { + case 'lib': + // Ignore these directory names. + continue; + default: + // Look for more extensions in subdirectories. + process_candidate_dir($fileinfo->getPathname()); + } + } + else { + // It's a file. + if (preg_match('/^(.+).info.yml$/', $fileinfo->getFilename(), $m)) { + // It's a *.info.yml file, so we found an extension directory. + $extension_name = $m[1]; + } + } + } + if (isset($extension_name)) { + process_extension($extension_name, $dir); + } +} + +/** + * Process a Drupal extension (module, theme) in a directory. + * + * This will move all class files in this extension two levels up, from + * lib/Drupal/$name/ to lib/. + * + * @param string $name + * Name of the extension. + * @param string $dir + * Directory of the extension. + * @throws \Exception + */ +function process_extension($name, $dir) { + + if (!is_dir($src = "$dir/lib/Drupal/$name")) { + // Nothing to do in this module. + return; + } + + // Move class files two levels up. + move_directory_contents($src, "$dir/lib"); + + // Clean up. + require_dir_empty("$dir/lib/Drupal"); + rmdir("$dir/lib/Drupal"); +} + +/** + * Move directory contents from an existing source directory to an existing + * destination directory. + * + * @param string $source + * An existing source directory. + * @param string $destination + * An existing destination directory. + * + * @throws \Exception + */ +function move_directory_contents($source, $destination) { + + if (!is_dir($source)) { + throw new \Exception("The source '$source' is not a directory."); + } + + if (!is_dir($destination)) { + throw new \Exception("The destination '$destination' is not a directory."); + } + + /** + * @var \DirectoryIterator $fileinfo + */ + foreach (new \DirectoryIterator($source) as $fileinfo) { + if ($fileinfo->isDot()) { + continue; + } + $dest_path = $destination . '/' . $fileinfo->getFilename(); + if (!file_exists($dest_path)) { + rename($fileinfo->getPathname(), $dest_path); + } + elseif ($fileinfo->isFile()) { + throw new \Exception("Destination '$dest_path' already exists, cannot overwrite."); + } + elseif ($fileinfo->isDir()) { + if (!is_dir($dest_path)) { + throw new \Exception("Destination '$dest_path' is not a directory."); + } + move_directory_contents($fileinfo->getPathname(), $dest_path); + } + } + + require_dir_empty($source); + + rmdir($source); +} + +/** + * Throws an exception if a directory is not empty. + * + * @param string $dir + * Directory to check. + * + * @throws \Exception + */ +function require_dir_empty($dir) { + if (is_file($dir)) { + throw new \Exception("The path '$dir' is a file, when it should be a directory."); + } + if (!is_dir($dir)) { + throw new \Exception("The directory '$dir' does not exist."); + } + if (!is_readable($dir)) { + throw new \Exception("The directory '$dir' is not readable."); + } + /** + * @var \DirectoryIterator $fileinfo + */ + foreach (new \DirectoryIterator($dir) as $fileinfo) { + if ($fileinfo->isDot()) { + continue; + } + $path = $fileinfo->getPathname(); + if ($fileinfo->isFile()) { + throw new \Exception("File '$path' found in a directory that should be empty."); + } + elseif ($fileinfo->isDir()) { + throw new \Exception("Subdirectory '$path' found in a directory that should be empty."); + } + } +} diff --git a/core/tests/bootstrap.php b/core/tests/bootstrap.php index c69a52f..ccb93ca 100644 --- a/core/tests/bootstrap.php +++ b/core/tests/bootstrap.php @@ -49,16 +49,17 @@ function drupal_phpunit_contrib_extension_directory_roots() { /** * Registers the namespace for each extension directory with the autoloader. * - * @param Composer\Autoload\ClassLoader $loader + * @param Drupal\Core\Autoload\ClassLoader $loader * The supplied autoloader. * @param array $dirs * An associative array of extension directories, keyed by extension name. */ -function drupal_phpunit_register_extension_dirs(Composer\Autoload\ClassLoader $loader, $dirs) { +function drupal_phpunit_register_extension_dirs(Drupal\Core\Autoload\ClassLoader $loader, $dirs) { foreach ($dirs as $extension => $dir) { $lib_path = $dir . '/lib'; if (is_dir($lib_path)) { $loader->add('Drupal\\' . $extension, $lib_path); + $loader->addPsr4('Drupal\\' . $extension . '\\', $lib_path); } $tests_path = $dir . '/tests'; if (is_dir($tests_path)) { diff --git a/core/vendor/autoload.php b/core/vendor/autoload.php index aea9b17..e665445 100644 --- a/core/vendor/autoload.php +++ b/core/vendor/autoload.php @@ -1,7 +1,23 @@