aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKunal Mehta <legoktm@member.fsf.org>2017-08-24 11:05:26 -0700
committerTim Starling <tstarling@wikimedia.org>2017-12-12 00:20:11 +0000
commit036f5b47efc99b56e9e841c8ebdfec7d3e197f83 (patch)
treef1043446f850f8ff9d760a2658c884b80af34045
parent09112df015428fcbb7f8112677e3b99a17754d0f (diff)
downloadmediawikicore-036f5b47efc99b56e9e841c8ebdfec7d3e197f83.tar.gz
mediawikicore-036f5b47efc99b56e9e841c8ebdfec7d3e197f83.zip
Enable using PSR-4 autoloader for MediaWiki core and extensions
This adds support for a PSR-4 (<http://www.php-fig.org/psr/psr-4/>) autoloader, so instead of needing to manually list each class, just the namespace prefix is needed. Extensions can set a "AutoloadNamespaces" property in extension.json to register PSR-4 compatible namespaces to be autoloaded. The implementation is based off of the example implementation (<http://www.php-fig.org/psr/psr-4/examples/>) with some modifications for performance, notably cutting down on function calls, and only trying to look up classes that are namespaced. The generateLocalAutoload.php script will ignore any directory that is registered as a PSR-4 namespace. Bug: T99865 Bug: T173799 Change-Id: Id095dde37cbb40aa424fb628bd3c94e684ca2f65
-rw-r--r--autoload.php3
-rw-r--r--docs/extension.schema.v1.json4
-rw-r--r--docs/extension.schema.v2.json4
-rw-r--r--includes/AutoLoader.php44
-rw-r--r--includes/registration/ExtensionRegistry.php35
-rw-r--r--includes/utils/AutoloadGenerator.php36
-rw-r--r--maintenance/generateLocalAutoload.php2
-rw-r--r--tests/phpunit/structure/AutoLoaderTest.php1
8 files changed, 112 insertions, 17 deletions
diff --git a/autoload.php b/autoload.php
index 2661fd7ed3a9..988701df769d 100644
--- a/autoload.php
+++ b/autoload.php
@@ -892,9 +892,6 @@ $wgAutoloadLocalClasses = [
'MediaWiki\\Languages\\Data\\CrhExceptions' => __DIR__ . '/languages/data/CrhExceptions.php',
'MediaWiki\\Languages\\Data\\Names' => __DIR__ . '/languages/data/Names.php',
'MediaWiki\\Languages\\Data\\ZhConversion' => __DIR__ . '/languages/data/ZhConversion.php',
- 'MediaWiki\\Linker\\LinkRenderer' => __DIR__ . '/includes/linker/LinkRenderer.php',
- 'MediaWiki\\Linker\\LinkRendererFactory' => __DIR__ . '/includes/linker/LinkRendererFactory.php',
- 'MediaWiki\\Linker\\LinkTarget' => __DIR__ . '/includes/linker/LinkTarget.php',
'MediaWiki\\Logger\\ConsoleLogger' => __DIR__ . '/includes/debug/logger/ConsoleLogger.php',
'MediaWiki\\Logger\\ConsoleSpi' => __DIR__ . '/includes/debug/logger/ConsoleSpi.php',
'MediaWiki\\Logger\\LegacyLogger' => __DIR__ . '/includes/debug/logger/LegacyLogger.php',
diff --git a/docs/extension.schema.v1.json b/docs/extension.schema.v1.json
index 7cfebcafa4d6..ddf82e8d9e37 100644
--- a/docs/extension.schema.v1.json
+++ b/docs/extension.schema.v1.json
@@ -567,6 +567,10 @@
"type": "object",
"description": "SpecialPages implemented in this extension (mapping of page name to class name)"
},
+ "AutoloadNamespaces": {
+ "type": "object",
+ "description": "Mapping of PSR-4 compliant namespace to directory for autoloading"
+ },
"AutoloadClasses": {
"type": "object"
},
diff --git a/docs/extension.schema.v2.json b/docs/extension.schema.v2.json
index 75a4f2c6fc64..0bdf97d41e8e 100644
--- a/docs/extension.schema.v2.json
+++ b/docs/extension.schema.v2.json
@@ -588,6 +588,10 @@
"type": "object",
"description": "SpecialPages implemented in this extension (mapping of page name to class name)"
},
+ "AutoloadNamespaces": {
+ "type": "object",
+ "description": "Mapping of PSR-4 compliant namespace to directory for autoloading"
+ },
"AutoloadClasses": {
"type": "object"
},
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php
index 8dc7d4094a0b..675e347b0d4b 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -31,6 +31,12 @@ class AutoLoader {
static protected $autoloadLocalClassesLower = null;
/**
+ * @private Only public for ExtensionRegistry
+ * @var string[] Namespace (ends with \) => Path (ends with /)
+ */
+ static public $psr4Namespaces = [];
+
+ /**
* autoload - take a class name and attempt to load it
*
* @param string $className Name of class we're looking for.
@@ -67,6 +73,28 @@ class AutoLoader {
}
}
+ if ( !$filename && strpos( $className, '\\' ) !== false ) {
+ // This class is namespaced, so try looking at the namespace map
+ $prefix = $className;
+ while ( false !== $pos = strrpos( $prefix, '\\' ) ) {
+ // Check to see if this namespace prefix is in the map
+ $prefix = substr( $className, 0, $pos + 1 );
+ if ( isset( self::$psr4Namespaces[$prefix] ) ) {
+ $relativeClass = substr( $className, $pos + 1 );
+ // Build the expected filename, and see if it exists
+ $file = self::$psr4Namespaces[$prefix] .
+ str_replace( '\\', '/', $relativeClass ) . '.php';
+ if ( file_exists( $file ) ) {
+ $filename = $file;
+ break;
+ }
+ }
+
+ // Remove trailing separator for next iteration
+ $prefix = rtrim( $prefix, '\\' );
+ }
+ }
+
if ( !$filename ) {
// Class not found; let the next autoloader try to find it
return;
@@ -88,6 +116,22 @@ class AutoLoader {
static function resetAutoloadLocalClassesLower() {
self::$autoloadLocalClassesLower = null;
}
+
+ /**
+ * Get a mapping of namespace => file path
+ * The namespaces should follow the PSR-4 standard for autoloading
+ *
+ * @see <http://www.php-fig.org/psr/psr-4/>
+ * @private Only public for usage in AutoloadGenerator
+ * @since 1.31
+ * @return string[]
+ */
+ public static function getAutoloadNamespaces() {
+ return [
+ 'MediaWiki\\Linker\\' => __DIR__ .'/linker/'
+ ];
+ }
}
+Autoloader::$psr4Namespaces = AutoLoader::getAutoloadNamespaces();
spl_autoload_register( [ 'AutoLoader', 'autoload' ] );
diff --git a/includes/registration/ExtensionRegistry.php b/includes/registration/ExtensionRegistry.php
index 740fed4eac80..bc2f8e47d3bc 100644
--- a/includes/registration/ExtensionRegistry.php
+++ b/includes/registration/ExtensionRegistry.php
@@ -196,6 +196,7 @@ class ExtensionRegistry {
public function readFromQueue( array $queue ) {
global $wgVersion;
$autoloadClasses = [];
+ $autoloadNamespaces = [];
$autoloaderPaths = [];
$processor = new ExtensionProcessor();
$versionChecker = new VersionChecker( $wgVersion );
@@ -226,10 +227,15 @@ class ExtensionRegistry {
$incompatible[] = "$path: unsupported manifest_version: {$version}";
}
- $autoload = $this->processAutoLoader( dirname( $path ), $info );
- // Set up the autoloader now so custom processors will work
- $GLOBALS['wgAutoloadClasses'] += $autoload;
- $autoloadClasses += $autoload;
+ $dir = dirname( $path );
+ if ( isset( $info['AutoloadClasses'] ) ) {
+ $autoload = $this->processAutoLoader( $dir, $info['AutoloadClasses'] );
+ $GLOBALS['wgAutoloadClasses'] += $autoload;
+ $autoloadClasses += $autoload;
+ }
+ if ( isset( $info['AutoloadNamespaces'] ) ) {
+ $autoloadNamespaces += $this->processAutoLoader( $dir, $info['AutoloadNamespaces'] );
+ }
// get all requirements/dependencies for this extension
$requires = $processor->getRequirements( $info );
@@ -241,7 +247,7 @@ class ExtensionRegistry {
// Get extra paths for later inclusion
$autoloaderPaths = array_merge( $autoloaderPaths,
- $processor->getExtraAutoloaderPaths( dirname( $path ), $info ) );
+ $processor->getExtraAutoloaderPaths( $dir, $info ) );
// Compatible, read and extract info
$processor->extractInfo( $path, $info, $version );
}
@@ -268,6 +274,7 @@ class ExtensionRegistry {
$data['globals']['wgAutoloadClasses'] = [];
$data['autoload'] = $autoloadClasses;
$data['autoloaderPaths'] = $autoloaderPaths;
+ $data['autoloaderNS'] = $autoloadNamespaces;
return $data;
}
@@ -315,6 +322,10 @@ class ExtensionRegistry {
}
}
+ if ( isset( $info['autoloaderNS'] ) ) {
+ Autoloader::$psr4Namespaces += $info['autoloaderNS'];
+ }
+
foreach ( $info['defines'] as $name => $val ) {
define( $name, $val );
}
@@ -399,20 +410,16 @@ class ExtensionRegistry {
}
/**
- * Register classes with the autoloader
+ * Fully expand autoloader paths
*
* @param string $dir
* @param array $info
* @return array
*/
protected function processAutoLoader( $dir, array $info ) {
- if ( isset( $info['AutoloadClasses'] ) ) {
- // Make paths absolute, relative to the JSON file
- return array_map( function ( $file ) use ( $dir ) {
- return "$dir/$file";
- }, $info['AutoloadClasses'] );
- } else {
- return [];
- }
+ // Make paths absolute, relative to the JSON file
+ return array_map( function ( $file ) use ( $dir ) {
+ return "$dir/$file";
+ }, $info );
}
}
diff --git a/includes/utils/AutoloadGenerator.php b/includes/utils/AutoloadGenerator.php
index 421a89067f2e..1c7c9b0f0f25 100644
--- a/includes/utils/AutoloadGenerator.php
+++ b/includes/utils/AutoloadGenerator.php
@@ -43,6 +43,13 @@ class AutoloadGenerator {
protected $overrides = [];
/**
+ * Directories that should be excluded
+ *
+ * @var string[]
+ */
+ protected $excludePaths = [];
+
+ /**
* @param string $basepath Root path of the project being scanned for classes
* @param array|string $flags
*
@@ -61,6 +68,32 @@ class AutoloadGenerator {
}
/**
+ * Directories that should be excluded
+ *
+ * @since 1.31
+ * @param string[] $paths
+ */
+ public function setExcludePaths( array $paths ) {
+ $this->excludePaths = $paths;
+ }
+
+ /**
+ * Whether the file should be excluded
+ *
+ * @param string $path File path
+ * @return bool
+ */
+ private function shouldExclude( $path ) {
+ foreach ( $this->excludePaths as $dir ) {
+ if ( strpos( $path, $dir ) === 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
* Force a class to be autoloaded from a specific path, regardless of where
* or if it was detected.
*
@@ -94,6 +127,9 @@ class AutoloadGenerator {
if ( substr( $inputPath, 0, $len ) !== $this->basepath ) {
throw new \Exception( "Path is not within basepath: $inputPath" );
}
+ if ( $this->shouldExclude( $inputPath ) ) {
+ return;
+ }
$result = $this->collector->getClasses(
file_get_contents( $inputPath )
);
diff --git a/maintenance/generateLocalAutoload.php b/maintenance/generateLocalAutoload.php
index 0c278bc18ea8..bec11a0de1d5 100644
--- a/maintenance/generateLocalAutoload.php
+++ b/maintenance/generateLocalAutoload.php
@@ -4,12 +4,14 @@ if ( PHP_SAPI != 'cli' ) {
die( "This script can only be run from the command line.\n" );
}
+require_once __DIR__ . '/../includes/AutoLoader.php';
require_once __DIR__ . '/../includes/utils/AutoloadGenerator.php';
// Mediawiki installation directory
$base = dirname( __DIR__ );
$generator = new AutoloadGenerator( $base, 'local' );
+$generator->setExcludePaths( array_values( AutoLoader::getAutoloadNamespaces() ) );
$generator->initMediaWikiDefault();
// Write out the autoload
diff --git a/tests/phpunit/structure/AutoLoaderTest.php b/tests/phpunit/structure/AutoLoaderTest.php
index d81e8c663d87..d45a58c5d38c 100644
--- a/tests/phpunit/structure/AutoLoaderTest.php
+++ b/tests/phpunit/structure/AutoLoaderTest.php
@@ -161,6 +161,7 @@ class AutoLoaderTest extends MediaWikiTestCase {
$path = realpath( __DIR__ . '/../../..' );
$oldAutoload = file_get_contents( $path . '/autoload.php' );
$generator = new AutoloadGenerator( $path, 'local' );
+ $generator->setExcludePaths( array_values( AutoLoader::getAutoloadNamespaces() ) );
$generator->initMediaWikiDefault();
$newAutoload = $generator->getAutoload( 'maintenance/generateLocalAutoload.php' );