Merge "Improve HTMLSubmitField return value"
[lhc/web/wiklou.git] / includes / Services / ServiceContainer.php
1 <?php
2 namespace MediaWiki\Services;
3
4 use InvalidArgumentException;
5 use RuntimeException;
6 use Wikimedia\Assert\Assert;
7
8 /**
9 * Generic service container.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License along
22 * with this program; if not, write to the Free Software Foundation, Inc.,
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 * http://www.gnu.org/copyleft/gpl.html
25 *
26 * @file
27 *
28 * @since 1.27
29 */
30
31 /**
32 * ServiceContainer provides a generic service to manage named services using
33 * lazy instantiation based on instantiator callback functions.
34 *
35 * Services managed by an instance of ServiceContainer may or may not implement
36 * a common interface.
37 *
38 * @note When using ServiceContainer to manage a set of services, consider
39 * creating a wrapper or a subclass that provides access to the services via
40 * getter methods with more meaningful names and more specific return type
41 * declarations.
42 *
43 * @see docs/injection.txt for an overview of using dependency injection in the
44 * MediaWiki code base.
45 */
46 class ServiceContainer {
47
48 /**
49 * @var object[]
50 */
51 private $services = [];
52
53 /**
54 * @var callable[]
55 */
56 private $serviceInstantiators = [];
57
58 /**
59 * @var array
60 */
61 private $extraInstantiationParams;
62
63 /**
64 * @param array $extraInstantiationParams Any additional parameters to be passed to the
65 * instantiator function when creating a service. This is typically used to provide
66 * access to additional ServiceContainers or Config objects.
67 */
68 public function __construct( array $extraInstantiationParams = [] ) {
69 $this->extraInstantiationParams = $extraInstantiationParams;
70 }
71
72 /**
73 * @param array $wiringFiles A list of PHP files to load wiring information from.
74 * Each file is loaded using PHP's include mechanism. Each file is expected to
75 * return an associative array that maps service names to instantiator functions.
76 */
77 public function loadWiringFiles( array $wiringFiles ) {
78 foreach ( $wiringFiles as $file ) {
79 // the wiring file is required to return an array of instantiators.
80 $wiring = require $file;
81
82 Assert::postcondition(
83 is_array( $wiring ),
84 "Wiring file $file is expected to return an array!"
85 );
86
87 $this->applyWiring( $wiring );
88 }
89 }
90
91 /**
92 * Registers multiple services (aka a "wiring").
93 *
94 * @param array $serviceInstantiators An associative array mapping service names to
95 * instantiator functions.
96 */
97 public function applyWiring( array $serviceInstantiators ) {
98 Assert::parameterElementType( 'callable', $serviceInstantiators, '$serviceInstantiators' );
99
100 foreach ( $serviceInstantiators as $name => $instantiator ) {
101 $this->defineService( $name, $instantiator );
102 }
103 }
104
105 /**
106 * Returns true if a service is defined for $name, that is, if a call to getService( $name )
107 * would return a service instance.
108 *
109 * @param string $name
110 *
111 * @return bool
112 */
113 public function hasService( $name ) {
114 return isset( $this->serviceInstantiators[$name] );
115 }
116
117 /**
118 * @return string[]
119 */
120 public function getServiceNames() {
121 return array_keys( $this->serviceInstantiators );
122 }
123
124 /**
125 * Define a new service. The service must not be known already.
126 *
127 * @see getService().
128 * @see replaceService().
129 *
130 * @param string $name The name of the service to register, for use with getService().
131 * @param callable $instantiator Callback that returns a service instance.
132 * Will be called with this MediaWikiServices instance as the only parameter.
133 * Any extra instantiation parameters provided to the constructor will be
134 * passed as subsequent parameters when invoking the instantiator.
135 *
136 * @throws RuntimeException if there is already a service registered as $name.
137 */
138 public function defineService( $name, callable $instantiator ) {
139 Assert::parameterType( 'string', $name, '$name' );
140
141 if ( $this->hasService( $name ) ) {
142 throw new RuntimeException( 'Service already defined: ' . $name );
143 }
144
145 $this->serviceInstantiators[$name] = $instantiator;
146 }
147
148 /**
149 * Replace an already defined service.
150 *
151 * @see defineService().
152 *
153 * @note This causes any previously instantiated instance of the service to be discarded.
154 *
155 * @param string $name The name of the service to register.
156 * @param callable $instantiator Callback function that returns a service instance.
157 * Will be called with this MediaWikiServices instance as the only parameter.
158 * The instantiator must return a service compatible with the originally defined service.
159 * Any extra instantiation parameters provided to the constructor will be
160 * passed as subsequent parameters when invoking the instantiator.
161 *
162 * @throws RuntimeException if $name is not a known service.
163 */
164 public function redefineService( $name, callable $instantiator ) {
165 Assert::parameterType( 'string', $name, '$name' );
166
167 if ( !$this->hasService( $name ) ) {
168 throw new RuntimeException( 'Service not defined: ' . $name );
169 }
170
171 if ( isset( $this->services[$name] ) ) {
172 throw new RuntimeException( 'Cannot redefine a service that is already in use: ' . $name );
173 }
174
175 $this->serviceInstantiators[$name] = $instantiator;
176 }
177
178 /**
179 * Returns a service object of the kind associated with $name.
180 * Services instances are instantiated lazily, on demand.
181 * This method may or may not return the same service instance
182 * when called multiple times with the same $name.
183 *
184 * @note Rather than calling this method directly, it is recommended to provide
185 * getters with more meaningful names and more specific return types, using
186 * a subclass or wrapper.
187 *
188 * @see redefineService().
189 *
190 * @param string $name The service name
191 *
192 * @throws InvalidArgumentException if $name is not a known service.
193 * @return object The service instance
194 */
195 public function getService( $name ) {
196 if ( !isset( $this->services[$name] ) ) {
197 $this->services[$name] = $this->createService( $name );
198 }
199
200 return $this->services[$name];
201 }
202
203 /**
204 * @param string $name
205 *
206 * @throws InvalidArgumentException if $name is not a known service.
207 * @return object
208 */
209 private function createService( $name ) {
210 if ( isset( $this->serviceInstantiators[$name] ) ) {
211 $service = call_user_func_array(
212 $this->serviceInstantiators[$name],
213 array_merge( [ $this ], $this->extraInstantiationParams )
214 );
215 } else {
216 throw new InvalidArgumentException( 'Unknown service: ' . $name );
217 }
218
219 return $service;
220 }
221
222 }