Mechanism for renaming/aliasing classes
authorKevin Israel <pleasestand@live.com>
Fri, 22 Nov 2013 04:13:03 +0000 (23:13 -0500)
committerKevin Israel <pleasestand@live.com>
Mon, 6 Jan 2014 15:36:18 +0000 (10:36 -0500)
Sometimes it is desirable to change a class name that is still referenced
in extensions or config files (e.g. for consistency, as in I507ba00a).
PHP's class_alias() function can help in preserving backward compatibility;
however, creating an alias first requires that the class be loaded.

Hence this is implemented in AutoLoader. Lazy loading continues to work,
the list of class names is still maintained in a central location, and
optionally, deprecation warnings can be generated.

Change-Id: I1d3fb04a448647b5be10bed7fec05238b9fc6fc7

includes/AutoLoader.php
tests/phpunit/data/autoloader/TestAutoloadedAliasedClassNew.php [new file with mode: 0644]
tests/phpunit/structure/AutoLoaderTest.php

index cd062e0..e6ce693 100644 (file)
@@ -1209,6 +1209,32 @@ class AutoLoader {
                        return;
                }
 
+               if ( substr( $filename, 0, 6 ) === 'alias:' ) {
+                       // Supported alias formats:
+                       // - No deprecation warning: alias:MyNewClassName
+                       // - Deprecated in MediaWiki 1.1: alias:MyNewClassName?v=1.1
+                       // - Deprecated in MyExtension 1.1: alias:MyNewClassName?c=MyExtension&v=1.1
+                       $parts = explode( '?', substr( $filename, 6 ), 2 );
+                       $newClassName = $parts[0];
+
+                       // If necessary, this will make a recursive call to this function to
+                       // load the class using its actual, canonical name.
+                       class_alias( $newClassName, $className );
+
+                       if ( isset( $parts[1] ) && function_exists( 'wfDeprecated' ) ) {
+                               $info = wfCgiToArray( $parts[1] );
+                               $function = "name $className for class $newClassName";
+                               $version = isset( $info['v'] ) ? $info['v'] : false;
+                               $component = isset( $info['c'] ) ? $info['c'] : false;
+
+                               // https://github.com/facebook/hhvm/issues/1018
+                               $callerOffset = wfIsHHVM() ? 2 : 3;
+                               wfDeprecated( $function, $version, $component, $callerOffset );
+                       }
+
+                       return;
+               }
+
                # Make an absolute path, this improves performance by avoiding some stat calls
                if ( substr( $filename, 0, 1 ) != '/' && substr( $filename, 1, 1 ) != ':' ) {
                        global $IP;
diff --git a/tests/phpunit/data/autoloader/TestAutoloadedAliasedClassNew.php b/tests/phpunit/data/autoloader/TestAutoloadedAliasedClassNew.php
new file mode 100644 (file)
index 0000000..5ce8483
--- /dev/null
@@ -0,0 +1,4 @@
+<?php
+
+class TestAutoloadedAliasedClassNew {
+}
index d8b90d5..1ffe811 100644 (file)
@@ -11,6 +11,9 @@ class AutoLoaderTest extends MediaWikiTestCase {
                        'TestAutoloadedLocalClass' => __DIR__ . '/../data/autoloader/TestAutoloadedLocalClass.php',
                        'TestAutoloadedCamlClass' => __DIR__ . '/../data/autoloader/TestAutoloadedCamlClass.php',
                        'TestAutoloadedSerializedClass' => __DIR__ . '/../data/autoloader/TestAutoloadedSerializedClass.php',
+                       'TestAutoloadedAliasedClass' => 'alias:TestAutoloadedAliasedClassNew',
+                       'TestAutoloadedAliasedClassDeprecated' => 'alias:TestAutoloadedAliasedClassNew?v=1.1',
+                       'TestAutoloadedAliasedClassNew' => __DIR__ . '/../data/autoloader/TestAutoloadedAliasedClassNew.php',
                );
                $this->setMwGlobals( 'wgAutoloadLocalClasses', $this->testLocalClasses + $wgAutoloadLocalClasses );
                AutoLoader::resetAutoloadLocalClassesLower();
@@ -44,7 +47,23 @@ class AutoLoaderTest extends MediaWikiTestCase {
                $expected = $wgAutoloadLocalClasses + $wgAutoloadClasses;
                $actual = array();
 
-               $files = array_unique( $expected );
+               // Check aliases
+               foreach ( $expected as $class => $file ) {
+                       if ( substr( $file, 0, 6 ) !== 'alias:' ) {
+                               // Not an alias, so should be an actual file
+                               $files[] = $file;
+                       } else {
+                               $newClass = substr( $file, 6, strcspn( $file, '?', 6 ) );
+                               if ( isset( $expected[$newClass] ) ) {
+                                       if ( substr( $expected[$newClass], 0, 6 ) !== 'alias:' ) {
+                                               // Alias pointing to an existing MediaWiki class
+                                               $actual[$class] = $file;
+                                       }
+                               }
+                       }
+               }
+
+               $files = array_unique( $files );
 
                foreach ( $files as $file ) {
                        // Only prefix $IP if it doesn't have it already.
@@ -92,4 +111,16 @@ class AutoLoaderTest extends MediaWikiTestCase {
                $this->assertFalse( $uncerealized instanceof __PHP_Incomplete_Class,
                        "unserialize() can load classes case-insensitively." );
        }
+
+       function testAliasedClass() {
+               $this->assertSame( 'TestAutoloadedAliasedClassNew',
+                       get_class( new TestAutoloadedAliasedClass ) );
+       }
+
+       function testAliasedClassDeprecated() {
+               wfSuppressWarnings();
+               $this->assertSame( 'TestAutoloadedAliasedClassNew',
+                       get_class( new TestAutoloadedAliasedClassDeprecated ) );
+               wfRestoreWarnings();
+       }
 }