2012-11-07 01:27:30 +00:00
|
|
|
<?php
|
|
|
|
|
|
2021-01-08 02:16:02 +00:00
|
|
|
use PhpParser\Node;
|
|
|
|
|
use PhpParser\Node\Expr;
|
|
|
|
|
use PhpParser\Node\Stmt;
|
|
|
|
|
use PhpParser\ParserFactory;
|
|
|
|
|
|
2020-06-30 15:09:24 +00:00
|
|
|
class AutoLoaderStructureTest extends MediaWikiIntegrationTestCase {
|
2013-07-17 23:55:36 +00:00
|
|
|
/**
|
|
|
|
|
* Assert that there were no classes loaded that are not registered with the AutoLoader.
|
|
|
|
|
*
|
|
|
|
|
* For example foo.php having class Foo and class Bar but only registering Foo.
|
|
|
|
|
* This is important because we should not be relying on Foo being used before Bar.
|
|
|
|
|
*/
|
2012-11-07 01:27:30 +00:00
|
|
|
public function testAutoLoadConfig() {
|
|
|
|
|
$results = self::checkAutoLoadConf();
|
|
|
|
|
|
|
|
|
|
$this->assertEquals(
|
|
|
|
|
$results['expected'],
|
|
|
|
|
$results['actual']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-25 01:55:47 +00:00
|
|
|
private static function parseFile( $contents ) {
|
|
|
|
|
// We could use token_get_all() here, but this is faster
|
|
|
|
|
// Note: Keep in sync with ClassCollector
|
|
|
|
|
$matches = [];
|
|
|
|
|
preg_match_all( '/
|
|
|
|
|
^ [\t ]* (?:
|
|
|
|
|
(?:final\s+)? (?:abstract\s+)? (?:class|interface|trait) \s+
|
2018-11-07 17:15:51 +00:00
|
|
|
(?P<class> \w+)
|
2018-05-25 01:55:47 +00:00
|
|
|
|
|
|
|
|
|
class_alias \s* \( \s*
|
|
|
|
|
([\'"]) (?P<original> [^\'"]+) \g{-2} \s* , \s*
|
|
|
|
|
([\'"]) (?P<alias> [^\'"]+ ) \g{-2} \s*
|
|
|
|
|
\) \s* ;
|
|
|
|
|
|
|
|
|
|
|
class_alias \s* \( \s*
|
2018-11-07 17:15:51 +00:00
|
|
|
(?P<originalStatic> [\w\\\\]+)::class \s* , \s*
|
2018-05-25 01:55:47 +00:00
|
|
|
([\'"]) (?P<aliasString> [^\'"]+ ) \g{-2} \s*
|
|
|
|
|
\) \s* ;
|
|
|
|
|
)
|
|
|
|
|
/imx', $contents, $matches, PREG_SET_ORDER );
|
|
|
|
|
|
|
|
|
|
$namespaceMatch = [];
|
|
|
|
|
preg_match( '/
|
|
|
|
|
^ [\t ]*
|
|
|
|
|
namespace \s+
|
2018-11-07 17:15:51 +00:00
|
|
|
(\w+(\\\\\w+)*)
|
2018-05-25 01:55:47 +00:00
|
|
|
\s* ;
|
|
|
|
|
/imx', $contents, $namespaceMatch );
|
|
|
|
|
$fileNamespace = $namespaceMatch ? $namespaceMatch[1] . '\\' : '';
|
|
|
|
|
|
|
|
|
|
$classesInFile = [];
|
|
|
|
|
$aliasesInFile = [];
|
|
|
|
|
|
|
|
|
|
foreach ( $matches as $match ) {
|
|
|
|
|
if ( !empty( $match['class'] ) ) {
|
|
|
|
|
// 'class Foo {}'
|
|
|
|
|
$class = $fileNamespace . $match['class'];
|
|
|
|
|
$classesInFile[$class] = true;
|
2019-03-29 20:12:24 +00:00
|
|
|
} elseif ( !empty( $match['original'] ) ) {
|
|
|
|
|
// 'class_alias( "Foo", "Bar" );'
|
2020-02-21 00:01:43 +00:00
|
|
|
$aliasesInFile[self::removeSlashes( $match['alias'] )] = $match['original'];
|
2018-05-25 01:55:47 +00:00
|
|
|
} else {
|
2019-03-29 20:12:24 +00:00
|
|
|
// 'class_alias( Foo::class, "Bar" );'
|
2020-02-21 00:01:43 +00:00
|
|
|
$aliasesInFile[self::removeSlashes( $match['aliasString'] )] =
|
|
|
|
|
$fileNamespace . $match['originalStatic'];
|
2018-05-25 01:55:47 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [ $classesInFile, $aliasesInFile ];
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-21 00:01:43 +00:00
|
|
|
private static function removeSlashes( $str ) {
|
|
|
|
|
return str_replace( '\\\\', '\\', $str );
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-31 01:04:49 +00:00
|
|
|
private static function fixSlashes( $str ) {
|
|
|
|
|
return str_replace( '\\', '/', $str );
|
|
|
|
|
}
|
|
|
|
|
|
2012-11-07 01:27:30 +00:00
|
|
|
protected static function checkAutoLoadConf() {
|
|
|
|
|
global $wgAutoloadLocalClasses, $wgAutoloadClasses, $IP;
|
|
|
|
|
|
|
|
|
|
// wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php
|
|
|
|
|
$expected = $wgAutoloadLocalClasses + $wgAutoloadClasses;
|
2016-02-17 09:09:32 +00:00
|
|
|
$actual = [];
|
2012-11-07 01:27:30 +00:00
|
|
|
|
2018-09-20 17:44:35 +00:00
|
|
|
$psr4Namespaces = [];
|
|
|
|
|
foreach ( AutoLoader::getAutoloadNamespaces() as $ns => $path ) {
|
2020-10-31 01:04:49 +00:00
|
|
|
$psr4Namespaces[rtrim( $ns, '\\' ) . '\\'] = self::fixSlashes( rtrim( $path, '/' ) );
|
2018-09-20 17:44:35 +00:00
|
|
|
}
|
|
|
|
|
|
2018-11-07 17:15:51 +00:00
|
|
|
foreach ( $expected as $class => $file ) {
|
2012-11-07 01:27:30 +00:00
|
|
|
// Only prefix $IP if it doesn't have it already.
|
|
|
|
|
// Generally local classes don't have it, and those from extensions and test suites do.
|
|
|
|
|
if ( substr( $file, 0, 1 ) != '/' && substr( $file, 1, 1 ) != ':' ) {
|
2020-10-31 01:04:49 +00:00
|
|
|
$filePath = self::fixSlashes( "$IP/$file" );
|
2012-11-07 01:27:30 +00:00
|
|
|
} else {
|
2020-10-31 01:04:49 +00:00
|
|
|
$filePath = self::fixSlashes( $file );
|
2012-11-07 01:27:30 +00:00
|
|
|
}
|
2014-01-23 20:49:39 +00:00
|
|
|
|
2022-01-03 20:47:04 +00:00
|
|
|
if ( !is_file( $filePath ) ) {
|
2015-02-13 19:31:24 +00:00
|
|
|
$actual[$class] = "[file '$filePath' does not exist]";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-24 19:57:59 +00:00
|
|
|
$contents = @file_get_contents( $filePath );
|
2015-02-13 19:31:24 +00:00
|
|
|
if ( $contents === false ) {
|
|
|
|
|
$actual[$class] = "[couldn't read file '$filePath']";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2014-01-23 20:49:39 +00:00
|
|
|
|
2018-05-25 01:55:47 +00:00
|
|
|
list( $classesInFile, $aliasesInFile ) = self::parseFile( $contents );
|
2014-01-23 20:49:39 +00:00
|
|
|
|
2018-05-25 01:55:47 +00:00
|
|
|
foreach ( $classesInFile as $className => $ignore ) {
|
2018-09-20 17:44:35 +00:00
|
|
|
// Skip if it's a PSR4 class
|
|
|
|
|
$parts = explode( '\\', $className );
|
|
|
|
|
for ( $i = count( $parts ) - 1; $i > 0; $i-- ) {
|
|
|
|
|
$ns = implode( '\\', array_slice( $parts, 0, $i ) ) . '\\';
|
|
|
|
|
if ( isset( $psr4Namespaces[$ns] ) ) {
|
|
|
|
|
$expectedPath = $psr4Namespaces[$ns] . '/'
|
|
|
|
|
. implode( '/', array_slice( $parts, $i ) )
|
|
|
|
|
. '.php';
|
|
|
|
|
if ( $filePath === $expectedPath ) {
|
|
|
|
|
continue 2;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Nope, add it.
|
2018-05-25 01:55:47 +00:00
|
|
|
$actual[$className] = $file;
|
2012-11-07 01:27:30 +00:00
|
|
|
}
|
2014-01-23 20:49:39 +00:00
|
|
|
|
|
|
|
|
// Only accept aliases for classes in the same file, because for correct
|
|
|
|
|
// behavior, all aliases for a class must be set up when the class is loaded
|
|
|
|
|
// (see <https://bugs.php.net/bug.php?id=61422>).
|
|
|
|
|
foreach ( $aliasesInFile as $alias => $class ) {
|
|
|
|
|
if ( isset( $classesInFile[$class] ) ) {
|
|
|
|
|
$actual[$alias] = $file;
|
|
|
|
|
} else {
|
|
|
|
|
$actual[$alias] = "[original class not in $file]";
|
|
|
|
|
}
|
2012-11-07 01:27:30 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
return [
|
2012-11-07 01:27:30 +00:00
|
|
|
'expected' => $expected,
|
|
|
|
|
'actual' => $actual,
|
2016-02-17 09:09:32 +00:00
|
|
|
];
|
2012-11-07 01:27:30 +00:00
|
|
|
}
|
2013-10-27 05:59:01 +00:00
|
|
|
|
2018-05-25 05:13:22 +00:00
|
|
|
public function testAutoloadOrder() {
|
2019-06-18 17:26:06 +00:00
|
|
|
$path = __DIR__ . '/../../..';
|
2016-01-20 18:27:47 +00:00
|
|
|
$oldAutoload = file_get_contents( $path . '/autoload.php' );
|
|
|
|
|
$generator = new AutoloadGenerator( $path, 'local' );
|
2018-09-20 17:44:35 +00:00
|
|
|
$generator->setPsr4Namespaces( AutoLoader::getAutoloadNamespaces() );
|
2016-01-20 18:27:47 +00:00
|
|
|
$generator->initMediaWikiDefault();
|
|
|
|
|
$newAutoload = $generator->getAutoload( 'maintenance/generateLocalAutoload.php' );
|
|
|
|
|
|
|
|
|
|
$this->assertEquals( $oldAutoload, $newAutoload, 'autoload.php does not match' .
|
|
|
|
|
' output of generateLocalAutoload.php script.' );
|
|
|
|
|
}
|
2020-08-06 19:07:46 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Verify that all the directories specified for PSR-4 autoloading
|
|
|
|
|
* actually exist, to prevent situations like T259448
|
|
|
|
|
*/
|
|
|
|
|
public function testAutoloadNamespaces() {
|
|
|
|
|
$missing = [];
|
|
|
|
|
foreach ( AutoLoader::$psr4Namespaces as $ns => $path ) {
|
|
|
|
|
if ( !is_dir( $path ) ) {
|
|
|
|
|
$missing[] = "Directory $path for namespace $ns does not exist";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->assertSame( [], $missing );
|
|
|
|
|
}
|
2021-01-08 02:16:02 +00:00
|
|
|
|
|
|
|
|
public static function provideAutoloadNoFileScope() {
|
|
|
|
|
global $wgAutoloadLocalClasses;
|
|
|
|
|
$files = array_unique( $wgAutoloadLocalClasses );
|
|
|
|
|
$args = [];
|
|
|
|
|
foreach ( $files as $file ) {
|
|
|
|
|
$args[$file] = [ $file ];
|
|
|
|
|
}
|
|
|
|
|
return $args;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Confirm that all files in $wgAutoloadLocalClasses have no file-scope code
|
|
|
|
|
* apart from specific exemptions.
|
|
|
|
|
*
|
|
|
|
|
* This is slow (~15s). Running it arguably renders all the performance
|
|
|
|
|
* optimisations above obsolete.
|
|
|
|
|
*
|
|
|
|
|
* @dataProvider provideAutoloadNoFileScope
|
|
|
|
|
*/
|
|
|
|
|
public function testAutoloadNoFileScope( $file ) {
|
|
|
|
|
$parser = ( new ParserFactory )->create( ParserFactory::ONLY_PHP7 );
|
|
|
|
|
$ast = $parser->parse( file_get_contents( $file ) );
|
|
|
|
|
foreach ( $ast as $node ) {
|
|
|
|
|
if ( $node instanceof Stmt\ClassLike
|
|
|
|
|
|| $node instanceof Stmt\Namespace_
|
|
|
|
|
|| $node instanceof Stmt\Use_
|
|
|
|
|
|| $node instanceof Stmt\Nop
|
|
|
|
|
|| $node instanceof Stmt\Declare_
|
|
|
|
|
|| $node instanceof Stmt\Function_
|
|
|
|
|
) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if ( $node instanceof Stmt\Expression ) {
|
|
|
|
|
$expr = $node->expr;
|
|
|
|
|
if ( $expr instanceof Expr\FuncCall ) {
|
|
|
|
|
if ( $expr->name instanceof Node\Name ) {
|
|
|
|
|
if ( in_array( $expr->name->toString(), [
|
|
|
|
|
'class_alias',
|
|
|
|
|
'define'
|
|
|
|
|
] ) ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $expr instanceof Expr\Include_ ) {
|
|
|
|
|
if ( $expr->type === Expr\Include_::TYPE_REQUIRE_ONCE ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
} elseif ( $expr instanceof Expr\Assign ) {
|
|
|
|
|
if ( $expr->var->name === 'maintClass' ) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
$line = $node->getLine();
|
|
|
|
|
$this->assertNull( $node, "Found file scope code in $file at line $line" );
|
|
|
|
|
}
|
|
|
|
|
$this->assertTrue( true );
|
|
|
|
|
}
|
2013-10-27 05:59:01 +00:00
|
|
|
}
|