jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/373626 )

Change subject: Enable using PSR-4 autoloader for MediaWiki core and extensions
......................................................................


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
---
M autoload.php
M docs/extension.schema.v1.json
M docs/extension.schema.v2.json
M includes/AutoLoader.php
M includes/registration/ExtensionRegistry.php
M includes/utils/AutoloadGenerator.php
M maintenance/generateLocalAutoload.php
M tests/phpunit/structure/AutoLoaderTest.php
8 files changed, 112 insertions(+), 17 deletions(-)

Approvals:
  Tim Starling: Looks good to me, approved
  jenkins-bot: Verified



diff --git a/autoload.php b/autoload.php
index 2661fd7..988701d 100644
--- a/autoload.php
+++ b/autoload.php
@@ -892,9 +892,6 @@
        '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 7cfebca..ddf82e8 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 75a4f2c..0bdf97d 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 8dc7d40..675e347 100644
--- a/includes/AutoLoader.php
+++ b/includes/AutoLoader.php
@@ -31,6 +31,12 @@
        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 @@
                        }
                }
 
+               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 @@
        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 740fed4..bc2f8e4 100644
--- a/includes/registration/ExtensionRegistry.php
+++ b/includes/registration/ExtensionRegistry.php
@@ -196,6 +196,7 @@
        public function readFromQueue( array $queue ) {
                global $wgVersion;
                $autoloadClasses = [];
+               $autoloadNamespaces = [];
                $autoloaderPaths = [];
                $processor = new ExtensionProcessor();
                $versionChecker = new VersionChecker( $wgVersion );
@@ -226,10 +227,15 @@
                                $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 @@
 
                        // 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 @@
                $data['globals']['wgAutoloadClasses'] = [];
                $data['autoload'] = $autoloadClasses;
                $data['autoloaderPaths'] = $autoloaderPaths;
+               $data['autoloaderNS'] = $autoloadNamespaces;
                return $data;
        }
 
@@ -313,6 +320,10 @@
                                default:
                                        throw new UnexpectedValueException( 
"Unknown merge strategy '$mergeStrategy'" );
                        }
+               }
+
+               if ( isset( $info['autoloaderNS'] ) ) {
+                       Autoloader::$psr4Namespaces += $info['autoloaderNS'];
                }
 
                foreach ( $info['defines'] as $name => $val ) {
@@ -399,20 +410,16 @@
        }
 
        /**
-        * 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 421a890..1c7c9b0 100644
--- a/includes/utils/AutoloadGenerator.php
+++ b/includes/utils/AutoloadGenerator.php
@@ -43,6 +43,13 @@
        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
         *
@@ -58,6 +65,32 @@
                if ( in_array( 'local', $flags ) ) {
                        $this->variableName = 'wgAutoloadLocalClasses';
                }
+       }
+
+       /**
+        * 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;
        }
 
        /**
@@ -94,6 +127,9 @@
                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 0c278bc..bec11a0 100644
--- a/maintenance/generateLocalAutoload.php
+++ b/maintenance/generateLocalAutoload.php
@@ -4,12 +4,14 @@
        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 d81e8c6..d45a58c 100644
--- a/tests/phpunit/structure/AutoLoaderTest.php
+++ b/tests/phpunit/structure/AutoLoaderTest.php
@@ -161,6 +161,7 @@
                $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' );
 

-- 
To view, visit https://gerrit.wikimedia.org/r/373626
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: Id095dde37cbb40aa424fb628bd3c94e684ca2f65
Gerrit-PatchSet: 9
Gerrit-Project: mediawiki/core
Gerrit-Branch: master
Gerrit-Owner: Legoktm <lego...@member.fsf.org>
Gerrit-Reviewer: Daniel Kinzler <daniel.kinz...@wikimedia.de>
Gerrit-Reviewer: Jforrester <jforres...@wikimedia.org>
Gerrit-Reviewer: Legoktm <lego...@member.fsf.org>
Gerrit-Reviewer: Lucas Werkmeister (WMDE) <lucas.werkmeis...@wikimedia.de>
Gerrit-Reviewer: Thiemo Mättig (WMDE) <thiemo.kr...@wikimedia.de>
Gerrit-Reviewer: Tim Starling <tstarl...@wikimedia.org>
Gerrit-Reviewer: WMDE-leszek <leszek.mani...@wikimedia.de>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to