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