MediaWiki REL1_31
ServiceContainerTest.php
Go to the documentation of this file.
1<?php
3
9class ServiceContainerTest extends PHPUnit\Framework\TestCase {
10
11 use MediaWikiCoversValidator;
12 use PHPUnit4And6Compat;
13
14 private function newServiceContainer( $extraArgs = [] ) {
15 return new ServiceContainer( $extraArgs );
16 }
17
18 public function testGetServiceNames() {
20 $names = $services->getServiceNames();
21
22 $this->assertInternalType( 'array', $names );
23 $this->assertEmpty( $names );
24
25 $name = 'TestService92834576';
26 $services->defineService( $name, function () {
27 return null;
28 } );
29
30 $names = $services->getServiceNames();
31 $this->assertContains( $name, $names );
32 }
33
34 public function testHasService() {
36
37 $name = 'TestService92834576';
38 $this->assertFalse( $services->hasService( $name ) );
39
40 $services->defineService( $name, function () {
41 return null;
42 } );
43
44 $this->assertTrue( $services->hasService( $name ) );
45 }
46
47 public function testGetService() {
48 $services = $this->newServiceContainer( [ 'Foo' ] );
49
50 $theService = new stdClass();
51 $name = 'TestService92834576';
52 $count = 0;
53
54 $services->defineService(
55 $name,
56 function ( $actualLocator, $extra ) use ( $services, $theService, &$count ) {
57 $count++;
58 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
59 PHPUnit_Framework_Assert::assertSame( $extra, 'Foo' );
60 return $theService;
61 }
62 );
63
64 $this->assertSame( $theService, $services->getService( $name ) );
65
66 $services->getService( $name );
67 $this->assertSame( 1, $count, 'instantiator should be called exactly once!' );
68 }
69
70 public function testGetService_fail_unknown() {
72
73 $name = 'TestService92834576';
74
75 $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
76
77 $services->getService( $name );
78 }
79
80 public function testPeekService() {
82
83 $services->defineService(
84 'Foo',
85 function () {
86 return new stdClass();
87 }
88 );
89
90 $services->defineService(
91 'Bar',
92 function () {
93 return new stdClass();
94 }
95 );
96
97 // trigger instantiation of Foo
98 $services->getService( 'Foo' );
99
100 $this->assertInternalType(
101 'object',
102 $services->peekService( 'Foo' ),
103 'Peek should return the service object if it had been accessed before.'
104 );
105
106 $this->assertNull(
107 $services->peekService( 'Bar' ),
108 'Peek should return null if the service was never accessed.'
109 );
110 }
111
114
115 $name = 'TestService92834576';
116
117 $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
118
119 $services->peekService( $name );
120 }
121
122 public function testDefineService() {
124
125 $theService = new stdClass();
126 $name = 'TestService92834576';
127
128 $services->defineService( $name, function ( $actualLocator ) use ( $services, $theService ) {
129 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
130 return $theService;
131 } );
132
133 $this->assertTrue( $services->hasService( $name ) );
134 $this->assertSame( $theService, $services->getService( $name ) );
135 }
136
139
140 $theService = new stdClass();
141 $name = 'TestService92834576';
142
143 $services->defineService( $name, function () use ( $theService ) {
144 return $theService;
145 } );
146
147 $this->setExpectedException( MediaWiki\Services\ServiceAlreadyDefinedException::class );
148
149 $services->defineService( $name, function () use ( $theService ) {
150 return $theService;
151 } );
152 }
153
154 public function testApplyWiring() {
156
157 $wiring = [
158 'Foo' => function () {
159 return 'Foo!';
160 },
161 'Bar' => function () {
162 return 'Bar!';
163 },
164 ];
165
166 $services->applyWiring( $wiring );
167
168 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
169 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
170 }
171
172 public function testImportWiring() {
174
175 $wiring = [
176 'Foo' => function () {
177 return 'Foo!';
178 },
179 'Bar' => function () {
180 return 'Bar!';
181 },
182 'Car' => function () {
183 return 'FUBAR!';
184 },
185 ];
186
187 $services->applyWiring( $wiring );
188
189 $newServices = $this->newServiceContainer();
190
191 // define a service before importing, so we can later check that
192 // existing service instances survive importWiring()
193 $newServices->defineService( 'Car', function () {
194 return 'Car!';
195 } );
196
197 // force instantiation
198 $newServices->getService( 'Car' );
199
200 // Define another service, so we can later check that extra wiring
201 // is not lost.
202 $newServices->defineService( 'Xar', function () {
203 return 'Xar!';
204 } );
205
206 // import wiring, but skip `Bar`
207 $newServices->importWiring( $services, [ 'Bar' ] );
208
209 $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
210 $this->assertSame( 'Foo!', $newServices->getService( 'Foo' ) );
211
212 // import all wiring, but preserve existing service instance
213 $newServices->importWiring( $services );
214
215 $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
216 $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
217 $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
218 $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
219 }
220
221 public function testLoadWiringFiles() {
223
224 $wiringFiles = [
225 __DIR__ . '/TestWiring1.php',
226 __DIR__ . '/TestWiring2.php',
227 ];
228
229 $services->loadWiringFiles( $wiringFiles );
230
231 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
232 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
233 }
234
237
238 $wiringFiles = [
239 __DIR__ . '/TestWiring1.php',
240 __DIR__ . '/./TestWiring1.php',
241 ];
242
243 // loading the same file twice should fail, because
244 $this->setExpectedException( MediaWiki\Services\ServiceAlreadyDefinedException::class );
245
246 $services->loadWiringFiles( $wiringFiles );
247 }
248
249 public function testRedefineService() {
250 $services = $this->newServiceContainer( [ 'Foo' ] );
251
252 $theService1 = new stdClass();
253 $name = 'TestService92834576';
254
255 $services->defineService( $name, function () {
256 PHPUnit_Framework_Assert::fail(
257 'The original instantiator function should not get called'
258 );
259 } );
260
261 // redefine before instantiation
262 $services->redefineService(
263 $name,
264 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
265 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
266 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
267 return $theService1;
268 }
269 );
270
271 // force instantiation, check result
272 $this->assertSame( $theService1, $services->getService( $name ) );
273 }
274
276 $services = $this->newServiceContainer( [ 'Foo' ] );
277
278 $theService1 = new stdClass();
279 $name = 'TestService92834576';
280
281 $services->defineService( $name, function () {
282 return 'Foo';
283 } );
284
285 // disable the service. we should be able to redefine it anyway.
286 $services->disableService( $name );
287
288 $services->redefineService( $name, function () use ( $theService1 ) {
289 return $theService1;
290 } );
291
292 // force instantiation, check result
293 $this->assertSame( $theService1, $services->getService( $name ) );
294 }
295
298
299 $theService = new stdClass();
300 $name = 'TestService92834576';
301
302 $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
303
304 $services->redefineService( $name, function () use ( $theService ) {
305 return $theService;
306 } );
307 }
308
310 $services = $this->newServiceContainer( [ 'Foo' ] );
311
312 $theService = new stdClass();
313 $name = 'TestService92834576';
314
315 $services->defineService( $name, function () {
316 return 'Foo';
317 } );
318
319 // create the service, so it can no longer be redefined
320 $services->getService( $name );
321
322 $this->setExpectedException( MediaWiki\Services\CannotReplaceActiveServiceException::class );
323
324 $services->redefineService( $name, function () use ( $theService ) {
325 return $theService;
326 } );
327 }
328
329 public function testDisableService() {
330 $services = $this->newServiceContainer( [ 'Foo' ] );
331
332 $destructible = $this->getMockBuilder( MediaWiki\Services\DestructibleService::class )
333 ->getMock();
334 $destructible->expects( $this->once() )
335 ->method( 'destroy' );
336
337 $services->defineService( 'Foo', function () use ( $destructible ) {
338 return $destructible;
339 } );
340 $services->defineService( 'Bar', function () {
341 return new stdClass();
342 } );
343 $services->defineService( 'Qux', function () {
344 return new stdClass();
345 } );
346
347 // instantiate Foo and Bar services
348 $services->getService( 'Foo' );
349 $services->getService( 'Bar' );
350
351 // disable service, should call destroy() once.
352 $services->disableService( 'Foo' );
353
354 // disabled service should still be listed
355 $this->assertContains( 'Foo', $services->getServiceNames() );
356
357 // getting other services should still work
358 $services->getService( 'Bar' );
359
360 // disable non-destructible service, and not-yet-instantiated service
361 $services->disableService( 'Bar' );
362 $services->disableService( 'Qux' );
363
364 $this->assertNull( $services->peekService( 'Bar' ) );
365 $this->assertNull( $services->peekService( 'Qux' ) );
366
367 // disabled service should still be listed
368 $this->assertContains( 'Bar', $services->getServiceNames() );
369 $this->assertContains( 'Qux', $services->getServiceNames() );
370
371 $this->setExpectedException( MediaWiki\Services\ServiceDisabledException::class );
372 $services->getService( 'Qux' );
373 }
374
377
378 $theService = new stdClass();
379 $name = 'TestService92834576';
380
381 $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
382
383 $services->redefineService( $name, function () use ( $theService ) {
384 return $theService;
385 } );
386 }
387
388 public function testDestroy() {
390
391 $destructible = $this->getMockBuilder( MediaWiki\Services\DestructibleService::class )
392 ->getMock();
393 $destructible->expects( $this->once() )
394 ->method( 'destroy' );
395
396 $services->defineService( 'Foo', function () use ( $destructible ) {
397 return $destructible;
398 } );
399
400 $services->defineService( 'Bar', function () {
401 return new stdClass();
402 } );
403
404 // create the service
405 $services->getService( 'Foo' );
406
407 // destroy the container
408 $services->destroy();
409
410 $this->setExpectedException( MediaWiki\Services\ContainerDisabledException::class );
411 $services->getService( 'Bar' );
412 }
413
414}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
ServiceContainer provides a generic service to manage named services using lazy instantiation based o...
MediaWiki\Services\ServiceContainer.
newServiceContainer( $extraArgs=[])
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition hooks.txt:2273
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
A helper class for throttling authentication attempts.