‪TYPO3CMS  ‪main
FailsafeContainerTest.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
20 use PHPUnit\Framework\Attributes\DataProvider;
21 use PHPUnit\Framework\Attributes\Test;
22 use Psr\Container\ContainerExceptionInterface;
23 use Psr\Container\ContainerInterface;
24 use Psr\Container\NotFoundExceptionInterface;
25 use stdClass as Service;
28 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
29 
30 final class ‪FailsafeContainerTest extends UnitTestCase
31 {
32  #[Test]
33  public function ‪implementsInterface(): void
34  {
35  self::assertInstanceOf(ContainerInterface::class, new Container());
36  }
37 
38  #[Test]
39  public function ‪withString(): void
40  {
41  $providerMock = $this->createMock(ServiceProviderInterface::class);
42  $providerMock->method('getExtensions')->willReturn([]);
43  $providerMock->method('getFactories')->willReturn([
44  'param' => static function () {
45  return 'value';
46  },
47  ]);
48  $container = new Container([$providerMock]);
49 
50  self::assertTrue($container->has('param'));
51  self::assertEquals('value', $container->get('param'));
52  }
53 
54  #[DataProvider('objectFactories')]
55  #[Test]
56  public function get(mixed $factory): void
57  {
58  $providerMock = $this->createMock(ServiceProviderInterface::class);
59  $providerMock->method('getExtensions')->willReturn([]);
60  $providerMock->method('getFactories')->willReturn([
61  'service' => $factory,
62  ]);
63  $container = new Container([$providerMock]);
64 
65  self::assertTrue($container->has('service'));
66  self::assertInstanceOf(Service::class, $container->get('service'));
67  }
68 
69  #[DataProvider('objectFactories')]
70  #[Test]
71  public function ‪multipleGetServicesShouldBeEqual(mixed $factory): void
72  {
73  $providerMock = $this->createMock(ServiceProviderInterface::class);
74  $providerMock->method('getFactories')->willReturn(['service' => $factory]);
75  // A factory can also be used as extension, as it's based on the same signature
76  $providerMock->method('getExtensions')->willReturn(['extension' => $factory]);
77 
78  $container = new Container([$providerMock]);
79 
80  $serviceOne = $container->get('service');
81  $serviceTwo = $container->get('service');
82 
83  $extensionOne = $container->get('extension');
84  $extensionTwo = $container->get('extension');
85 
86  self::assertSame($serviceOne, $serviceTwo);
87  self::assertSame($extensionOne, $extensionTwo);
88  }
89 
90  #[Test]
91  public function ‪passesContainerAsParameter(): void
92  {
93  $providerMock = $this->createMock(ServiceProviderInterface::class);
94  $providerMock->method('getExtensions')->willReturn([]);
95  $providerMock->method('getFactories')->willReturn([
96  'service' => static function () {
97  return new Service();
98  },
99  'container' => static function (ContainerInterface $container) {
100  return $container;
101  },
102  ]);
103  $container = new Container([$providerMock]);
104 
105  self::assertNotSame($container, $container->get('service'));
106  self::assertSame($container, $container->get('container'));
107  }
108 
109  #[Test]
110  public function ‪nullValueEntry(): void
111  {
112  $providerMock = $this->createMock(ServiceProviderInterface::class);
113  $providerMock->method('getExtensions')->willReturn([]);
114  $providerMock->method('getFactories')->willReturn([
115  'null' => static function () {
116  return null;
117  },
118  ]);
119  $container = new Container([$providerMock]);
120 
121  self::assertTrue($container->has('null'));
122  self::assertNull($container->get('null'));
123  }
124 
125  #[Test]
127  {
128  $calledCount = 0;
129  $factory = static function () use (&$calledCount) {
130  $calledCount++;
131  return null;
132  };
133  $providerMock = $this->createMock(ServiceProviderInterface::class);
134  $providerMock->method('getExtensions')->willReturn([]);
135  $providerMock->method('getFactories')->willReturn([
136  'null' => $factory,
137  ]);
138  $container = new Container([$providerMock]);
139 
140  self::assertTrue($container->has('null'));
141  self::assertNull($container->get('null'));
142  self::assertTrue($container->has('null'));
143  self::assertNull($container->get('null'));
144  self::assertEquals(1, $calledCount);
145  }
146 
147  #[Test]
148  public function ‪has(): void
149  {
150  $providerMock = $this->createMock(ServiceProviderInterface::class);
151  $providerMock->method('getExtensions')->willReturn([]);
152  $providerMock->method('getFactories')->willReturn([
153  'service' => static function () {
154  return new Service();
155  },
156  'param' => static function () {
157  return 'value';
158  },
159  'int' => static function () {
160  return 2;
161  },
162  'bool' => static function () {
163  return false;
164  },
165  'null' => static function () {
166  return null;
167  },
168  '0' => static function () {
169  return 0;
170  },
171  ]);
172  $container = new Container([$providerMock]);
173 
174  self::assertTrue($container->has('param'));
175  self::assertTrue($container->has('service'));
176  self::assertTrue($container->has('int'));
177  self::assertTrue($container->has('bool'));
178  self::assertTrue($container->has('null'));
179  self::assertFalse($container->has('non_existent'));
180  }
181 
182  #[Test]
183  public function ‪defaultEntry(): void
184  {
185  $default = ['param' => 'value'];
186  $container = new Container([], $default);
187 
188  self::assertSame('value', $container->get('param'));
189  }
190 
191  #[Test]
192  public function ‪getValidatesKeyIsPresent(): void
193  {
194  $container = new Container();
195 
196  $this->expectException(NotFoundExceptionInterface::class);
197  $this->expectExceptionMessage('Container entry "foo" is not available.');
198  $container->get('foo');
199  }
200 
201  #[DataProvider('objectFactories')]
202  #[Test]
203  public function ‪extension(mixed $factory): void
204  {
205  $providerMockA = $this->createMock(ServiceProviderInterface::class);
206  $providerMockA->method('getFactories')->willReturn(['service' => $factory]);
207  $providerMockA->method('getExtensions')->willReturn([]);
208 
209  $providerMockB = $this->createMock(ServiceProviderInterface::class);
210  $providerMockB->method('getFactories')->willReturn([]);
211  $providerMockB->method('getExtensions')->willReturn([
212  'service' => static function (ContainerInterface $c, Service $s) {
213  $s->value = 'value';
214  return $s;
215  },
216  ]);
217  $iterator = (static function () use ($providerMockA, $providerMockB): iterable {
218  yield $providerMockA;
219  yield $providerMockB;
220  })();
221  $container = new Container($iterator);
222 
223  self::assertSame('value', $container->get('service')->value);
224  }
225 
226  #[DataProvider('objectFactories')]
227  #[Test]
228  public function ‪extendingLaterProvider(mixed $factory): void
229  {
230  $providerMockA = $this->createMock(ServiceProviderInterface::class);
231  $providerMockA->method('getFactories')->willReturn(['service' => $factory]);
232  $providerMockA->method('getExtensions')->willReturn([]);
233 
234  $providerMockB = $this->createMock(ServiceProviderInterface::class);
235  $providerMockB->method('getFactories')->willReturn([]);
236  $providerMockB->method('getExtensions')->willReturn([
237  'service' => static function (ContainerInterface $c, Service $s) {
238  $s->value = 'value';
239  return $s;
240  },
241  ]);
242  $container = new Container([$providerMockB, $providerMockA]);
243 
244  self::assertSame('value', $container->get('service')->value);
245  }
246 
247  #[DataProvider('objectFactories')]
248  #[Test]
249  public function ‪extendingOwnFactory(mixed $factory): void
250  {
251  $providerMock = $this->createMock(ServiceProviderInterface::class);
252  $providerMock->method('getFactories')->willReturn(['service' => $factory]);
253  $providerMock->method('getExtensions')->willReturn(
254  [
255  'service' => static function (ContainerInterface $c, Service $s) {
256  $s->value = 'value';
257  return $s;
258  },
259  ]
260  );
261  $container = new Container([$providerMock]);
262 
263  self::assertSame('value', $container->get('service')->value);
264  }
265 
266  #[Test]
267  public function ‪extendingNonExistingFactory(): void
268  {
269  $providerMock = $this->createMock(ServiceProviderInterface::class);
270  $providerMock->method('getFactories')->willReturn([]);
271  $providerMock->method('getExtensions')->willReturn([
272  'service' => static function (ContainerInterface $c, Service $s = null) {
273  if ($s === null) {
274  $s = new Service();
275  }
276  $s->value = 'value';
277  return $s;
278  },
279  ]);
280  $container = new Container([$providerMock]);
281 
282  self::assertSame('value', $container->get('service')->value);
283  }
284 
285  #[DataProvider('objectFactories')]
286  #[Test]
287  public function ‪multipleExtensions(mixed $factory): void
288  {
289  $providerMockA = $this->createMock(ServiceProviderInterface::class);
290  $providerMockA->method('getFactories')->willReturn(['service' => $factory]);
291  $providerMockA->method('getExtensions')->willReturn([]);
292 
293  $providerMockB = $this->createMock(ServiceProviderInterface::class);
294  $providerMockB->method('getFactories')->willReturn([]);
295  $providerMockB->method('getExtensions')->willReturn([
296  'service' => static function (ContainerInterface $c, Service $s) {
297  $s->value = '1';
298  return $s;
299  },
300  ]);
301 
302  $providerMockC = $this->createMock(ServiceProviderInterface::class);
303  $providerMockC->method('getFactories')->willReturn([]);
304  $providerMockC->method('getExtensions')->willReturn([
305  'service' => static function (ContainerInterface $c, Service $s) {
306  $s->value .= '2';
307  return $s;
308  },
309  ]);
310  $container = new Container([$providerMockA, $providerMockB, $providerMockC]);
311 
312  self::assertSame('12', $container->get('service')->value);
313  }
314 
315  #[DataProvider('objectFactories')]
316  #[Test]
317  public function ‪entryOverriding(mixed $factory): void
318  {
319  $providerMockA = $this->createMock(ServiceProviderInterface::class);
320  $providerMockA->method('getFactories')->willReturn(['service' => $factory]);
321  $providerMockA->method('getExtensions')->willReturn([]);
322 
323  $providerMockB = $this->createMock(ServiceProviderInterface::class);
324  $providerMockB->method('getExtensions')->willReturn([]);
325  $providerMockB->method('getFactories')->willReturn(['service' => static function () {
326  return 'value';
327  }]);
328 
329  $container = new Container([$providerMockA, $providerMockB]);
330 
331  self::assertNotInstanceOf(Service::class, $container->get('service'));
332  self::assertEquals('value', $container->get('service'));
333  }
334 
335  #[Test]
336  public function ‪cyclicDependency(): void
337  {
338  $providerMock = $this->createMock(ServiceProviderInterface::class);
339  $providerMock->method('getExtensions')->willReturn([]);
340  $providerMock->method('getFactories')->willReturn([
341  'A' => static function (ContainerInterface $container) {
342  return $container->get('B');
343  },
344  'B' => static function (ContainerInterface $container) {
345  return $container->get('A');
346  },
347  ]);
348 
349  $container = new Container([$providerMock]);
350 
351  $this->expectException(ContainerExceptionInterface::class);
352  $this->expectExceptionMessage('Container entry "A" is part of a cyclic dependency chain.');
353  $container->get('A');
354  }
355 
356  #[Test]
357  public function ‪cyclicDependencyRetrievedTwice(): void
358  {
359  $providerMock = $this->createMock(ServiceProviderInterface::class);
360  $providerMock->method('getExtensions')->willReturn([]);
361  $providerMock->method('getFactories')->willReturn([
362  'A' => static function (ContainerInterface $container) {
363  return $container->get('B');
364  },
365  'B' => static function (ContainerInterface $container) {
366  return $container->get('A');
367  },
368  ]);
369 
370  $container = new Container([$providerMock]);
371 
372  $this->expectException(ContainerExceptionInterface::class);
373  $this->expectExceptionMessage('Container entry "A" is part of a cyclic dependency chain.');
374  try {
375  $container->get('A');
376  } catch (ContainerExceptionInterface $e) {
377  }
378  self::assertTrue($container->has('A'));
379  $container->get('A');
380  }
381 
382  #[Test]
383  public function ‪nullContainer(): void
384  {
385  $container = new Container();
386  self::assertFalse($container->has('foo'));
387  }
388 
389  #[Test]
390  public function ‪nullContainerWithDefaultEntries(): void
391  {
392  $container = new Container([], ['foo' => 'bar']);
393  self::assertTrue($container->has('foo'));
394  }
395 
396  public static function ‪factory(): Service
397  {
398  return new Service();
399  }
400 
405  public static function ‪objectFactories(): array
406  {
407  return [
408  [
409  // Static callback
410  [self::class, 'factory'],
411  ],
412  [
413  // Closure
414  static function () {
415  return new Service();
416  },
417  ],
418  [
419  // Invokable
420  new class () {
421  public function __invoke(): Service
422  {
423  return new Service();
424  }
425  },
426  ],
427  [
428  // Non-static factory
429  [
430  new class () {
431  public function ‪factory(): Service
432  {
433  return new Service();
434  }
435  },
436  'factory',
437  ],
438  ],
439  ];
440  }
441 }
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\nullContainer
‪nullContainer()
Definition: FailsafeContainerTest.php:383
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\passesContainerAsParameter
‪passesContainerAsParameter()
Definition: FailsafeContainerTest.php:91
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\nullValueEntry
‪nullValueEntry()
Definition: FailsafeContainerTest.php:110
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\extendingNonExistingFactory
‪extendingNonExistingFactory()
Definition: FailsafeContainerTest.php:267
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\cyclicDependency
‪cyclicDependency()
Definition: FailsafeContainerTest.php:336
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\objectFactories
‪static objectFactories()
Definition: FailsafeContainerTest.php:405
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\has
‪has()
Definition: FailsafeContainerTest.php:148
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\cyclicDependencyRetrievedTwice
‪cyclicDependencyRetrievedTwice()
Definition: FailsafeContainerTest.php:357
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\multipleGetServicesShouldBeEqual
‪multipleGetServicesShouldBeEqual(mixed $factory)
Definition: FailsafeContainerTest.php:71
‪TYPO3\CMS\Core\DependencyInjection\FailsafeContainer
Definition: FailsafeContainer.php:26
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\extension
‪extension(mixed $factory)
Definition: FailsafeContainerTest.php:203
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection
Definition: ConsoleCommandPassTest.php:18
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\nullContainerWithDefaultEntries
‪nullContainerWithDefaultEntries()
Definition: FailsafeContainerTest.php:390
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\defaultEntry
‪defaultEntry()
Definition: FailsafeContainerTest.php:183
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\extendingOwnFactory
‪extendingOwnFactory(mixed $factory)
Definition: FailsafeContainerTest.php:249
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\entryOverriding
‪entryOverriding(mixed $factory)
Definition: FailsafeContainerTest.php:317
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\getValidatesKeyIsPresent
‪getValidatesKeyIsPresent()
Definition: FailsafeContainerTest.php:192
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\extendingLaterProvider
‪extendingLaterProvider(mixed $factory)
Definition: FailsafeContainerTest.php:228
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest
Definition: FailsafeContainerTest.php:31
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\nullValueEntryCallsFactoryOnlyOnce
‪nullValueEntryCallsFactoryOnlyOnce()
Definition: FailsafeContainerTest.php:126
‪TYPO3\CMS\Core\DependencyInjection\ServiceProviderInterface
Definition: ServiceProviderInterface.php:26
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\implementsInterface
‪implementsInterface()
Definition: FailsafeContainerTest.php:33
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\withString
‪withString()
Definition: FailsafeContainerTest.php:39
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\factory
‪static factory()
Definition: FailsafeContainerTest.php:396
‪TYPO3\CMS\Core\Tests\Unit\DependencyInjection\FailsafeContainerTest\multipleExtensions
‪multipleExtensions(mixed $factory)
Definition: FailsafeContainerTest.php:287