### Problem
Instances of `SpecialPage` and `ApiBase` need to be able to use services from the container. However, there is currently not a way to inject services into these classes without making the method aware of the container. This makes testing difficult as the classes have to be tested with the container rather than mocked dependencies. This does **not** mean that SpecialPages and API modules need to become services themselves (and thus be a part of the container).
### Solution #1: Inject Container into Factory Closure
`SpecialPageFactory::getPage()` allows a Special Pages's definition to be a callable:
```
lang=php
if ( is_callable( $rec ) ) {
// Use callback to instantiate the special page
$page = $rec();
}
```
we could change this to inject the container in the callable:
```
lang=php
if ( is_callable( $rec ) ) {
// Use callback to instantiate the special page
$page = $rec( MediaWikiServices::getInstance() );
}
```
this would allow SpecialPages to be instantiated with an anonymous function (much like `includes/ServiceWiring.php`). `SpecialPageFactory::$coreList` would become a non-static and the SpecialPages with dependencies would be closures rather than strings.
An implementation would look like:
```
lang=php
'EditTags' => function( ServiceContainer $container ) {
return new \SpecialEditTags(
$container->get( 'PermissionManager')
);
}
```
Likewise in `ApiModuleManager::instantiateModule` allows a `factory` key to be defined as a callable:
```
lang=php
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!"
);
}
}
```
we could change this to inject the container into the factory:
```
lang=php
if ( $factory !== null ) {
// create instance from factory
$instance = call_user_func( $factory, $this->mParent, $name, MediaWikiServices::getInstance() );
if ( !$instance instanceof $class ) {
throw new MWException(
"The factory function for module $name did not return an instance of $class!"
);
}
}
```
again, `ApiMain::$Modules` would become non-static and an implementation could look like this:
```
lang=php
'revisiondelete' => [
'class' => ApiRevisionDelete::class,
'factory' => function ( ApiMain $main, $name, ServiceContainer $container ) : ApiRevisionDelete {
return new ApiRevisionDelete(
$main,
$name,
'',
$container->get( 'PermissionManager' )
);
},
],
```
### Solution #2: Implement an interface with a static method for injecting the container to create the object
A new interface for `SpecialPage` could be created like this:
```
lang=php
interface ContainerInjectionInterface {
public static function create( ServiceContainer $container );
}
```
then `SpecialPageFactory::getPage()` can be updated from this:
```
lang=php
elseif ( is_string( $rec ) ) {
$className = $rec;
$page = new $className;
}
```
to something like this:
```
lang=php
elseif ( is_string( $rec ) ) {
$className = $rec;
if ( is_subclass_of( $className, ContainerInjectionInterface::class ) ) {
$page = $className::create( MediaWikiServices::getInstance() );
} else {
$page = new $className;
}
}
```
an implementation might look like this:
```
lang=php
class SpecialEditTags extends UnlistedSpecialPage implements ContainerInjectionInterface {
public static function create( ServiceContainer $container ) {
return new self(
$container->get( 'PermissionManager' )
);
}
}
```
likewise, a new interface would be created for API modules:
```
lang=php
interface ApiContainerInjectionInterface {
public static function create( ServiceContainer $container, ApiMain $mainModule, $moduleName );
}
```
and `ApiModuleManager::instantiateModule()` would be updated from this:
```
lang=php
// create instance from class name
$instance = new $class( $this->mParent, $name );
```
to this:
```
lang=php
// create instance from class name
if ( is_subclass_of( $class, ApiContainerInjectionInterface::class ) ) {
$instance = $class::create( MediaWikiServices::getInstance(), $this->mParent, $name );
} else {
$instance = new $class( $this->mParent, $name );
}
```
and an implementation might look like this:
```
lang=php
class ApiRevisionDelete extends ApiBase implements ApiContainerInjectionInterface {
public static function create( ServiceContainer $container, ApiMain $mainModule, $moduleName ) {
return new self(
$mainModule,
$moduleName,
'',
$container->get( 'PermissionManager' )
);
}
}
```