MediaWiki REL1_33
ServiceContainerTest.php
Go to the documentation of this file.
1<?php
3
7class ServiceContainerTest extends PHPUnit\Framework\TestCase {
8
9 use MediaWikiCoversValidator; // TODO this library is supposed to be independent of MediaWiki
10 use PHPUnit4And6Compat;
11
12 private function newServiceContainer( $extraArgs = [] ) {
13 return new ServiceContainer( $extraArgs );
14 }
15
16 public function testGetServiceNames() {
18 $names = $services->getServiceNames();
19
20 $this->assertInternalType( 'array', $names );
21 $this->assertEmpty( $names );
22
23 $name = 'TestService92834576';
24 $services->defineService( $name, function () {
25 return null;
26 } );
27
28 $names = $services->getServiceNames();
29 $this->assertContains( $name, $names );
30 }
31
32 public function testHasService() {
34
35 $name = 'TestService92834576';
36 $this->assertFalse( $services->hasService( $name ) );
37
38 $services->defineService( $name, function () {
39 return null;
40 } );
41
42 $this->assertTrue( $services->hasService( $name ) );
43 }
44
45 public function testGetService() {
46 $services = $this->newServiceContainer( [ 'Foo' ] );
47
48 $theService = new stdClass();
49 $name = 'TestService92834576';
50 $count = 0;
51
52 $services->defineService(
53 $name,
54 function ( $actualLocator, $extra ) use ( $services, $theService, &$count ) {
55 $count++;
56 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
57 PHPUnit_Framework_Assert::assertSame( $extra, 'Foo' );
58 return $theService;
59 }
60 );
61
62 $this->assertSame( $theService, $services->getService( $name ) );
63
64 $services->getService( $name );
65 $this->assertSame( 1, $count, 'instantiator should be called exactly once!' );
66 }
67
68 public function testGetService_fail_unknown() {
70
71 $name = 'TestService92834576';
72
73 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
74
75 $services->getService( $name );
76 }
77
78 public function testPeekService() {
80
81 $services->defineService(
82 'Foo',
83 function () {
84 return new stdClass();
85 }
86 );
87
88 $services->defineService(
89 'Bar',
90 function () {
91 return new stdClass();
92 }
93 );
94
95 // trigger instantiation of Foo
96 $services->getService( 'Foo' );
97
98 $this->assertInternalType(
99 'object',
100 $services->peekService( 'Foo' ),
101 'Peek should return the service object if it had been accessed before.'
102 );
103
104 $this->assertNull(
105 $services->peekService( 'Bar' ),
106 'Peek should return null if the service was never accessed.'
107 );
108 }
109
112
113 $name = 'TestService92834576';
114
115 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
116
117 $services->peekService( $name );
118 }
119
120 public function testDefineService() {
122
123 $theService = new stdClass();
124 $name = 'TestService92834576';
125
126 $services->defineService( $name, function ( $actualLocator ) use ( $services, $theService ) {
127 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
128 return $theService;
129 } );
130
131 $this->assertTrue( $services->hasService( $name ) );
132 $this->assertSame( $theService, $services->getService( $name ) );
133 }
134
137
138 $theService = new stdClass();
139 $name = 'TestService92834576';
140
141 $services->defineService( $name, function () use ( $theService ) {
142 return $theService;
143 } );
144
145 $this->setExpectedException( Wikimedia\Services\ServiceAlreadyDefinedException::class );
146
147 $services->defineService( $name, function () use ( $theService ) {
148 return $theService;
149 } );
150 }
151
152 public function testApplyWiring() {
154
155 $wiring = [
156 'Foo' => function () {
157 return 'Foo!';
158 },
159 'Bar' => function () {
160 return 'Bar!';
161 },
162 ];
163
164 $services->applyWiring( $wiring );
165
166 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
167 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
168 }
169
170 public function testImportWiring() {
172
173 $wiring = [
174 'Foo' => function () {
175 return 'Foo!';
176 },
177 'Bar' => function () {
178 return 'Bar!';
179 },
180 'Car' => function () {
181 return 'FUBAR!';
182 },
183 ];
184
185 $services->applyWiring( $wiring );
186
187 $services->addServiceManipulator( 'Foo', function ( $service ) {
188 return $service . '+X';
189 } );
190
191 $services->addServiceManipulator( 'Car', function ( $service ) {
192 return $service . '+X';
193 } );
194
195 $newServices = $this->newServiceContainer();
196
197 // create a service with manipulator
198 $newServices->defineService( 'Foo', function () {
199 return 'Foo!';
200 } );
201
202 $newServices->addServiceManipulator( 'Foo', function ( $service ) {
203 return $service . '+Y';
204 } );
205
206 // create a service before importing, so we can later check that
207 // existing service instances survive importWiring()
208 $newServices->defineService( 'Car', function () {
209 return 'Car!';
210 } );
211
212 // force instantiation
213 $newServices->getService( 'Car' );
214
215 // Define another service, so we can later check that extra wiring
216 // is not lost.
217 $newServices->defineService( 'Xar', function () {
218 return 'Xar!';
219 } );
220
221 // import wiring, but skip `Bar`
222 $newServices->importWiring( $services, [ 'Bar' ] );
223
224 $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
225 $this->assertSame( 'Foo!+Y+X', $newServices->getService( 'Foo' ) );
226
227 // import all wiring, but preserve existing service instance
228 $newServices->importWiring( $services );
229
230 $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
231 $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
232 $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
233 $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
234 }
235
236 public function testLoadWiringFiles() {
238
239 $wiringFiles = [
240 __DIR__ . '/TestWiring1.php',
241 __DIR__ . '/TestWiring2.php',
242 ];
243
244 $services->loadWiringFiles( $wiringFiles );
245
246 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
247 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
248 }
249
252
253 $wiringFiles = [
254 __DIR__ . '/TestWiring1.php',
255 __DIR__ . '/./TestWiring1.php',
256 ];
257
258 // loading the same file twice should fail, because
259 $this->setExpectedException( Wikimedia\Services\ServiceAlreadyDefinedException::class );
260
261 $services->loadWiringFiles( $wiringFiles );
262 }
263
264 public function testRedefineService() {
265 $services = $this->newServiceContainer( [ 'Foo' ] );
266
267 $theService1 = new stdClass();
268 $name = 'TestService92834576';
269
270 $services->defineService( $name, function () {
271 PHPUnit_Framework_Assert::fail(
272 'The original instantiator function should not get called'
273 );
274 } );
275
276 // redefine before instantiation
277 $services->redefineService(
278 $name,
279 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
280 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
281 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
282 return $theService1;
283 }
284 );
285
286 // force instantiation, check result
287 $this->assertSame( $theService1, $services->getService( $name ) );
288 }
289
291 $services = $this->newServiceContainer( [ 'Foo' ] );
292
293 $theService1 = new stdClass();
294 $name = 'TestService92834576';
295
296 $services->defineService( $name, function () {
297 return 'Foo';
298 } );
299
300 // disable the service. we should be able to redefine it anyway.
301 $services->disableService( $name );
302
303 $services->redefineService( $name, function () use ( $theService1 ) {
304 return $theService1;
305 } );
306
307 // force instantiation, check result
308 $this->assertSame( $theService1, $services->getService( $name ) );
309 }
310
313
314 $theService = new stdClass();
315 $name = 'TestService92834576';
316
317 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
318
319 $services->redefineService( $name, function () use ( $theService ) {
320 return $theService;
321 } );
322 }
323
325 $services = $this->newServiceContainer( [ 'Foo' ] );
326
327 $theService = new stdClass();
328 $name = 'TestService92834576';
329
330 $services->defineService( $name, function () {
331 return 'Foo';
332 } );
333
334 // create the service, so it can no longer be redefined
335 $services->getService( $name );
336
337 $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException::class );
338
339 $services->redefineService( $name, function () use ( $theService ) {
340 return $theService;
341 } );
342 }
343
344 public function testAddServiceManipulator() {
345 $services = $this->newServiceContainer( [ 'Foo' ] );
346
347 $theService1 = new stdClass();
348 $theService2 = new stdClass();
349 $name = 'TestService92834576';
350
351 $services->defineService(
352 $name,
353 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
354 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
355 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
356 return $theService1;
357 }
358 );
359
360 $services->addServiceManipulator(
361 $name,
362 function (
363 $theService, $actualLocator, $extra
364 ) use (
365 $services, $theService1, $theService2
366 ) {
367 PHPUnit_Framework_Assert::assertSame( $theService1, $theService );
368 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
369 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
370 return $theService2;
371 }
372 );
373
374 // force instantiation, check result
375 $this->assertSame( $theService2, $services->getService( $name ) );
376 }
377
380
381 $theService = new stdClass();
382 $name = 'TestService92834576';
383
384 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
385
386 $services->addServiceManipulator( $name, function () use ( $theService ) {
387 return $theService;
388 } );
389 }
390
392 $services = $this->newServiceContainer( [ 'Foo' ] );
393
394 $theService = new stdClass();
395 $name = 'TestService92834576';
396
397 $services->defineService( $name, function () use ( $theService ) {
398 return $theService;
399 } );
400
401 // create the service, so it can no longer be redefined
402 $services->getService( $name );
403
404 $this->setExpectedException( Wikimedia\Services\CannotReplaceActiveServiceException::class );
405
406 $services->addServiceManipulator( $name, function () {
407 return 'Foo';
408 } );
409 }
410
411 public function testDisableService() {
412 $services = $this->newServiceContainer( [ 'Foo' ] );
413
414 $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class )
415 ->getMock();
416 $destructible->expects( $this->once() )
417 ->method( 'destroy' );
418
419 $services->defineService( 'Foo', function () use ( $destructible ) {
420 return $destructible;
421 } );
422 $services->defineService( 'Bar', function () {
423 return new stdClass();
424 } );
425 $services->defineService( 'Qux', function () {
426 return new stdClass();
427 } );
428
429 // instantiate Foo and Bar services
430 $services->getService( 'Foo' );
431 $services->getService( 'Bar' );
432
433 // disable service, should call destroy() once.
434 $services->disableService( 'Foo' );
435
436 // disabled service should still be listed
437 $this->assertContains( 'Foo', $services->getServiceNames() );
438
439 // getting other services should still work
440 $services->getService( 'Bar' );
441
442 // disable non-destructible service, and not-yet-instantiated service
443 $services->disableService( 'Bar' );
444 $services->disableService( 'Qux' );
445
446 $this->assertNull( $services->peekService( 'Bar' ) );
447 $this->assertNull( $services->peekService( 'Qux' ) );
448
449 // disabled service should still be listed
450 $this->assertContains( 'Bar', $services->getServiceNames() );
451 $this->assertContains( 'Qux', $services->getServiceNames() );
452
453 $this->setExpectedException( Wikimedia\Services\ServiceDisabledException::class );
454 $services->getService( 'Qux' );
455 }
456
459
460 $theService = new stdClass();
461 $name = 'TestService92834576';
462
463 $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
464
465 $services->redefineService( $name, function () use ( $theService ) {
466 return $theService;
467 } );
468 }
469
470 public function testDestroy() {
472
473 $destructible = $this->getMockBuilder( Wikimedia\Services\DestructibleService::class )
474 ->getMock();
475 $destructible->expects( $this->once() )
476 ->method( 'destroy' );
477
478 $services->defineService( 'Foo', function () use ( $destructible ) {
479 return $destructible;
480 } );
481
482 $services->defineService( 'Bar', function () {
483 return new stdClass();
484 } );
485
486 // create the service
487 $services->getService( 'Foo' );
488
489 // destroy the container
490 $services->destroy();
491
492 $this->setExpectedException( Wikimedia\Services\ContainerDisabledException::class );
493 $services->getService( 'Bar' );
494 }
495
496}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Wikimedia\Services\ServiceContainer.
newServiceContainer( $extraArgs=[])
ServiceContainer provides a generic service to manage named services using lazy instantiation based o...
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response 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:2290
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:271
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
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...