MediaWiki REL1_32
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 $services->addServiceManipulator( 'Foo', function ( $service ) {
190 return $service . '+X';
191 } );
192
193 $services->addServiceManipulator( 'Car', function ( $service ) {
194 return $service . '+X';
195 } );
196
197 $newServices = $this->newServiceContainer();
198
199 // create a service with manipulator
200 $newServices->defineService( 'Foo', function () {
201 return 'Foo!';
202 } );
203
204 $newServices->addServiceManipulator( 'Foo', function ( $service ) {
205 return $service . '+Y';
206 } );
207
208 // create a service before importing, so we can later check that
209 // existing service instances survive importWiring()
210 $newServices->defineService( 'Car', function () {
211 return 'Car!';
212 } );
213
214 // force instantiation
215 $newServices->getService( 'Car' );
216
217 // Define another service, so we can later check that extra wiring
218 // is not lost.
219 $newServices->defineService( 'Xar', function () {
220 return 'Xar!';
221 } );
222
223 // import wiring, but skip `Bar`
224 $newServices->importWiring( $services, [ 'Bar' ] );
225
226 $this->assertNotContains( 'Bar', $newServices->getServiceNames(), 'Skip `Bar` service' );
227 $this->assertSame( 'Foo!+Y+X', $newServices->getService( 'Foo' ) );
228
229 // import all wiring, but preserve existing service instance
230 $newServices->importWiring( $services );
231
232 $this->assertContains( 'Bar', $newServices->getServiceNames(), 'Import all services' );
233 $this->assertSame( 'Bar!', $newServices->getService( 'Bar' ) );
234 $this->assertSame( 'Car!', $newServices->getService( 'Car' ), 'Use existing service instance' );
235 $this->assertSame( 'Xar!', $newServices->getService( 'Xar' ), 'Predefined services are kept' );
236 }
237
238 public function testLoadWiringFiles() {
240
241 $wiringFiles = [
242 __DIR__ . '/TestWiring1.php',
243 __DIR__ . '/TestWiring2.php',
244 ];
245
246 $services->loadWiringFiles( $wiringFiles );
247
248 $this->assertSame( 'Foo!', $services->getService( 'Foo' ) );
249 $this->assertSame( 'Bar!', $services->getService( 'Bar' ) );
250 }
251
254
255 $wiringFiles = [
256 __DIR__ . '/TestWiring1.php',
257 __DIR__ . '/./TestWiring1.php',
258 ];
259
260 // loading the same file twice should fail, because
261 $this->setExpectedException( MediaWiki\Services\ServiceAlreadyDefinedException::class );
262
263 $services->loadWiringFiles( $wiringFiles );
264 }
265
266 public function testRedefineService() {
267 $services = $this->newServiceContainer( [ 'Foo' ] );
268
269 $theService1 = new stdClass();
270 $name = 'TestService92834576';
271
272 $services->defineService( $name, function () {
273 PHPUnit_Framework_Assert::fail(
274 'The original instantiator function should not get called'
275 );
276 } );
277
278 // redefine before instantiation
279 $services->redefineService(
280 $name,
281 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
282 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
283 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
284 return $theService1;
285 }
286 );
287
288 // force instantiation, check result
289 $this->assertSame( $theService1, $services->getService( $name ) );
290 }
291
293 $services = $this->newServiceContainer( [ 'Foo' ] );
294
295 $theService1 = new stdClass();
296 $name = 'TestService92834576';
297
298 $services->defineService( $name, function () {
299 return 'Foo';
300 } );
301
302 // disable the service. we should be able to redefine it anyway.
303 $services->disableService( $name );
304
305 $services->redefineService( $name, function () use ( $theService1 ) {
306 return $theService1;
307 } );
308
309 // force instantiation, check result
310 $this->assertSame( $theService1, $services->getService( $name ) );
311 }
312
315
316 $theService = new stdClass();
317 $name = 'TestService92834576';
318
319 $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
320
321 $services->redefineService( $name, function () use ( $theService ) {
322 return $theService;
323 } );
324 }
325
327 $services = $this->newServiceContainer( [ 'Foo' ] );
328
329 $theService = new stdClass();
330 $name = 'TestService92834576';
331
332 $services->defineService( $name, function () {
333 return 'Foo';
334 } );
335
336 // create the service, so it can no longer be redefined
337 $services->getService( $name );
338
339 $this->setExpectedException( MediaWiki\Services\CannotReplaceActiveServiceException::class );
340
341 $services->redefineService( $name, function () use ( $theService ) {
342 return $theService;
343 } );
344 }
345
346 public function testAddServiceManipulator() {
347 $services = $this->newServiceContainer( [ 'Foo' ] );
348
349 $theService1 = new stdClass();
350 $theService2 = new stdClass();
351 $name = 'TestService92834576';
352
353 $services->defineService(
354 $name,
355 function ( $actualLocator, $extra ) use ( $services, $theService1 ) {
356 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
357 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
358 return $theService1;
359 }
360 );
361
362 $services->addServiceManipulator(
363 $name,
364 function (
365 $theService, $actualLocator, $extra
366 ) use (
367 $services, $theService1, $theService2
368 ) {
369 PHPUnit_Framework_Assert::assertSame( $theService1, $theService );
370 PHPUnit_Framework_Assert::assertSame( $services, $actualLocator );
371 PHPUnit_Framework_Assert::assertSame( 'Foo', $extra );
372 return $theService2;
373 }
374 );
375
376 // force instantiation, check result
377 $this->assertSame( $theService2, $services->getService( $name ) );
378 }
379
382
383 $theService = new stdClass();
384 $name = 'TestService92834576';
385
386 $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
387
388 $services->addServiceManipulator( $name, function () use ( $theService ) {
389 return $theService;
390 } );
391 }
392
394 $services = $this->newServiceContainer( [ 'Foo' ] );
395
396 $theService = new stdClass();
397 $name = 'TestService92834576';
398
399 $services->defineService( $name, function () use ( $theService ) {
400 return $theService;
401 } );
402
403 // create the service, so it can no longer be redefined
404 $services->getService( $name );
405
406 $this->setExpectedException( MediaWiki\Services\CannotReplaceActiveServiceException::class );
407
408 $services->addServiceManipulator( $name, function () {
409 return 'Foo';
410 } );
411 }
412
413 public function testDisableService() {
414 $services = $this->newServiceContainer( [ 'Foo' ] );
415
416 $destructible = $this->getMockBuilder( MediaWiki\Services\DestructibleService::class )
417 ->getMock();
418 $destructible->expects( $this->once() )
419 ->method( 'destroy' );
420
421 $services->defineService( 'Foo', function () use ( $destructible ) {
422 return $destructible;
423 } );
424 $services->defineService( 'Bar', function () {
425 return new stdClass();
426 } );
427 $services->defineService( 'Qux', function () {
428 return new stdClass();
429 } );
430
431 // instantiate Foo and Bar services
432 $services->getService( 'Foo' );
433 $services->getService( 'Bar' );
434
435 // disable service, should call destroy() once.
436 $services->disableService( 'Foo' );
437
438 // disabled service should still be listed
439 $this->assertContains( 'Foo', $services->getServiceNames() );
440
441 // getting other services should still work
442 $services->getService( 'Bar' );
443
444 // disable non-destructible service, and not-yet-instantiated service
445 $services->disableService( 'Bar' );
446 $services->disableService( 'Qux' );
447
448 $this->assertNull( $services->peekService( 'Bar' ) );
449 $this->assertNull( $services->peekService( 'Qux' ) );
450
451 // disabled service should still be listed
452 $this->assertContains( 'Bar', $services->getServiceNames() );
453 $this->assertContains( 'Qux', $services->getServiceNames() );
454
455 $this->setExpectedException( MediaWiki\Services\ServiceDisabledException::class );
456 $services->getService( 'Qux' );
457 }
458
461
462 $theService = new stdClass();
463 $name = 'TestService92834576';
464
465 $this->setExpectedException( MediaWiki\Services\NoSuchServiceException::class );
466
467 $services->redefineService( $name, function () use ( $theService ) {
468 return $theService;
469 } );
470 }
471
472 public function testDestroy() {
474
475 $destructible = $this->getMockBuilder( MediaWiki\Services\DestructibleService::class )
476 ->getMock();
477 $destructible->expects( $this->once() )
478 ->method( 'destroy' );
479
480 $services->defineService( 'Foo', function () use ( $destructible ) {
481 return $destructible;
482 } );
483
484 $services->defineService( 'Bar', function () {
485 return new stdClass();
486 } );
487
488 // create the service
489 $services->getService( 'Foo' );
490
491 // destroy the container
492 $services->destroy();
493
494 $this->setExpectedException( MediaWiki\Services\ContainerDisabledException::class );
495 $services->getService( 'Bar' );
496 }
497
498}
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 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:2335
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.