* @since 1.21
*/
+use MediaWiki\MediaWikiServices;
+use Wikimedia\ObjectFactory;
+
/**
* This class holds a list of modules and handles instantiation
*
* @var array[]
*/
private $mModules = [];
+ /**
+ * @var ObjectFactory
+ */
+ private $objectFactory;
/**
* Construct new module manager
+ *
* @param ApiBase $parentModule Parent module instance will be used during instantiation
+ * @param ObjectFactory|null $objectFactory Object factory to use when instantiating modules
*/
- public function __construct( ApiBase $parentModule ) {
+ public function __construct( ApiBase $parentModule, ObjectFactory $objectFactory = null ) {
$this->mParent = $parentModule;
+ $this->objectFactory = $objectFactory ?? MediaWikiServices::getInstance()->getObjectFactory();
}
/**
* Add a list of modules to the manager. Each module is described
- * by a module spec.
- *
- * Each module spec is an associative array containing at least
- * the 'class' key for the module's class, and optionally a
- * 'factory' key for the factory function to use for the module.
+ * by an ObjectFactory spec.
*
- * That factory function will be called with two parameters,
- * the parent module (an instance of ApiBase, usually ApiMain)
- * and the name the module was registered under. The return
- * value must be an instance of the class given in the 'class'
- * field.
+ * This simply calls `addModule()` for each module in `$modules`.
*
- * For backward compatibility, the module spec may also be a
- * simple string containing the module's class name. In that
- * case, the class' constructor will be called with the parent
- * module and module name as parameters, as described above.
- *
- * Examples for defining module specs:
- *
- * @code
- * $modules['foo'] = 'ApiFoo';
- * $modules['bar'] = [
- * 'class' => ApiBar::class,
- * 'factory' => function( $main, $name ) { ... }
- * ];
- * $modules['xyzzy'] = [
- * 'class' => ApiXyzzy::class,
- * 'factory' => [ XyzzyFactory::class, 'newApiModule' ]
- * ];
- * @endcode
- *
- * @param array $modules A map of ModuleName => ModuleSpec; The ModuleSpec
- * is either a string containing the module's class name, or an associative
- * array (see above for details).
+ * @see ApiModuleManager::addModule()
+ * @param array $modules A map of ModuleName => ModuleSpec
* @param string $group Which group modules belong to (action,format,...)
*/
public function addModules( array $modules, $group ) {
foreach ( $modules as $name => $moduleSpec ) {
- if ( is_array( $moduleSpec ) ) {
- $class = $moduleSpec['class'];
- $factory = ( $moduleSpec['factory'] ?? null );
- } else {
- $class = $moduleSpec;
- $factory = null;
- }
-
- $this->addModule( $name, $group, $class, $factory );
+ $this->addModule( $name, $group, $moduleSpec );
}
}
* classes who wish to add their own modules to their lexicon or override the
* behavior of inherent ones.
*
+ * ObjectFactory is used to instantiate the module when needed. The parent module
+ * (`$parentModule` from `__construct()`) and the `$name` are passed as extraArgs.
+ *
+ * @since 1.34, accepts an ObjectFactory spec as the third parameter. The old calling convention,
+ * passing a class name as parameter #3 and an optional factory callable as parameter #4, is
+ * deprecated.
* @param string $name The identifier for this module.
* @param string $group Name of the module group
- * @param string $class The class where this module is implemented.
- * @param callable|null $factory Callback for instantiating the module.
+ * @param string|array $spec The ObjectFactory spec for instantiating the module,
+ * or a class name to instantiate.
+ * @param callable|null $factory Callback for instantiating the module (deprecated).
*
* @throws InvalidArgumentException
*/
- public function addModule( $name, $group, $class, $factory = null ) {
+ public function addModule( $name, $group, $spec, $factory = null ) {
if ( !is_string( $name ) ) {
throw new InvalidArgumentException( '$name must be a string' );
}
throw new InvalidArgumentException( '$group must be a string' );
}
- if ( !is_string( $class ) ) {
- throw new InvalidArgumentException( '$class must be a string' );
- }
+ if ( is_string( $spec ) ) {
+ $spec = [
+ 'class' => $spec
+ ];
- if ( $factory !== null && !is_callable( $factory ) ) {
- throw new InvalidArgumentException( '$factory must be a callable (or null)' );
+ if ( is_callable( $factory ) ) {
+ // Uncomment this when callers are cleaned up:
+ // wfDeprecated( __METHOD__ . ' with $class and $factory', '1.34' );
+ $spec['factory'] = $factory;
+ }
+ } elseif ( !is_array( $spec ) ) {
+ throw new InvalidArgumentException( '$spec must be a string or an array' );
+ } elseif ( !isset( $spec['class'] ) ) {
+ throw new InvalidArgumentException( '$spec must define a class name' );
}
$this->mGroups[$group] = null;
- $this->mModules[$name] = [ $group, $class, $factory ];
+ $this->mModules[$name] = [ $group, $spec ];
}
/**
return null;
}
- list( $moduleGroup, $moduleClass, $moduleFactory ) = $this->mModules[$moduleName];
+ list( $moduleGroup, $spec ) = $this->mModules[$moduleName];
if ( $group !== null && $moduleGroup !== $group ) {
return null;
return $this->mInstances[$moduleName];
} else {
// new instance
- $instance = $this->instantiateModule( $moduleName, $moduleClass, $moduleFactory );
+ $instance = $this->instantiateModule( $moduleName, $spec );
if ( !$ignoreCache ) {
// cache this instance in case it is needed later
* Instantiate the module using the given class or factory function.
*
* @param string $name The identifier for this module.
- * @param string $class The class where this module is implemented.
- * @param callable|null $factory Callback for instantiating the module.
+ * @param array $spec The ObjectFactory spec for instantiating the module.
*
- * @throws MWException
+ * @throws UnexpectedValueException
* @return ApiBase
*/
- private function instantiateModule( $name, $class, $factory = null ) {
- if ( $factory !== null ) {
- // create instance from factory
- $instance = call_user_func( $factory, $this->mParent, $name );
-
- if ( !$instance instanceof $class ) {
- throw new MWException(
- "The factory function for module $name did not return an instance of $class!"
- );
- }
- } else {
- // create instance from class name
- $instance = new $class( $this->mParent, $name );
- }
-
- return $instance;
+ private function instantiateModule( $name, $spec ) {
+ return $this->objectFactory->createObject(
+ $spec,
+ [
+ 'extraArgs' => [
+ $this->mParent,
+ $name
+ ],
+ 'assertClass' => $spec['class']
+ ]
+ );
}
/**
return array_keys( $this->mModules );
}
$result = [];
- foreach ( $this->mModules as $name => $grpCls ) {
- if ( $grpCls[0] === $group ) {
+ foreach ( $this->mModules as $name => $groupAndSpec ) {
+ if ( $groupAndSpec[0] === $group ) {
$result[] = $name;
}
}
*/
public function getNamesWithClasses( $group = null ) {
$result = [];
- foreach ( $this->mModules as $name => $grpCls ) {
- if ( $group === null || $grpCls[0] === $group ) {
- $result[$name] = $grpCls[1];
+ foreach ( $this->mModules as $name => $groupAndSpec ) {
+ if ( $group === null || $groupAndSpec[0] === $group ) {
+ $result[$name] = $groupAndSpec[1]['class'];
}
}
*/
public function getClassName( $module ) {
if ( isset( $this->mModules[$module] ) ) {
- return $this->mModules[$module][1];
+ return $this->mModules[$module][1]['class'];
}
return false;
<?php
+use MediaWiki\MediaWikiServices;
+use Wikimedia\ObjectFactory;
+
/**
* @covers ApiModuleManager
*
private function getModuleManager() {
$request = new FauxRequest();
$main = new ApiMain( $request );
- return new ApiModuleManager( $main );
+
+ return new ApiModuleManager( $main, MediaWikiServices::getInstance()->getObjectFactory() );
}
public function newApiLogin( $main, $action ) {
null,
],
- 'with factory' => [
+ 'with class and factory' => [
'login',
'action',
ApiLogin::class,
[ $this, 'newApiLogin' ],
],
- 'with closure' => [
- 'logout',
+ 'with spec (class only)' => [
+ 'login',
'action',
- ApiLogout::class,
- function ( ApiMain $main, $action ) {
- return new ApiLogout( $main, $action );
- },
+ [
+ 'class' => ApiLogin::class
+ ],
+ null,
+ ],
+
+ 'with spec' => [
+ 'login',
+ 'action',
+ [
+ 'class' => ApiLogin::class,
+ 'factory' => [ $this, 'newApiLogin' ],
+ ],
+ null,
],
+
+ 'with spec (using services)' => [
+ 'logout',
+ 'action',
+ [
+ 'class' => ApiLogout::class,
+ 'factory' => function ( ApiMain $main, $action, ObjectFactory $objectFactory ) {
+ return new ApiLogout( $main, $action );
+ },
+ 'services' => [
+ 'ObjectFactory'
+ ],
+ ],
+ null,
+ ]
];
}
/**
* @dataProvider addModuleProvider
*/
- public function testAddModule( $name, $group, $class, $factory = null ) {
+ public function testAddModule( $name, $group, $spec, $factory ) {
$moduleManager = $this->getModuleManager();
- $moduleManager->addModule( $name, $group, $class, $factory );
+ $moduleManager->addModule( $name, $group, $spec, $factory );
$this->assertTrue( $moduleManager->isDefined( $name, $group ), 'isDefined' );
$this->assertNotNull( $moduleManager->getModule( $name, $group, true ), 'getModule' );
$moduleManager->getClassName( 'nonexistentmodule' )
);
}
+
+ /**
+ * @expectedException \InvalidArgumentException
+ * @expectedExceptionMessage $spec must define a class name
+ */
+ public function testAddModuleWithIncompleteSpec() {
+ $moduleManager = $this->getModuleManager();
+
+ $moduleManager->addModule(
+ 'logout',
+ 'action',
+ [
+ 'factory' => function ( ApiMain $main, $action ) {
+ return new ApiLogout( $main, $action );
+ },
+ ]
+ );
+ }
}