ApiModuleManager.php 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. <?php
  2. /**
  3. *
  4. *
  5. * Created on Dec 27, 2012
  6. *
  7. * Copyright © 2012 Yuri Astrakhan "<Firstname><Lastname>@gmail.com"
  8. *
  9. * This program is free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation; either version 2 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License along
  20. * with this program; if not, write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. * http://www.gnu.org/copyleft/gpl.html
  23. *
  24. * @file
  25. * @since 1.21
  26. */
  27. /**
  28. * This class holds a list of modules and handles instantiation
  29. *
  30. * @since 1.21
  31. * @ingroup API
  32. */
  33. class ApiModuleManager extends ContextSource {
  34. /**
  35. * @var ApiBase
  36. */
  37. private $mParent;
  38. /**
  39. * @var ApiBase[]
  40. */
  41. private $mInstances = [];
  42. /**
  43. * @var null[]
  44. */
  45. private $mGroups = [];
  46. /**
  47. * @var array[]
  48. */
  49. private $mModules = [];
  50. /**
  51. * Construct new module manager
  52. * @param ApiBase $parentModule Parent module instance will be used during instantiation
  53. */
  54. public function __construct( ApiBase $parentModule ) {
  55. $this->mParent = $parentModule;
  56. }
  57. /**
  58. * Add a list of modules to the manager. Each module is described
  59. * by a module spec.
  60. *
  61. * Each module spec is an associative array containing at least
  62. * the 'class' key for the module's class, and optionally a
  63. * 'factory' key for the factory function to use for the module.
  64. *
  65. * That factory function will be called with two parameters,
  66. * the parent module (an instance of ApiBase, usually ApiMain)
  67. * and the name the module was registered under. The return
  68. * value must be an instance of the class given in the 'class'
  69. * field.
  70. *
  71. * For backward compatibility, the module spec may also be a
  72. * simple string containing the module's class name. In that
  73. * case, the class' constructor will be called with the parent
  74. * module and module name as parameters, as described above.
  75. *
  76. * Examples for defining module specs:
  77. *
  78. * @code
  79. * $modules['foo'] = 'ApiFoo';
  80. * $modules['bar'] = [
  81. * 'class' => 'ApiBar',
  82. * 'factory' => function( $main, $name ) { ... }
  83. * ];
  84. * $modules['xyzzy'] = [
  85. * 'class' => 'ApiXyzzy',
  86. * 'factory' => [ 'XyzzyFactory', 'newApiModule' ]
  87. * ];
  88. * @endcode
  89. *
  90. * @param array $modules A map of ModuleName => ModuleSpec; The ModuleSpec
  91. * is either a string containing the module's class name, or an associative
  92. * array (see above for details).
  93. * @param string $group Which group modules belong to (action,format,...)
  94. */
  95. public function addModules( array $modules, $group ) {
  96. foreach ( $modules as $name => $moduleSpec ) {
  97. if ( is_array( $moduleSpec ) ) {
  98. $class = $moduleSpec['class'];
  99. $factory = ( isset( $moduleSpec['factory'] ) ? $moduleSpec['factory'] : null );
  100. } else {
  101. $class = $moduleSpec;
  102. $factory = null;
  103. }
  104. $this->addModule( $name, $group, $class, $factory );
  105. }
  106. }
  107. /**
  108. * Add or overwrite a module in this ApiMain instance. Intended for use by extending
  109. * classes who wish to add their own modules to their lexicon or override the
  110. * behavior of inherent ones.
  111. *
  112. * @param string $name The identifier for this module.
  113. * @param string $group Name of the module group
  114. * @param string $class The class where this module is implemented.
  115. * @param callable|null $factory Callback for instantiating the module.
  116. *
  117. * @throws InvalidArgumentException
  118. */
  119. public function addModule( $name, $group, $class, $factory = null ) {
  120. if ( !is_string( $name ) ) {
  121. throw new InvalidArgumentException( '$name must be a string' );
  122. }
  123. if ( !is_string( $group ) ) {
  124. throw new InvalidArgumentException( '$group must be a string' );
  125. }
  126. if ( !is_string( $class ) ) {
  127. throw new InvalidArgumentException( '$class must be a string' );
  128. }
  129. if ( $factory !== null && !is_callable( $factory ) ) {
  130. throw new InvalidArgumentException( '$factory must be a callable (or null)' );
  131. }
  132. $this->mGroups[$group] = null;
  133. $this->mModules[$name] = [ $group, $class, $factory ];
  134. }
  135. /**
  136. * Get module instance by name, or instantiate it if it does not exist
  137. *
  138. * @param string $moduleName Module name
  139. * @param string $group Optionally validate that the module is in a specific group
  140. * @param bool $ignoreCache If true, force-creates a new instance and does not cache it
  141. *
  142. * @return ApiBase|null The new module instance, or null if failed
  143. */
  144. public function getModule( $moduleName, $group = null, $ignoreCache = false ) {
  145. if ( !isset( $this->mModules[$moduleName] ) ) {
  146. return null;
  147. }
  148. list( $moduleGroup, $moduleClass, $moduleFactory ) = $this->mModules[$moduleName];
  149. if ( $group !== null && $moduleGroup !== $group ) {
  150. return null;
  151. }
  152. if ( !$ignoreCache && isset( $this->mInstances[$moduleName] ) ) {
  153. // already exists
  154. return $this->mInstances[$moduleName];
  155. } else {
  156. // new instance
  157. $instance = $this->instantiateModule( $moduleName, $moduleClass, $moduleFactory );
  158. if ( !$ignoreCache ) {
  159. // cache this instance in case it is needed later
  160. $this->mInstances[$moduleName] = $instance;
  161. }
  162. return $instance;
  163. }
  164. }
  165. /**
  166. * Instantiate the module using the given class or factory function.
  167. *
  168. * @param string $name The identifier for this module.
  169. * @param string $class The class where this module is implemented.
  170. * @param callable|null $factory Callback for instantiating the module.
  171. *
  172. * @throws MWException
  173. * @return ApiBase
  174. */
  175. private function instantiateModule( $name, $class, $factory = null ) {
  176. if ( $factory !== null ) {
  177. // create instance from factory
  178. $instance = call_user_func( $factory, $this->mParent, $name );
  179. if ( !$instance instanceof $class ) {
  180. throw new MWException(
  181. "The factory function for module $name did not return an instance of $class!"
  182. );
  183. }
  184. } else {
  185. // create instance from class name
  186. $instance = new $class( $this->mParent, $name );
  187. }
  188. return $instance;
  189. }
  190. /**
  191. * Get an array of modules in a specific group or all if no group is set.
  192. * @param string $group Optional group filter
  193. * @return array List of module names
  194. */
  195. public function getNames( $group = null ) {
  196. if ( $group === null ) {
  197. return array_keys( $this->mModules );
  198. }
  199. $result = [];
  200. foreach ( $this->mModules as $name => $grpCls ) {
  201. if ( $grpCls[0] === $group ) {
  202. $result[] = $name;
  203. }
  204. }
  205. return $result;
  206. }
  207. /**
  208. * Create an array of (moduleName => moduleClass) for a specific group or for all.
  209. * @param string $group Name of the group to get or null for all
  210. * @return array Name=>class map
  211. */
  212. public function getNamesWithClasses( $group = null ) {
  213. $result = [];
  214. foreach ( $this->mModules as $name => $grpCls ) {
  215. if ( $group === null || $grpCls[0] === $group ) {
  216. $result[$name] = $grpCls[1];
  217. }
  218. }
  219. return $result;
  220. }
  221. /**
  222. * Returns the class name of the given module
  223. *
  224. * @param string $module Module name
  225. * @return string|bool class name or false if the module does not exist
  226. * @since 1.24
  227. */
  228. public function getClassName( $module ) {
  229. if ( isset( $this->mModules[$module] ) ) {
  230. return $this->mModules[$module][1];
  231. }
  232. return false;
  233. }
  234. /**
  235. * Returns true if the specific module is defined at all or in a specific group.
  236. * @param string $moduleName Module name
  237. * @param string $group Group name to check against, or null to check all groups,
  238. * @return bool True if defined
  239. */
  240. public function isDefined( $moduleName, $group = null ) {
  241. if ( isset( $this->mModules[$moduleName] ) ) {
  242. return $group === null || $this->mModules[$moduleName][0] === $group;
  243. }
  244. return false;
  245. }
  246. /**
  247. * Returns the group name for the given module
  248. * @param string $moduleName
  249. * @return string|null Group name or null if missing
  250. */
  251. public function getModuleGroup( $moduleName ) {
  252. if ( isset( $this->mModules[$moduleName] ) ) {
  253. return $this->mModules[$moduleName][0];
  254. }
  255. return null;
  256. }
  257. /**
  258. * Get a list of groups this manager contains.
  259. * @return array
  260. */
  261. public function getGroups() {
  262. return array_keys( $this->mGroups );
  263. }
  264. }