Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.33% covered (success)
93.33%
56 / 60
72.73% covered (warning)
72.73%
8 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiModuleManager
94.92% covered (success)
94.92%
56 / 59
72.73% covered (warning)
72.73%
8 / 11
32.13
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addModules
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 addModule
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
5.01
 getModule
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
7.04
 instantiateModule
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
1
 getNames
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 getNamesWithClasses
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 getClassName
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isDefined
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 getModuleGroup
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getGroups
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * Copyright © 2012 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
4 *
5 * @license GPL-2.0-or-later
6 * @file
7 * @since 1.21
8 */
9
10namespace MediaWiki\Api;
11
12use InvalidArgumentException;
13use MediaWiki\Context\ContextSource;
14use MediaWiki\MediaWikiServices;
15use UnexpectedValueException;
16use Wikimedia\ObjectFactory\ObjectFactory;
17
18/**
19 * This class holds a list of modules and handles instantiation
20 *
21 * @since 1.21
22 * @ingroup API
23 */
24class ApiModuleManager extends ContextSource {
25
26    /**
27     * @var ApiBase[]
28     */
29    private $mInstances = [];
30    /**
31     * @var null[]
32     */
33    private $mGroups = [];
34    /**
35     * @var array[]
36     */
37    private $mModules = [];
38    /**
39     * @var ObjectFactory
40     */
41    private $objectFactory;
42
43    /**
44     * Construct new module manager
45     *
46     * @param ApiBase $parentModule Parent module instance will be used during instantiation
47     * @param ObjectFactory|null $objectFactory Object factory to use when instantiating modules
48     */
49    public function __construct(
50        private readonly ApiBase $parentModule,
51        ?ObjectFactory $objectFactory = null,
52    ) {
53        $this->objectFactory = $objectFactory ?? MediaWikiServices::getInstance()->getObjectFactory();
54    }
55
56    /**
57     * Add a list of modules to the manager. Each module is described
58     * by an ObjectFactory spec.
59     *
60     * This simply calls `addModule()` for each module in `$modules`.
61     *
62     * @see self::addModule()
63     * @param array $modules A map of ModuleName => ModuleSpec
64     * @param string $group Which group modules belong to (action,format,...)
65     */
66    public function addModules( array $modules, $group ) {
67        foreach ( $modules as $name => $moduleSpec ) {
68            $this->addModule( $name, $group, $moduleSpec );
69        }
70    }
71
72    /**
73     * Add or overwrite a module in this ApiMain instance. Intended for use by extending
74     * classes who wish to add their own modules to their lexicon or override the
75     * behavior of inherent ones.
76     *
77     * ObjectFactory is used to instantiate the module when needed. The parent module
78     * (`$parentModule` from `__construct()`) and the `$name` are passed as extraArgs.
79     *
80     * @since 1.34, accepts an ObjectFactory spec as the third parameter. The old calling convention,
81     *  passing a class name as parameter #3 and an optional factory callable as parameter #4, is
82     *  deprecated.
83     * @param string $name The identifier for this module.
84     * @param string $group Name of the module group
85     * @param string|array $spec The ObjectFactory spec for instantiating the module,
86     *  or a class name to instantiate.
87     * @param callable|null $factory Callback for instantiating the module (deprecated).
88     */
89    public function addModule( string $name, string $group, $spec, $factory = null ) {
90        if ( is_string( $spec ) ) {
91            $spec = [
92                'class' => $spec
93            ];
94
95            if ( is_callable( $factory ) ) {
96                wfDeprecated( __METHOD__ . ' with $class and $factory', '1.34' );
97                $spec['factory'] = $factory;
98            }
99        } elseif ( !is_array( $spec ) ) {
100            throw new InvalidArgumentException( '$spec must be a string or an array' );
101        } elseif ( !isset( $spec['class'] ) ) {
102            throw new InvalidArgumentException( '$spec must define a class name' );
103        }
104
105        $this->mGroups[$group] = null;
106        $this->mModules[$name] = [ $group, $spec ];
107    }
108
109    /**
110     * Get module instance by name, or instantiate it if it does not exist
111     *
112     * @param string $moduleName
113     * @param string|null $group Optionally validate that the module is in a specific group
114     * @param bool $ignoreCache If true, force-creates a new instance and does not cache it
115     *
116     * @return ApiBase|null The new module instance, or null if failed
117     */
118    public function getModule( $moduleName, $group = null, $ignoreCache = false ) {
119        if ( !isset( $this->mModules[$moduleName] ) ) {
120            return null;
121        }
122
123        [ $moduleGroup, $spec ] = $this->mModules[$moduleName];
124
125        if ( $group !== null && $moduleGroup !== $group ) {
126            return null;
127        }
128
129        if ( !$ignoreCache && isset( $this->mInstances[$moduleName] ) ) {
130            // already exists
131            return $this->mInstances[$moduleName];
132        } else {
133            // new instance
134            $instance = $this->instantiateModule( $moduleName, $spec );
135
136            if ( !$ignoreCache ) {
137                // cache this instance in case it is needed later
138                $this->mInstances[$moduleName] = $instance;
139            }
140
141            return $instance;
142        }
143    }
144
145    /**
146     * Instantiate the module using the given class or factory function.
147     *
148     * @param string $name The identifier for this module.
149     * @param array $spec The ObjectFactory spec for instantiating the module.
150     *
151     * @throws UnexpectedValueException
152     * @return ApiBase
153     */
154    private function instantiateModule( $name, $spec ) {
155        return $this->objectFactory->createObject(
156            $spec,
157            [
158                'extraArgs' => [
159                    $this->parentModule,
160                    $name
161                ],
162                'assertClass' => $spec['class']
163            ]
164        );
165    }
166
167    /**
168     * Get an array of modules in a specific group or all if no group is set.
169     * @param string|null $group Optional group filter
170     * @return string[] List of module names
171     */
172    public function getNames( $group = null ) {
173        if ( $group === null ) {
174            return array_keys( $this->mModules );
175        }
176        $result = [];
177        foreach ( $this->mModules as $name => $groupAndSpec ) {
178            if ( $groupAndSpec[0] === $group ) {
179                $result[] = $name;
180            }
181        }
182
183        return $result;
184    }
185
186    /**
187     * Create an array of (moduleName => moduleClass) for a specific group or for all.
188     * @param string|null $group Name of the group to get or null for all
189     * @return array Name=>class map
190     */
191    public function getNamesWithClasses( $group = null ) {
192        $result = [];
193        foreach ( $this->mModules as $name => $groupAndSpec ) {
194            if ( $group === null || $groupAndSpec[0] === $group ) {
195                $result[$name] = $groupAndSpec[1]['class'];
196            }
197        }
198
199        return $result;
200    }
201
202    /**
203     * Returns the class name of the given module
204     *
205     * @param string $module Module name
206     * @return string|false class name or false if the module does not exist
207     * @since 1.24
208     */
209    public function getClassName( $module ) {
210        if ( isset( $this->mModules[$module] ) ) {
211            return $this->mModules[$module][1]['class'];
212        }
213
214        return false;
215    }
216
217    /**
218     * Returns true if the specific module is defined at all or in a specific group.
219     * @param string $moduleName
220     * @param string|null $group Group name to check against, or null to check all groups,
221     * @return bool True if defined
222     */
223    public function isDefined( $moduleName, $group = null ) {
224        if ( isset( $this->mModules[$moduleName] ) ) {
225            return $group === null || $this->mModules[$moduleName][0] === $group;
226        }
227
228        return false;
229    }
230
231    /**
232     * Returns the group name for the given module
233     * @param string $moduleName
234     * @return string|null Group name or null if missing
235     */
236    public function getModuleGroup( $moduleName ) {
237        if ( isset( $this->mModules[$moduleName] ) ) {
238            return $this->mModules[$moduleName][0];
239        }
240
241        return null;
242    }
243
244    /**
245     * Get a list of groups this manager contains.
246     * @return array
247     */
248    public function getGroups() {
249        return array_keys( $this->mGroups );
250    }
251}
252
253/** @deprecated class alias since 1.43 */
254class_alias( ApiModuleManager::class, 'ApiModuleManager' );