Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
44.44% covered (danger)
44.44%
24 / 54
27.27% covered (danger)
27.27%
3 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
AutoLoader
50.00% covered (danger)
50.00%
24 / 48
27.27% covered (danger)
27.27%
3 / 11
82.50
0.00% covered (danger)
0.00%
0 / 1
 registerNamespaces
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 registerClasses
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 loadFile
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 loadFiles
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 find
86.36% covered (warning)
86.36%
19 / 22
0.00% covered (danger)
0.00%
0 / 1
9.21
 autoload
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 assertTesting
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getClassFiles
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getNamespaceDirectories
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getState
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 restoreState
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7// NO_AUTOLOAD -- file scope code, can't load self
8
9/**
10 * Locations of core classes
11 * Extension classes are specified with $wgAutoloadClasses
12 */
13require_once __DIR__ . '/../autoload.php';
14
15/**
16 * @defgroup Autoload Autoload
17 */
18
19/**
20 * This initializes autoloading for MediaWiki core, extensions, and vendored libs.
21 *
22 * NOTE: This file sets up the PHP autoloader and so its stable contract is not this
23 * class, but the act of initializing spl_autoload_register and vendor.
24 * This file is widely referenced (akin to includes/Defines.php) and is therefore
25 * not renamed or moved to /includes/autoload.
26 *
27 * @since 1.7
28 * @ingroup Autoload
29 */
30class AutoLoader {
31
32    /**
33     * A mapping of namespace => file path for MediaWiki core.
34     * The namespaces must follow the PSR-4 standard for autoloading.
35     *
36     * MediaWiki core does not use PSR-4 autoloading due to performance issues,
37     * but enforce the mapping to be maintained for future use.
38     * Instead using PSR-0, class map stored in autoload.php generated via script:
39     * php maintenance/run.php generateLocalAutoload
40     *
41     * @see <https://www.php-fig.org/psr/psr-4/>
42     * @see <https://techblog.wikimedia.org/2024/01/16/web-perf-hero-mate-szabo/>
43     * @internal Only public for usage in AutoloadGenerator/AutoLoaderTest
44     * @phpcs-require-sorted-array
45     */
46    public const CORE_NAMESPACES = [
47        'MediaWiki\\' => __DIR__ . '/',
48        'MediaWiki\\Maintenance\\' => __DIR__ . '/../maintenance/includes/',
49        'Wikimedia\\' => __DIR__ . '/libs/',
50    ];
51
52    /**
53     * @var string[] Namespace (ends with \) => Path (ends with /)
54     */
55    private static $psr4Namespaces = [];
56
57    /**
58     * @var string[] Class => File
59     */
60    private static $classFiles = [];
61
62    /**
63     * Register a directory to load the classes of a given namespace from,
64     * per PSR4.
65     *
66     * @see <https://www.php-fig.org/psr/psr-4/>
67     * @since 1.39
68     * @param string[] $dirs a map of namespace (ends with \) to path (ends with /)
69     */
70    public static function registerNamespaces( array $dirs ): void {
71        self::$psr4Namespaces += $dirs;
72    }
73
74    /**
75     * Register a file to load the given class from.
76     * @since 1.39
77     *
78     * @param string[] $files a map of qualified class names to file names
79     */
80    public static function registerClasses( array $files ): void {
81        self::$classFiles += $files;
82    }
83
84    /**
85     * Load a file that declares classes, functions, or constants.
86     * The file will be loaded immediately using require_once in function scope.
87     *
88     * @note The file to be loaded MUST NOT set global variables or otherwise
89     * affect the global state. It MAY however use conditionals to determine
90     * what to declare and how, e.g. to provide polyfills.
91     *
92     * @note The file to be loaded MUST NOT assume that MediaWiki has been
93     * initialized. In particular, it MUST NOT access configuration variables
94     * or MediaWikiServices.
95     *
96     * @since 1.39
97     *
98     * @param string $file the path of the file to load.
99     */
100    public static function loadFile( string $file ): void {
101        require_once $file;
102    }
103
104    /**
105     * Batch version of loadFile()
106     *
107     * @see loadFile()
108     *
109     * @since 1.39
110     *
111     * @param string[] $files the paths of the files to load.
112     */
113    public static function loadFiles( array $files ): void {
114        foreach ( $files as $f ) {
115            self::loadFile( $f );
116        }
117    }
118
119    /**
120     * Find the file containing the given class.
121     *
122     * @param class-string $className Name of class we're looking for.
123     * @return string|null The path containing the class, not null if not found
124     */
125    public static function find( $className ): ?string {
126        global $wgAutoloadLocalClasses, $wgAutoloadClasses;
127
128        // NOTE: $wgAutoloadClasses is supported for compatibility with old-style extension
129        //       registration files.
130
131        $filename = $wgAutoloadLocalClasses[$className] ??
132            self::$classFiles[$className] ??
133            $wgAutoloadClasses[$className] ??
134            false;
135
136        if ( !$filename && str_contains( $className, '\\' ) ) {
137            // This class is namespaced, so look in the namespace map
138            $prefix = $className;
139            // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition
140            while ( ( $pos = strrpos( $prefix, '\\' ) ) !== false ) {
141                // Check to see if this namespace prefix is in the map
142                $prefix = substr( $className, 0, $pos + 1 );
143                if ( isset( self::$psr4Namespaces[$prefix] ) ) {
144                    $relativeClass = substr( $className, $pos + 1 );
145                    // Build the expected filename, and see if it exists
146                    $file = self::$psr4Namespaces[$prefix] .
147                        strtr( $relativeClass, '\\', '/' ) .
148                        '.php';
149                    if ( is_file( $file ) ) {
150                        $filename = $file;
151                        break;
152                    }
153                }
154
155                // Remove trailing separator for next iteration
156                $prefix = rtrim( $prefix, '\\' );
157            }
158        }
159
160        if ( !$filename ) {
161            // Class not found; let the next autoloader try to find it
162            return null;
163        }
164
165        // Make an absolute path, this improves performance by avoiding some stat calls
166        // Optimisation: use string offset access instead of substr
167        if ( $filename[0] !== '/' && $filename[1] !== ':' ) {
168            $filename = __DIR__ . '/../' . $filename;
169        }
170
171        return $filename;
172    }
173
174    /**
175     * autoload - take a class name and attempt to load it
176     *
177     * @param class-string $className Name of class we're looking for.
178     */
179    public static function autoload( $className ) {
180        $filename = self::find( $className );
181
182        if ( $filename !== null ) {
183            require_once $filename;
184        }
185    }
186
187    ///// Methods used during testing //////////////////////////////////////////////
188    private static function assertTesting( string $method ): void {
189        if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
190            throw new LogicException( "$method is not supported outside phpunit tests!" );
191        }
192    }
193
194    /**
195     * Returns a map of class names to file paths for testing.
196     * @note Will throw if called outside of phpunit tests!
197     * @return string[]
198     */
199    public static function getClassFiles(): array {
200        global $wgAutoloadLocalClasses, $wgAutoloadClasses;
201
202        self::assertTesting( __METHOD__ );
203
204        // NOTE: ensure the order of preference is the same as used by find().
205        return array_merge(
206            $wgAutoloadClasses,
207            self::$classFiles,
208            $wgAutoloadLocalClasses
209        );
210    }
211
212    /**
213     * Returns a map of namespace names to directories, per PSR4.
214     * @note Will throw if called outside of phpunit tests!
215     * @return string[]
216     */
217    public static function getNamespaceDirectories(): array {
218        self::assertTesting( __METHOD__ );
219        return self::$psr4Namespaces;
220    }
221
222    /**
223     * Returns an array representing the internal state of Autoloader,
224     * so it can be remembered and later restored during testing.
225     * @internal
226     * @note Will throw if called outside of phpunit tests!
227     * @return array
228     */
229    public static function getState(): array {
230        self::assertTesting( __METHOD__ );
231        return [
232            'classFiles' => self::$classFiles,
233            'psr4Namespaces' => self::$psr4Namespaces,
234        ];
235    }
236
237    /**
238     * Returns an array representing the internal state of Autoloader,
239     * so it can be remembered and later restored during testing.
240     * @internal
241     * @note Will throw if called outside of phpunit tests!
242     *
243     * @param array $state A state array returned by getState().
244     */
245    public static function restoreState( $state ): void {
246        self::assertTesting( __METHOD__ );
247
248        self::$classFiles = $state['classFiles'];
249        self::$psr4Namespaces = $state['psr4Namespaces'];
250    }
251
252}
253
254spl_autoload_register( [ 'AutoLoader', 'autoload' ] );
255
256// Load composer's autoloader if present
257if ( is_readable( __DIR__ . '/../vendor/autoload.php' ) ) {
258    require_once __DIR__ . '/../vendor/autoload.php';
259} elseif ( file_exists( __DIR__ . '/../vendor/autoload.php' ) ) {
260    die( __DIR__ . '/../vendor/autoload.php exists but is not readable' );
261}