MediaWiki  master
ServiceContainerTest.php
Go to the documentation of this file.
1 <?php
3 
8 
9  use MediaWikiCoversValidator; // TODO this library is supposed to be independent of MediaWiki
11 
12  private function newServiceContainer( $extraArgs = [] ) {
13  return new ServiceContainer( $extraArgs );
14  }
15 
16  public function testGetServiceNames() {
17  $services = $this->newServiceContainer();
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() {
33  $services = $this->newServiceContainer();
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() {
69  $services = $this->newServiceContainer();
70 
71  $name = 'TestService92834576';
72 
73  $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
74 
75  $services->getService( $name );
76  }
77 
78  public function testPeekService() {
79  $services = $this->newServiceContainer();
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 
110  public function testPeekService_fail_unknown() {
111  $services = $this->newServiceContainer();
112 
113  $name = 'TestService92834576';
114 
115  $this->setExpectedException( Wikimedia\Services\NoSuchServiceException::class );
116 
117  $services->peekService( $name );
118  }
119 
120  public function testDefineService() {
121  $services = $this->newServiceContainer();
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 
136  $services = $this->newServiceContainer();
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() {
153  $services = $this->newServiceContainer();
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() {
171  $services = $this->newServiceContainer();
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() {
237  $services = $this->newServiceContainer();
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 
251  $services = $this->newServiceContainer();
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 
290  public function testRedefineService_disabled() {
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 
312  $services = $this->newServiceContainer();
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 
379  $services = $this->newServiceContainer();
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 
458  $services = $this->newServiceContainer();
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() {
471  $services = $this->newServiceContainer();
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 }
Wikimedia\Services\ServiceContainer.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...
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:2217
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
newServiceContainer( $extraArgs=[])
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:35
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
ServiceContainer provides a generic service to manage named services using lazy instantiation based o...