Passed
Push — master ( 3ad7fe...719238 )
by Alexander
01:23
created

InjectorTest   A

Complexity

Total Complexity 4

Size/Duplication

Total Lines 673
Duplicated Lines 0 %

Importance

Changes 9
Bugs 0 Features 1
Metric Value
eloc 258
c 9
b 0
f 1
dl 0
loc 673
rs 10
wmc 4

95 Methods

Rating   Name   Duplication   Size   Complexity  
A testInvokeStatic() 0 7 1
testInvokeAnonymousClass() 0 14 ?
A hp$0 ➔ testExtendedArgumentsWithOneCustomNamedParameter2() 0 14 1
A hp$0 ➔ testWithNullableOptionalArgumentThatNull() 0 9 1
A hp$0 ➔ testVariadicStringArgumentWithUnnamedStringsParams() 0 9 1
testWithNullableArgumentAndEmptyContainer() 0 9 ?
A hp$0 ➔ testMissingRequiredNotTypedParameter() 0 12 1
A hp$0 ➔ testNullableVariadicArgument() 0 9 1
A hp$0 ➔ testInvokeAnonymousClass() 0 14 1
A testInvokeCallableArray() 0 9 1
A hp$0 ➔ testWithNullableArgumentAndEmptyContainer() 0 9 1
A hp$0 ➔ testTwoEqualCustomArgumentsWithOneCustomNamedParameter() 0 12 1
testVariadicMixedArgumentWithMixedParams() 0 12 ?
testVariadicArgumentUnnamedParams() 0 12 ?
testNullableVariadicArgument() 0 9 ?
testWithNullableOptionalArgumentThatNull() 0 9 ?
testInvokeWithoutArguments() 0 9 ?
testWithNullableArgument() 0 9 ?
testVariadicStringArgumentWithUnnamedStringsParams() 0 9 ?
A hp$0 ➔ testVariadicArgumentUnnamedParams() 0 12 1
A hp$0 ➔ testTwoEqualCustomArgumentsWithOneCustom() 0 12 1
testTwoEqualCustomArgumentsWithOneCustom() 0 12 ?
testTwoEqualCustomArgumentsWithOneCustomNamedParameter() 0 12 ?
testNotFoundException() 0 12 ?
testMissingRequiredNotTypedParameter() 0 12 ?
A hp$0 ➔ testVariadicMixedArgumentWithMixedParams() 0 12 1
testCustomDependency() 0 13 ?
A hp$0 ➔ testAloneScalarVariadicArgumentAnsNamedParam() 0 9 1
testWithNullableScalarArgument() 0 9 ?
A hp$0 ➔ testWithNullableScalarArgument() 0 9 1
A hp$0 ➔ testWithNullableArgument() 0 9 1
A testInvokeClosure() 0 9 1
A hp$0 ➔ setEngine() 0 3 1
testExtendedArgumentsWithOneCustomNamedParameter2() 0 14 ?
A hp$0 ➔ testWithNullableOptionalArgument() 0 9 1
A hp$0 ➔ testNotFoundException() 0 12 1
testAloneScalarVariadicArgumentAnsNamedParam() 0 9 ?
A hp$0 ➔ testInvokeWithoutArguments() 0 9 1
testWithNullableOptionalArgument() 0 9 ?
A hp$0 ➔ testMissingRequiredTypedParameter() 0 12 1
A hp$0 ➔ testCustomDependency() 0 13 1
testMissingRequiredTypedParameter() 0 12 ?
A hp$0 ➔ testMakeWithoutConstructor() 0 7 1
A hp$0 ➔ testInvokeWithRequiredObjectTypeWithoutInstance() 0 8 1
testMakeWithCustomParam() 0 11 ?
A hp$0 ➔ testMakeWithCustomParam() 0 11 1
A hp$0 ➔ testMakeWithVariadicFromArguments() 0 8 1
testMakeInternalClass() 0 5 ?
A hp$0 ➔ testAppendingUnusedParams() 0 13 1
testInvokeable() 0 5 ?
testWrongNamedParam() 0 9 ?
A hp$0 ➔ testMakeWithPrivateConstructor() 0 8 1
A hp$0 ➔ testCallableArgumentWithUnnamedType() 0 9 1
testMakeWithInvalidCustomParam() 0 7 ?
testInvokeReferencedArguments() 0 40 ?
A hp$0 ➔ testWrongNamedParam() 0 9 1
A hp$0 ➔ testMakeAbstractClass() 0 6 1
A hp$0 ➔ testIterableArgumentWithUnnamedType() 0 9 1
A hp$0 ➔ testInvokeReferencedArguments() 0 40 1
testMakeAbstractClass() 0 6 ?
testInvokeReferencedArgument() 0 10 ?
A hp$0 ➔ testMakeWithVariadicFromContainer() 0 8 1
testInvokeWithObjectType() 0 8 ?
A hp$0 ➔ testArrayArgumentWithUnnamedType() 0 9 1
getContainer() 0 19 ?
testMakeWithoutArguments() 0 7 ?
A hp$0 ➔ testUnnamedScalarParam() 0 9 1
A hp$0 ➔ testMakeInvalidClass() 0 8 1
A hp$0 ➔ testInvokeable() 0 5 1
A hp$0 ➔ testMakeInternalClassWithOptionalMiddleArgumentSkipped() 0 12 1
testCallableArgumentWithUnnamedType() 0 9 ?
testMakeWithVariadicFromContainer() 0 8 ?
A hp$0 ➔ testMakeWithInvalidCustomParam() 0 7 1
testInvokeReferencedArgumentNamedVariadic() 0 21 ?
testMakeWithoutConstructor() 0 7 ?
A hp$0 ➔ testMakeInternalClass() 0 5 1
A hp$0 ➔ testInvokeWithObjectType() 0 8 1
testIterableArgumentWithUnnamedType() 0 9 ?
A hp$1 ➔ getContainer() 0 19 1
A hp$0 ➔ testInvokeReferencedArgument() 0 10 1
A hp$1 ➔ get() 0 7 2
testAppendingUnusedParams() 0 13 ?
A hp$0 ➔ testMakeInterface() 0 6 1
A hp$2 ➔ __construct() 0 3 1
testMakeWithVariadicFromArguments() 0 8 ?
testMakeInvalidClass() 0 8 ?
A hp$0 ➔ testInvokeReferencedArgumentNamedVariadic() 0 21 1
testMakeWithPrivateConstructor() 0 8 ?
testArrayArgumentWithUnnamedType() 0 9 ?
testUnnamedScalarParam() 0 9 ?
A hp$0 ➔ testMakeWithoutArguments() 0 7 1
testInvokeWithRequiredObjectTypeWithoutInstance() 0 8 ?
A hp$1 ➔ has() 0 3 1
testMakeInterface() 0 6 ?
testMakeInternalClassWithOptionalMiddleArgumentSkipped() 0 12 ?
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Injector\Tests;
6
7
use DateTime;
8
use DateTimeImmutable;
9
use DateTimeInterface;
10
use PHPUnit\Framework\TestCase;
11
use Psr\Container\ContainerInterface;
12
use Psr\Container\NotFoundExceptionInterface;
13
use stdClass;
14
use Yiisoft\Injector\Injector;
15
use Yiisoft\Injector\InvalidArgumentException;
16
use Yiisoft\Injector\MissingInternalArgumentException;
17
use Yiisoft\Injector\MissingRequiredArgumentException;
18
use Yiisoft\Injector\Tests\Support\ColorInterface;
19
use Yiisoft\Injector\Tests\Support\EngineInterface;
20
use Yiisoft\Injector\Tests\Support\EngineMarkTwo;
21
use Yiisoft\Injector\Tests\Support\EngineZIL130;
22
use Yiisoft\Injector\Tests\Support\EngineVAZ2101;
23
use Yiisoft\Injector\Tests\Support\Invokeable;
24
use Yiisoft\Injector\Tests\Support\LightEngine;
25
use Yiisoft\Injector\Tests\Support\MakeEmptyConstructor;
26
use Yiisoft\Injector\Tests\Support\MakeEngineCollector;
27
use Yiisoft\Injector\Tests\Support\MakeEngineMatherWithParam;
28
use Yiisoft\Injector\Tests\Support\MakeNoConstructor;
29
use Yiisoft\Injector\Tests\Support\MakePrivateConstructor;
30
31
class InjectorTest extends TestCase
32
{
33
    /**
34
     * Injector should be able to invoke closure.
35
     */
36
    public function testInvokeClosure(): void
37
    {
38
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
39
40
        $getEngineName = fn (EngineInterface $engine) => $engine->getName();
41
42
        $engineName = (new Injector($container))->invoke($getEngineName);
43
44
        $this->assertSame('Mark Two', $engineName);
45
    }
46
47
    /**
48
     * Injector should be able to invoke array callable.
49
     */
50
    public function testInvokeCallableArray(): void
51
    {
52
        $container = $this->getContainer();
53
54
        $object = new EngineVAZ2101();
55
56
        $engine = (new Injector($container))->invoke([$object, 'rust'], ['index' => 5.0]);
57
58
        $this->assertInstanceOf(EngineVAZ2101::class, $engine);
59
    }
60
61
    /**
62
     * Injector should be able to invoke static method.
63
     */
64
    public function testInvokeStatic(): void
65
    {
66
        $container = $this->getContainer();
67
68
        $result = (new Injector($container))->invoke([EngineVAZ2101::class, 'isWroomWroom']);
69
70
        $this->assertIsBool($result);
71
    }
72
73
    /**
74
     * Injector should be able to invoke static method.
75
     */
76
    public function testInvokeAnonymousClass(): void
77
    {
78
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
79
        $class = new class() {
80
            public EngineInterface $engine;
81
            public function setEngine(EngineInterface $engine)
82
            {
83
                $this->engine = $engine;
84
            }
85
        };
86
87
        (new Injector($container))->invoke([$class, 'setEngine']);
88
89
        $this->assertInstanceOf(EngineInterface::class, $class->engine);
90
    }
91
92
    /**
93
     * Injector should be able to invoke method without arguments.
94
     */
95
    public function testInvokeWithoutArguments(): void
96
    {
97
        $container = $this->getContainer();
98
99
        $true = fn () => true;
100
101
        $result = (new Injector($container))->invoke($true);
102
103
        $this->assertTrue($result);
104
    }
105
106
    /**
107
     * Nullable arguments should be searched in container.
108
     */
109
    public function testWithNullableArgument(): void
110
    {
111
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
112
113
        $nullable = fn (?EngineInterface $engine) => $engine;
114
115
        $result = (new Injector($container))->invoke($nullable);
116
117
        $this->assertNotNull($result);
118
    }
119
120
    /**
121
     * Nullable arguments not found in container should be passed as `null`.
122
     */
123
    public function testWithNullableArgumentAndEmptyContainer(): void
124
    {
125
        $container = $this->getContainer();
126
127
        $nullable = fn (?EngineInterface $engine) => $engine;
128
129
        $result = (new Injector($container))->invoke($nullable);
130
131
        $this->assertNull($result);
132
    }
133
134
    /**
135
     * Nullable scalars should be set with `null` if not specified by name explicitly.
136
     */
137
    public function testWithNullableScalarArgument(): void
138
    {
139
        $container = $this->getContainer();
140
141
        $nullableInt = fn (?int $number) => $number;
142
143
        $result = (new Injector($container))->invoke($nullableInt);
144
145
        $this->assertNull($result);
146
    }
147
148
    /**
149
     * Optional scalar arguments should be set with default value if not specified by name explicitly.
150
     */
151
    public function testWithNullableOptionalArgument(): void
152
    {
153
        $container = $this->getContainer();
154
155
        $nullableInt = fn (?int $number = 6) => $number;
156
157
        $result = (new Injector($container))->invoke($nullableInt);
158
159
        $this->assertSame(6, $result);
160
    }
161
162
    /**
163
     * Optional arguments with `null` by default should be set with `null` if other value not specified in parameters
164
     * or container.
165
     */
166
    public function testWithNullableOptionalArgumentThatNull(): void
167
    {
168
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
169
170
        $callable = fn (EngineInterface $engine = null) => $engine;
171
172
        $result = (new Injector($container))->invoke($callable);
173
174
        $this->assertNotNull($result);
175
    }
176
177
    /**
178
     * An object for a typed argument can be specified in parameters without named key and without following the order.
179
     */
180
    public function testCustomDependency(): void
181
    {
182
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
183
        $needleEngine = new EngineZIL130();
184
185
        $getEngineName = fn (EngineInterface $engine) => $engine->getName();
186
187
        $engineName = (new Injector($container))->invoke(
188
            $getEngineName,
189
            [new stdClass(), $needleEngine, new DateTimeImmutable()]
190
        );
191
192
        $this->assertSame(EngineZIL130::NAME, $engineName);
193
    }
194
195
    /**
196
     * In this case, first argument will be set from parameters, and second argument from container.
197
     */
198
    public function testTwoEqualCustomArgumentsWithOneCustom(): void
199
    {
200
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
201
202
        $compareEngines = static function (EngineInterface $engine1, EngineInterface $engine2) {
203
            return $engine1->getPower() <=> $engine2->getPower();
204
        };
205
        $zilEngine = new EngineZIL130();
206
207
        $result = (new Injector($container))->invoke($compareEngines, [$zilEngine]);
208
209
        $this->assertSame(-1, $result);
210
    }
211
212
    /**
213
     * In this case, second argument will be set from parameters by name, and first argument from container.
214
     */
215
    public function testTwoEqualCustomArgumentsWithOneCustomNamedParameter(): void
216
    {
217
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
218
219
        $compareEngines = static function (EngineInterface $engine1, EngineInterface $engine2) {
220
            return $engine1->getPower() <=> $engine2->getPower();
221
        };
222
        $zilEngine = new EngineZIL130();
223
224
        $result = (new Injector($container))->invoke($compareEngines, ['engine2' => $zilEngine]);
225
226
        $this->assertSame(1, $result);
227
    }
228
229
    /**
230
     * Values for arguments are not matched by the greater similarity of parameter types and arguments, but simply pass
231
     * in order as is.
232
     */
233
    public function testExtendedArgumentsWithOneCustomNamedParameter2(): void
234
    {
235
        $container = $this->getContainer([LightEngine::class => new EngineVAZ2101()]);
236
237
        $concatEngineNames = static function (EngineInterface $engine1, LightEngine $engine2) {
238
            return $engine1->getName() . $engine2->getName();
239
        };
240
241
        $result = (new Injector($container))->invoke($concatEngineNames, [
242
            new EngineMarkTwo(), // LightEngine, EngineInterface
243
            new EngineZIL130(), // EngineInterface
244
        ]);
245
246
        $this->assertSame(EngineMarkTwo::NAME . EngineVAZ2101::NAME, $result);
247
    }
248
249
    public function testMissingRequiredTypedParameter(): void
250
    {
251
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
252
253
        $getEngineName = static function (EngineInterface $engine, string $two) {
0 ignored issues
show
Unused Code introduced by
The parameter $two is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

253
        $getEngineName = static function (EngineInterface $engine, /** @scrutinizer ignore-unused */ string $two) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
254
            return $engine->getName();
255
        };
256
257
        $injector = new Injector($container);
258
259
        $this->expectException(MissingRequiredArgumentException::class);
260
        $injector->invoke($getEngineName);
261
    }
262
263
    public function testMissingRequiredNotTypedParameter(): void
264
    {
265
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
266
267
        $getEngineName = static function (EngineInterface $engine, $two) {
0 ignored issues
show
Unused Code introduced by
The parameter $two is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

267
        $getEngineName = static function (EngineInterface $engine, /** @scrutinizer ignore-unused */ $two) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
268
            return $engine->getName();
269
        };
270
        $injector = new Injector($container);
271
272
        $this->expectException(MissingRequiredArgumentException::class);
273
274
        $injector->invoke($getEngineName);
275
    }
276
277
    public function testNotFoundException(): void
278
    {
279
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
280
281
        $getEngineName = static function (EngineInterface $engine, ColorInterface $color) {
0 ignored issues
show
Unused Code introduced by
The parameter $color is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

281
        $getEngineName = static function (EngineInterface $engine, /** @scrutinizer ignore-unused */ ColorInterface $color) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
282
            return $engine->getName();
283
        };
284
285
        $injector = new Injector($container);
286
287
        $this->expectException(NotFoundExceptionInterface::class);
288
        $injector->invoke($getEngineName);
289
    }
290
291
    /**
292
     * A values collection for a variadic argument can be passed as an array in a named parameter.
293
     */
294
    public function testAloneScalarVariadicArgumentAnsNamedParam(): void
295
    {
296
        $container = $this->getContainer();
297
298
        $callable = fn (...$var) => array_sum($var);
299
300
        $result = (new Injector($container))->invoke($callable, ['var' => [1, 2, 3]]);
301
302
        $this->assertSame(6, $result);
303
    }
304
305
    /**
306
     * If type of a variadic argument is a class and named parameter with values collection is not set then injector
307
     * will search for objects by class name among all unnamed parameters.
308
     */
309
    public function testVariadicArgumentUnnamedParams(): void
310
    {
311
        $container = $this->getContainer([DateTimeInterface::class => new DateTimeImmutable()]);
312
313
        $callable = fn (DateTimeInterface $dateTime, EngineInterface ...$engines) => count($engines);
314
315
        $result = (new Injector($container))->invoke(
316
            $callable,
317
            [new EngineZIL130(), new EngineVAZ2101(), new stdClass(), new EngineMarkTwo(), new stdClass()]
318
        );
319
320
        $this->assertSame(3, $result);
321
    }
322
323
    /**
324
     * If calling method have an untyped variadic argument then all remaining unnamed parameters will be passed.
325
     */
326
    public function testVariadicMixedArgumentWithMixedParams(): void
327
    {
328
        $container = $this->getContainer([DateTimeInterface::class => new DateTimeImmutable()]);
329
330
        $callable = fn (...$engines) => $engines;
331
332
        $result = (new Injector($container))->invoke(
333
            $callable,
334
            [new EngineZIL130(), new EngineVAZ2101(), new EngineMarkTwo(), new stdClass()]
335
        );
336
337
        $this->assertCount(4, $result);
338
    }
339
340
    /**
341
     * Any unnamed parameter can only be an object. Scalar, array, null and other values can only be named parameters.
342
     */
343
    public function testVariadicStringArgumentWithUnnamedStringsParams(): void
344
    {
345
        $container = $this->getContainer([DateTimeInterface::class => new DateTimeImmutable()]);
346
347
        $callable = fn (string ...$engines) => $engines;
348
349
        $this->expectException(\Exception::class);
350
351
        (new Injector($container))->invoke($callable, ['str 1', 'str 2', 'str 3']);
352
    }
353
354
    /**
355
     * In the absence of other values to a nullable variadic argument `null` is not passed by default.
356
     */
357
    public function testNullableVariadicArgument(): void
358
    {
359
        $container = $this->getContainer();
360
361
        $callable = fn (?EngineInterface ...$engines) => $engines;
362
363
        $result = (new Injector($container))->invoke($callable, []);
364
365
        $this->assertSame([], $result);
366
    }
367
368
    /**
369
     * Parameters that were passed but were not used are appended to the call so they could be obtained
370
     * with func_get_args().
371
     */
372
    public function testAppendingUnusedParams(): void
373
    {
374
        $container = $this->getContainer();
375
376
        $callable = fn (?EngineInterface $engine, $id = 'test') => func_num_args();
0 ignored issues
show
Unused Code introduced by
The parameter $id is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

376
        $callable = fn (?EngineInterface $engine, /** @scrutinizer ignore-unused */ $id = 'test') => func_num_args();

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $engine is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

376
        $callable = fn (/** @scrutinizer ignore-unused */ ?EngineInterface $engine, $id = 'test') => func_num_args();

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
377
378
        $result = (new Injector($container))->invoke($callable, [
379
            new DateTimeImmutable(),
380
            new DateTimeImmutable(),
381
            new EngineMarkTwo()
382
        ]);
383
384
        $this->assertSame(4, $result);
385
    }
386
387
    /**
388
     * Object type may be passed as unnamed parameter
389
     */
390
    public function testInvokeWithObjectType(): void
391
    {
392
        $container = $this->getContainer();
393
        $callable = fn (object $object) => get_class($object);
394
395
        $result = (new Injector($container))->invoke($callable, [new DateTimeImmutable()]);
396
397
        $this->assertSame(DateTimeImmutable::class, $result);
398
    }
399
400
    /**
401
     * Required `object` type should not be requested from the container
402
     */
403
    public function testInvokeWithRequiredObjectTypeWithoutInstance(): void
404
    {
405
        $container = $this->getContainer();
406
        $callable = fn (object $object) => get_class($object);
407
408
        $this->expectException(MissingRequiredArgumentException::class);
409
410
        (new Injector($container))->invoke($callable);
411
    }
412
413
    /**
414
     * Arguments passed by reference
415
     */
416
    public function testInvokeReferencedArguments(): void
417
    {
418
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
419
        $foo = 1;
420
        $bar = new stdClass();
421
        $baz = null;
422
        $callable = static function (
423
            int &$foo,
424
            object &$bar,
425
            &$baz,
426
            ?ColorInterface &$nullable,
427
            EngineInterface &$object, // from container
428
            DateTimeInterface &...$dates // collect all unnamed DateTimeInterface objects
429
        ) {
430
            $return = func_get_args();
431
            $bar = new DateTimeImmutable();
432
            $baz = false;
433
            $foo = count($dates);
434
            return $return;
435
        };
436
        $result = (new Injector($container))
437
            ->invoke($callable, [
438
                new DateTimeImmutable(),
439
                new DateTime(),
440
                new DateTime(),
441
                'foo' => &$foo,
442
                'bar' => $bar,
443
                'baz' => &$baz,
444
            ]);
445
446
        // passed
447
        $this->assertSame(1, $result[0]);
448
        $this->assertInstanceOf(stdClass::class, $result[1]);
449
        $this->assertNull($result[2]);
450
        $this->assertNull($result[3]);
451
        $this->assertInstanceOf(EngineMarkTwo::class, $result[4]);
452
        // transformed
453
        $this->assertSame(3, $foo); // count of DateTimeInterface objects
454
        $this->assertInstanceOf(stdClass::class, $bar);
455
        $this->assertFalse($baz);
456
    }
457
458
    public function testInvokeReferencedArgumentNamedVariadic(): void
459
    {
460
        $container = $this->getContainer();
461
462
        $callable = static function (DateTimeInterface &...$dates) {
463
            $dates[0] = false;
464
            $dates[1] = false;
465
            return count($dates);
466
        };
467
        $foo = new DateTimeImmutable();
468
        $bar = new DateTimeImmutable();
469
        $result = (new Injector($container))
470
            ->invoke($callable, [
471
                $foo,
472
                &$bar,
473
                new DateTime(),
474
            ]);
475
476
        $this->assertSame(3, $result);
477
        $this->assertInstanceOf(DateTimeImmutable::class, $foo);
478
        $this->assertFalse($bar);
479
    }
480
481
    /**
482
     * If argument passed by reference but it is not supported by function
483
     */
484
    public function testInvokeReferencedArgument(): void
485
    {
486
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
487
        $foo = 1;
488
        $callable = fn (int $foo) => ++$foo;
489
        $result = (new Injector($container))->invoke($callable, ['foo' => &$foo]);
490
491
        // $foo has been not changed
492
        $this->assertSame(1, $foo);
493
        $this->assertSame(2, $result);
494
    }
495
496
    public function testWrongNamedParam(): void
497
    {
498
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
499
500
        $callable = fn (EngineInterface $engine) => $engine;
501
502
        $this->expectException(\Throwable::class);
503
504
        (new Injector($container))->invoke($callable, ['engine' => new DateTimeImmutable()]);
505
    }
506
507
    public function testArrayArgumentWithUnnamedType(): void
508
    {
509
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
510
511
        $callable = fn (array $arg) => $arg;
512
513
        $this->expectException(MissingRequiredArgumentException::class);
514
515
        (new Injector($container))->invoke($callable, [['test']]);
516
    }
517
518
    public function testCallableArgumentWithUnnamedType(): void
519
    {
520
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
521
522
        $callable = fn (callable $arg) => $arg();
523
524
        $this->expectException(MissingRequiredArgumentException::class);
525
526
        (new Injector($container))->invoke($callable, [fn () => true]);
527
    }
528
529
    public function testIterableArgumentWithUnnamedType(): void
530
    {
531
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
532
533
        $callable = fn (iterable $arg) => $arg;
534
535
        $this->expectException(MissingRequiredArgumentException::class);
536
537
        (new Injector($container))->invoke($callable, [new \SplStack()]);
538
    }
539
540
    public function testUnnamedScalarParam(): void
541
    {
542
        $container = $this->getContainer();
543
544
        $getEngineName = fn () => 42;
545
546
        $this->expectException(InvalidArgumentException::class);
547
548
        (new Injector($container))->invoke($getEngineName, ['test']);
549
    }
550
551
    public function testInvokeable(): void
552
    {
553
        $container = $this->getContainer();
554
        $result = (new Injector($container))->invoke(new Invokeable());
555
        $this->assertSame(42, $result);
556
    }
557
558
    /**
559
     * Constructor method not defined
560
     */
561
    public function testMakeWithoutConstructor(): void
562
    {
563
        $container = $this->getContainer();
564
565
        $object = (new Injector($container))->make(MakeNoConstructor::class);
566
567
        $this->assertInstanceOf(MakeNoConstructor::class, $object);
568
    }
569
570
    /**
571
     * Constructor without arguments
572
     */
573
    public function testMakeWithoutArguments(): void
574
    {
575
        $container = $this->getContainer();
576
577
        $object = (new Injector($container))->make(MakeEmptyConstructor::class);
578
579
        $this->assertInstanceOf(MakeEmptyConstructor::class, $object);
580
    }
581
582
    /**
583
     * Private constructor unavailable from Injector context
584
     */
585
    public function testMakeWithPrivateConstructor(): void
586
    {
587
        $container = $this->getContainer();
588
589
        $this->expectException(\InvalidArgumentException::class);
590
        $this->expectExceptionMessageMatches('/not instantiable/');
591
592
        (new Injector($container))->make(MakePrivateConstructor::class);
593
    }
594
595
    public function testMakeInvalidClass(): void
596
    {
597
        $container = $this->getContainer();
598
599
        $this->expectException(\ReflectionException::class);
600
        $this->expectExceptionMessageMatches('/does not exist/');
601
602
        (new Injector($container))->make(UndefinedClassThatShouldNotBeDefined::class);
0 ignored issues
show
Bug introduced by
The type Yiisoft\Injector\Tests\U...sThatShouldNotBeDefined was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
603
    }
604
605
    public function testMakeInternalClass(): void
606
    {
607
        $container = $this->getContainer();
608
        $object = (new Injector($container))->make(DateTimeImmutable::class);
609
        $this->assertInstanceOf(DateTimeImmutable::class, $object);
610
    }
611
612
    public function testMakeInternalClassWithOptionalMiddleArgumentSkipped(): void
613
    {
614
        $container = $this->getContainer();
615
616
        $this->expectException(MissingInternalArgumentException::class);
617
        $this->expectExceptionMessageMatches('/PHP internal/');
618
619
        (new Injector($container))->make(\SplFileObject::class, [
620
            'file_name' => __FILE__,
621
            // second parameter skipped
622
            'use_include_path' => false,
623
            'other-parameter' => true,
624
        ]);
625
    }
626
627
    public function testMakeAbstractClass(): void
628
    {
629
        $container = $this->getContainer();
630
        $this->expectException(\InvalidArgumentException::class);
631
        $this->expectExceptionMessageMatches('/not instantiable/');
632
        (new Injector($container))->make(LightEngine::class);
633
    }
634
635
    public function testMakeInterface(): void
636
    {
637
        $container = $this->getContainer();
638
        $this->expectException(\InvalidArgumentException::class);
639
        $this->expectExceptionMessageMatches('/not instantiable/');
640
        (new Injector($container))->make(EngineInterface::class);
641
    }
642
643
    public function testMakeWithVariadicFromContainer(): void
644
    {
645
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
646
647
        $object = (new Injector($container))->make(MakeEngineCollector::class, []);
648
649
        $this->assertSame(1, count($object->engines));
650
        $this->assertSame([$container->get(EngineInterface::class)], $object->engines);
651
    }
652
653
    public function testMakeWithVariadicFromArguments(): void
654
    {
655
        $container = $this->getContainer();
656
657
        $values = [new EngineMarkTwo(), new EngineVAZ2101()];
658
        $object = (new Injector($container))->make(MakeEngineCollector::class, $values);
659
660
        $this->assertSame($values, $object->engines);
661
    }
662
663
    public function testMakeWithCustomParam(): void
664
    {
665
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
666
667
        $object = (new Injector($container))
668
            ->make(MakeEngineMatherWithParam::class, [new EngineVAZ2101(), 'parameter' => 'power']);
669
670
        $this->assertNotSame($object->engine1, $object->engine2);
671
        $this->assertInstanceOf(EngineVAZ2101::class, $object->engine1);
672
        $this->assertNotSame(EngineMarkTwo::class, $object->engine2);
673
        $this->assertSame($object->parameter, 'power');
674
    }
675
676
    public function testMakeWithInvalidCustomParam(): void
677
    {
678
        $container = $this->getContainer([EngineInterface::class => new EngineMarkTwo()]);
679
680
        $this->expectException(\TypeError::class);
681
682
        (new Injector($container))->make(MakeEngineMatherWithParam::class, ['parameter' => 100500]);
683
    }
684
685
    private function getContainer(array $definitions = []): ContainerInterface
686
    {
687
        return new class($definitions) implements ContainerInterface {
688
            private array $definitions = [];
689
            public function __construct(array $definitions = [])
690
            {
691
                $this->definitions = $definitions;
692
            }
693
            public function get($id)
694
            {
695
                if (!$this->has($id)) {
696
                    throw new class() extends \Exception implements NotFoundExceptionInterface {
697
                    };
698
                }
699
                return $this->definitions[$id];
700
            }
701
            public function has($id)
702
            {
703
                return array_key_exists($id, $this->definitions);
704
            }
705
        };
706
    }
707
}
708