Completed
Push — 4.11 ( 62bf31...086631 )
by Guy
44s queued 26s
created

InjectorTest::testGetThrowsOnNotFound()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Tests\Injector;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\Core\Environment;
7
use SilverStripe\Core\Injector\Factory;
8
use SilverStripe\Core\Injector\Injector;
9
use SilverStripe\Core\Injector\InjectorNotFoundException;
10
use SilverStripe\Core\Injector\SilverStripeServiceConfigurationLocator;
11
use SilverStripe\Core\Tests\Injector\AopProxyServiceTest\AnotherService;
12
use SilverStripe\Core\Tests\Injector\AopProxyServiceTest\SampleService;
13
use SilverStripe\Core\Tests\Injector\InjectorTest\CircularOne;
14
use SilverStripe\Core\Tests\Injector\InjectorTest\CircularTwo;
15
use SilverStripe\Core\Tests\Injector\InjectorTest\ConstructableObject;
16
use SilverStripe\Core\Tests\Injector\InjectorTest\DummyRequirements;
17
use SilverStripe\Core\Tests\Injector\InjectorTest\EmptyFactory;
18
use SilverStripe\Core\Tests\Injector\InjectorTest\MyChildClass;
19
use SilverStripe\Core\Tests\Injector\InjectorTest\MyParentClass;
20
use SilverStripe\Core\Tests\Injector\InjectorTest\NeedsBothCirculars;
21
use SilverStripe\Core\Tests\Injector\InjectorTest\NewRequirementsBackend;
22
use SilverStripe\Core\Tests\Injector\InjectorTest\OriginalRequirementsBackend;
23
use SilverStripe\Core\Tests\Injector\InjectorTest\OtherTestObject;
24
use SilverStripe\Core\Tests\Injector\InjectorTest\TestObject;
25
use SilverStripe\Core\Tests\Injector\InjectorTest\TestSetterInjections;
26
use SilverStripe\Core\Tests\Injector\InjectorTest\TestStaticInjections;
27
use SilverStripe\Dev\SapphireTest;
28
use SilverStripe\Dev\TestOnly;
29
use stdClass;
30
31
define('TEST_SERVICES', __DIR__ . '/AopProxyServiceTest');
32
33
/**
34
 * Tests for the dependency injector
35
 *
36
 * Note that these are SS conversions of the existing Simpletest unit tests
37
 *
38
 * @author      [email protected]
39
 * @license     BSD License http://silverstripe.org/bsd-license/
40
 * @skipUpgrade
41
 */
42
class InjectorTest extends SapphireTest
43
{
44
45
    protected $nestingLevel = 0;
46
47
    protected function setUp(): void
48
    {
49
        parent::setUp();
50
51
        $this->nestingLevel = 0;
52
    }
53
54
    protected function tearDown(): void
55
    {
56
57
        while ($this->nestingLevel > 0) {
58
            $this->nestingLevel--;
59
            Config::unnest();
60
        }
61
62
        parent::tearDown();
63
    }
64
65
    public function testCorrectlyInitialised()
66
    {
67
        $injector = Injector::inst();
68
        $this->assertTrue(
69
            $injector->getConfigLocator() instanceof SilverStripeServiceConfigurationLocator,
70
            'Failure most likely because the injector has been referenced BEFORE being initialised in Core.php'
71
        );
72
    }
73
74
    public function testBasicInjector()
75
    {
76
        $injector = new Injector();
77
        $injector->setAutoScanProperties(true);
78
        $config = [
79
            'SampleService' => [
80
                'src' => TEST_SERVICES . '/SampleService.php',
81
                'class' => SampleService::class,
82
            ]
83
        ];
84
85
        $injector->load($config);
86
87
88
        $this->assertFalse($injector->has('UnknownService'));
89
        $this->assertNull($injector->getServiceName('UnknownService'));
90
91
        $this->assertTrue($injector->has('SampleService'));
92
        $this->assertEquals(
93
            'SampleService',
94
            $injector->getServiceName('SampleService')
95
        );
96
97
        $myObject = new TestObject();
98
        $injector->inject($myObject);
99
100
        $this->assertInstanceOf(
101
            SampleService::class,
102
            $myObject->sampleService
103
        );
104
    }
105
106
    public function testEmptyFactory()
107
    {
108
        $this->expectException(InjectorNotFoundException::class);
109
        $injector = new Injector();
110
        $services = [
111
            'SomeClass' => [
112
                'class' => AnotherService::class,
113
                'factory' => EmptyFactory::class,
114
            ]
115
        ];
116
117
        $injector->load($services);
118
        $injector->create('SomeClass');
119
    }
120
121
    public function testConfiguredInjector()
122
    {
123
        $injector = new Injector();
124
        $services = [
125
            'AnotherService' => [
126
                'class' => AnotherService::class,
127
                'src' => TEST_SERVICES . '/AnotherService.php',
128
                'properties' => ['config_property' => 'Value'],
129
            ],
130
            'SampleService' => [
131
                'class' => SampleService::class,
132
                'src' => TEST_SERVICES . '/SampleService.php',
133
            ]
134
        ];
135
136
        $injector->load($services);
137
        $this->assertTrue($injector->has('SampleService'));
138
        $this->assertEquals(
139
            'SampleService',
140
            $injector->getServiceName('SampleService')
141
        );
142
        // We expect a false because the AnotherService::class is actually
143
        // just a replacement of the SilverStripe\Core\Tests\Injector\AopProxyServiceTest\SampleService
144
        $this->assertTrue($injector->has('SampleService'));
145
        $this->assertEquals(
146
            'AnotherService',
147
            $injector->getServiceName('AnotherService')
148
        );
149
150
        $item = $injector->get('AnotherService');
151
152
        $this->assertEquals('Value', $item->config_property);
153
    }
154
155
    public function testIdToNameMap()
156
    {
157
        $injector = new Injector();
158
        $services = [
159
            'FirstId' => AnotherService::class,
160
            'SecondId' => SampleService::class,
161
        ];
162
163
        $injector->load($services);
164
165
        $this->assertTrue($injector->has('FirstId'));
166
        $this->assertEquals($injector->getServiceName('FirstId'), 'FirstId');
167
168
        $this->assertTrue($injector->has('SecondId'));
169
        $this->assertEquals($injector->getServiceName('SecondId'), 'SecondId');
170
171
        $this->assertTrue($injector->get('FirstId') instanceof AnotherService);
172
        $this->assertTrue($injector->get('SecondId') instanceof SampleService);
173
    }
174
175
    public function testReplaceService()
176
    {
177
        $injector = new Injector();
178
        $injector->setAutoScanProperties(true);
179
180
        $config = [
181
            'SampleService' => [
182
                'src' => TEST_SERVICES . '/SampleService.php',
183
                'class' => SampleService::class,
184
            ]
185
        ];
186
187
        // load
188
        $injector->load($config);
189
190
        // inject
191
        $myObject = new TestObject();
192
        $injector->inject($myObject);
193
194
        $this->assertInstanceOf(
195
            SampleService::class,
196
            $myObject->sampleService
197
        );
198
199
        // also tests that ID can be the key in the array
200
        $config = [
201
            'SampleService' => [
202
                'src' => TEST_SERVICES . '/AnotherService.php',
203
                'class' => AnotherService::class,
204
            ]
205
        ];
206
        // , 'id' => SampleService::class));
207
        // load
208
        $injector->load($config);
209
210
        $injector->inject($myObject);
211
        $this->assertInstanceOf(
212
            AnotherService::class,
213
            $myObject->sampleService
214
        );
215
    }
216
217
    public function testUpdateSpec()
218
    {
219
        $injector = new Injector();
220
        $services = [
221
            AnotherService::class => [
222
                'src' => TEST_SERVICES . '/AnotherService.php',
223
                'properties' => [
224
                    'filters' => [
225
                        'One',
226
                        'Two',
227
                    ]
228
                ],
229
            ]
230
        ];
231
232
        $injector->load($services);
233
234
        $injector->updateSpec(AnotherService::class, 'filters', 'Three');
235
        $another = $injector->get(AnotherService::class);
236
237
        $this->assertEquals(3, count($another->filters ?? []));
238
        $this->assertEquals('Three', $another->filters[2]);
239
    }
240
241
    public function testConstantUsage()
242
    {
243
        $injector = new Injector();
244
        $services = [
245
            AnotherService::class => [
246
                'properties' => [
247
                    'filters' => [
248
                        '`BASE_PATH`',
249
                        '`TEMP_PATH`',
250
                        '`NOT_DEFINED`',
251
                        'THIRDPARTY_DIR' // Not back-tick escaped
252
                    ]
253
                ],
254
            ]
255
        ];
256
257
        $injector->load($services);
258
        $another = $injector->get(AnotherService::class);
259
        $this->assertEquals(
260
            [
261
                BASE_PATH,
262
                TEMP_PATH,
263
                null,
264
                'THIRDPARTY_DIR',
265
            ],
266
            $another->filters
267
        );
268
    }
269
270
    public function testAutoSetInjector()
271
    {
272
        $injector = new Injector();
273
        $injector->setAutoScanProperties(true);
274
        $injector->addAutoProperty('auto', 'somevalue');
0 ignored issues
show
Bug introduced by
'somevalue' of type string is incompatible with the type object expected by parameter $object of SilverStripe\Core\Inject...ctor::addAutoProperty(). ( Ignorable by Annotation )

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

274
        $injector->addAutoProperty('auto', /** @scrutinizer ignore-type */ 'somevalue');
Loading history...
275
        $config = [
276
            'SampleService' => [
277
                'src' => TEST_SERVICES . '/SampleService.php',
278
                'class' => SampleService::class
279
            ]
280
        ];
281
        $injector->load($config);
282
283
        $this->assertTrue($injector->has('SampleService'));
284
        $this->assertEquals(
285
            'SampleService',
286
            $injector->getServiceName('SampleService')
287
        );
288
        // We expect a false because the AnotherService::class is actually
289
        // just a replacement of the SilverStripe\Core\Tests\Injector\AopProxyServiceTest\SampleService
290
291
        $myObject = new InjectorTest\TestObject();
292
293
        $injector->inject($myObject);
294
295
        $this->assertInstanceOf(
296
            SampleService::class,
297
            $myObject->sampleService
298
        );
299
        $this->assertEquals($myObject->auto, 'somevalue');
0 ignored issues
show
Bug introduced by
The property auto does not seem to exist on SilverStripe\Core\Tests\...InjectorTest\TestObject.
Loading history...
300
    }
301
302
    public function testSettingSpecificProperty()
303
    {
304
        $injector = new Injector();
305
        $config = [AnotherService::class];
306
        $injector->load($config);
307
        $injector->setInjectMapping(TestObject::class, 'sampleService', AnotherService::class);
308
        $testObject = $injector->get(TestObject::class);
309
310
        $this->assertInstanceOf(
311
            AnotherService::class,
312
            $testObject->sampleService
313
        );
314
    }
315
316
    public function testSettingSpecificMethod()
317
    {
318
        $injector = new Injector();
319
        $config = [AnotherService::class];
320
        $injector->load($config);
321
        $injector->setInjectMapping(TestObject::class, 'setSomething', AnotherService::class, 'method');
322
323
        $testObject = $injector->get(TestObject::class);
324
325
        $this->assertInstanceOf(
326
            AnotherService::class,
327
            $testObject->sampleService
328
        );
329
    }
330
331
    public function testInjectingScopedService()
332
    {
333
        $injector = new Injector();
334
335
        $config = [
336
            AnotherService::class,
337
            'SilverStripe\Core\Tests\Injector\AopProxyServiceTest\AnotherService.DottedChild'   => SampleService::class,
338
        ];
339
340
        $injector->load($config);
341
342
        $service = $injector->get('SilverStripe\Core\Tests\Injector\AopProxyServiceTest\AnotherService.DottedChild');
343
        $this->assertInstanceOf(SampleService::class, $service);
344
345
        $service = $injector->get('SilverStripe\Core\Tests\Injector\AopProxyServiceTest\AnotherService.Subset');
346
        $this->assertInstanceOf(AnotherService::class, $service);
347
348
        $injector->setInjectMapping(TestObject::class, 'sampleService', 'SilverStripe\Core\Tests\Injector\AopProxyServiceTest\AnotherService.Geronimo');
349
        $testObject = $injector->create(TestObject::class);
350
        $this->assertEquals(get_class($testObject->sampleService), AnotherService::class);
351
352
        $injector->setInjectMapping(TestObject::class, 'sampleService', 'SilverStripe\Core\Tests\Injector\AopProxyServiceTest\AnotherService.DottedChild.AnotherDown');
353
        $testObject = $injector->create(TestObject::class);
354
        $this->assertEquals(get_class($testObject->sampleService), SampleService::class);
355
    }
356
357
    public function testInjectUsingConstructor()
358
    {
359
        $injector = new Injector();
360
        $config = [
361
            'SampleService' => [
362
                'src' => TEST_SERVICES . '/SampleService.php',
363
                'class' => SampleService::class,
364
                'constructor' => [
365
                    'val1',
366
                    'val2',
367
                ]
368
            ]
369
        ];
370
371
        $injector->load($config);
372
        $sample = $injector->get('SampleService');
373
        $this->assertEquals($sample->constructorVarOne, 'val1');
374
        $this->assertEquals($sample->constructorVarTwo, 'val2');
375
376
        $injector = new Injector();
377
        $config = [
378
            'AnotherService' => AnotherService::class,
379
            'SampleService' => [
380
                'src' => TEST_SERVICES . '/SampleService.php',
381
                'class' => SampleService::class,
382
                'constructor' => [
383
                    'val1',
384
                    '%$AnotherService',
385
                ]
386
            ]
387
        ];
388
389
        $injector->load($config);
390
        $sample = $injector->get('SampleService');
391
        $this->assertEquals($sample->constructorVarOne, 'val1');
392
        $this->assertInstanceOf(
393
            AnotherService::class,
394
            $sample->constructorVarTwo
395
        );
396
397
        $injector = new Injector();
398
        $config = [
399
            'SampleService' => [
400
                'src' => TEST_SERVICES . '/SampleService.php',
401
                'class' => SampleService::class,
402
                'constructor' => [
403
                    'val1',
404
                    'val2',
405
                ]
406
            ]
407
        ];
408
409
        $injector->load($config);
410
        $sample = $injector->get('SampleService');
411
        $this->assertEquals($sample->constructorVarOne, 'val1');
412
        $this->assertEquals($sample->constructorVarTwo, 'val2');
413
414
        // test constructors on prototype
415
        $injector = new Injector();
416
        $config = [
417
            'SampleService' => [
418
                'type'  => 'prototype',
419
                'src' => TEST_SERVICES . '/SampleService.php',
420
                'class' => SampleService::class,
421
                'constructor' => [
422
                    'val1',
423
                    'val2',
424
                ]
425
            ]
426
        ];
427
428
        $injector->load($config);
429
        $sample = $injector->get('SampleService');
430
        $this->assertEquals($sample->constructorVarOne, 'val1');
431
        $this->assertEquals($sample->constructorVarTwo, 'val2');
432
433
        $again = $injector->get('SampleService');
434
        $this->assertFalse($sample === $again);
435
436
        $this->assertEquals($sample->constructorVarOne, 'val1');
437
        $this->assertEquals($sample->constructorVarTwo, 'val2');
438
    }
439
440
    public function testInjectUsingSetter()
441
    {
442
        $injector = new Injector();
443
        $injector->setAutoScanProperties(true);
444
        $config = [
445
            'SampleService' => [
446
                'src' => TEST_SERVICES . '/SampleService.php',
447
                'class' => SampleService::class,
448
            ]
449
        ];
450
451
        $injector->load($config);
452
        $this->assertTrue($injector->has('SampleService'));
453
        $this->assertEquals('SampleService', $injector->getServiceName('SampleService'));
454
455
        $myObject = new InjectorTest\OtherTestObject();
456
        $injector->inject($myObject);
457
458
        $this->assertInstanceOf(
459
            SampleService::class,
460
            $myObject->s()
461
        );
462
463
        // and again because it goes down a different code path when setting things
464
        // based on the inject map
465
        $myObject = new InjectorTest\OtherTestObject();
466
        $injector->inject($myObject);
467
468
        $this->assertInstanceOf(
469
            SampleService::class,
470
            $myObject->s()
471
        );
472
    }
473
474
    // make sure we can just get any arbitrary object - it should be created for us
475
    public function testInstantiateAnObjectViaGet()
476
    {
477
        $injector = new Injector();
478
        $injector->setAutoScanProperties(true);
479
        $config = [
480
            'SampleService' => [
481
                'src' => TEST_SERVICES . '/SampleService.php',
482
                'class' => SampleService::class,
483
            ]
484
        ];
485
486
        $injector->load($config);
487
        $this->assertTrue($injector->has('SampleService'));
488
        $this->assertEquals('SampleService', $injector->getServiceName('SampleService'));
489
490
        $myObject = $injector->get(OtherTestObject::class);
491
        $this->assertInstanceOf(
492
            SampleService::class,
493
            $myObject->s()
494
        );
495
496
        // and again because it goes down a different code path when setting things
497
        // based on the inject map
498
        $myObject = $injector->get(OtherTestObject::class);
499
        $this->assertInstanceOf(SampleService::class, $myObject->s());
500
    }
501
502
    public function testCircularReference()
503
    {
504
        $services = [
505
            'CircularOne' => CircularOne::class,
506
            'CircularTwo' => CircularTwo::class
507
        ];
508
        $injector = new Injector($services);
509
        $injector->setAutoScanProperties(true);
510
511
        $obj = $injector->get(NeedsBothCirculars::class);
512
513
        $this->assertTrue($obj->circularOne instanceof InjectorTest\CircularOne);
514
        $this->assertTrue($obj->circularTwo instanceof InjectorTest\CircularTwo);
515
    }
516
517
    public function testPrototypeObjects()
518
    {
519
        $services = [
520
            'CircularOne' => CircularOne::class,
521
            'CircularTwo' => CircularTwo::class,
522
            'NeedsBothCirculars' => [
523
                'class' => NeedsBothCirculars::class,
524
                'type' => 'prototype'
525
            ]
526
        ];
527
        $injector = new Injector($services);
528
        $injector->setAutoScanProperties(true);
529
        $obj1 = $injector->get('NeedsBothCirculars');
530
        $obj2 = $injector->get('NeedsBothCirculars');
531
532
        // if this was the same object, then $obj1->var would now be two
533
        $obj1->var = 'one';
534
        $obj2->var = 'two';
535
536
        $this->assertTrue($obj1->circularOne instanceof InjectorTest\CircularOne);
537
        $this->assertTrue($obj1->circularTwo instanceof InjectorTest\CircularTwo);
538
539
        $this->assertEquals($obj1->circularOne, $obj2->circularOne);
540
        $this->assertNotEquals($obj1, $obj2);
541
    }
542
543
    public function testSimpleInstantiation()
544
    {
545
        $services = [
546
            'CircularOne' => CircularOne::class,
547
            'CircularTwo' => CircularTwo::class
548
        ];
549
        $injector = new Injector($services);
550
551
        // similar to the above, but explicitly instantiating this object here
552
        $obj1 = $injector->create(NeedsBothCirculars::class);
553
        $obj2 = $injector->create(NeedsBothCirculars::class);
554
555
        // if this was the same object, then $obj1->var would now be two
556
        $obj1->var = 'one';
557
        $obj2->var = 'two';
558
559
        $this->assertEquals($obj1->circularOne, $obj2->circularOne);
560
        $this->assertNotEquals($obj1, $obj2);
561
    }
562
563
    public function testCreateWithConstructor()
564
    {
565
        $injector = new Injector();
566
        $obj = $injector->create(CircularTwo::class, 'param');
567
        $this->assertEquals($obj->otherVar, 'param');
568
    }
569
570
    public function testSimpleSingleton()
571
    {
572
        $injector = new Injector();
573
574
        $one = $injector->create(CircularOne::class);
575
        $two = $injector->create(CircularOne::class);
576
577
        $this->assertFalse($one === $two);
578
579
        $one = $injector->get(CircularTwo::class);
580
        $two = $injector->get(CircularTwo::class);
581
582
        $this->assertTrue($one === $two);
583
    }
584
585
    public function testOverridePriority()
586
    {
587
        $injector = new Injector();
588
        $injector->setAutoScanProperties(true);
589
        $config = [
590
            'SampleService' => [
591
                'src' => TEST_SERVICES . '/SampleService.php',
592
                'class' => SampleService::class,
593
                'priority' => 10,
594
            ]
595
        ];
596
597
        // load
598
        $injector->load($config);
599
600
        // inject
601
        $myObject = new InjectorTest\TestObject();
602
        $injector->inject($myObject);
603
604
        $this->assertInstanceOf(SampleService::class, $myObject->sampleService);
605
606
        $config = [
607
            [
608
                'src' => TEST_SERVICES . '/AnotherService.php',
609
                'class' => AnotherService::class,
610
                'id' => 'SampleService',
611
                'priority' => 1,
612
            ]
613
        ];
614
        // load
615
        $injector->load($config);
616
617
        $injector->inject($myObject);
618
        $this->assertInstanceOf(
619
            SampleService::class,
620
            $myObject->sampleService
621
        );
622
    }
623
624
    /**
625
     * Specific test method to illustrate various ways of setting a requirements backend
626
     */
627
    public function testRequirementsSettingOptions()
628
    {
629
        $injector = new Injector();
630
        $config = [
631
            OriginalRequirementsBackend::class,
632
            NewRequirementsBackend::class,
633
            DummyRequirements::class => [
634
                'constructor' => [
635
                    '%$' . OriginalRequirementsBackend::class
636
                ]
637
            ]
638
        ];
639
640
        $injector->load($config);
641
642
        $requirements = $injector->get(DummyRequirements::class);
643
        $this->assertInstanceOf(
644
            OriginalRequirementsBackend::class,
645
            $requirements->backend
646
        );
647
648
        // just overriding the definition here
649
        $injector->load(
650
            [
651
            DummyRequirements::class => [
652
                'constructor' => [
653
                    '%$' . NewRequirementsBackend::class
654
                ]
655
            ]
656
            ]
657
        );
658
659
        // requirements should have been reinstantiated with the new bean setting
660
        $requirements = $injector->get(DummyRequirements::class);
661
        $this->assertInstanceOf(
662
            NewRequirementsBackend::class,
663
            $requirements->backend
664
        );
665
    }
666
667
    /**
668
     * disabled for now
669
     */
670
    public function testStaticInjections()
671
    {
672
        $injector = new Injector();
673
        $config = [
674
            NewRequirementsBackend::class,
675
        ];
676
677
        $injector->load($config);
678
679
        $si = $injector->get(TestStaticInjections::class);
680
        $this->assertInstanceOf(
681
            NewRequirementsBackend::class,
682
            $si->backend
683
        );
684
    }
685
686
    public function testSetterInjections()
687
    {
688
        $injector = new Injector();
689
        $config = [
690
            NewRequirementsBackend::class,
691
        ];
692
693
        $injector->load($config);
694
695
        $si = $injector->get(TestSetterInjections::class);
696
        $this->assertInstanceOf(
697
            NewRequirementsBackend::class,
698
            $si->getBackend()
699
        );
700
    }
701
702
    public function testCustomObjectCreator()
703
    {
704
        $injector = new Injector();
705
        $injector->setObjectCreator(new InjectorTest\SSObjectCreator($injector));
706
        $config = [
707
            OriginalRequirementsBackend::class,
708
            DummyRequirements::class => [
709
                'class' => DummyRequirements::class . '(\'%$' . OriginalRequirementsBackend::class . '\')'
710
            ]
711
        ];
712
        $injector->load($config);
713
714
        $requirements = $injector->get(DummyRequirements::class);
715
        $this->assertEquals(OriginalRequirementsBackend::class, get_class($requirements->backend));
716
    }
717
718
    public function testInheritedConfig()
719
    {
720
721
        // Test that child class does not automatically inherit config
722
        $injector = new Injector(['locator' => SilverStripeServiceConfigurationLocator::class]);
723
        Config::modify()->merge(
724
            Injector::class,
725
            MyParentClass::class,
726
            [
727
            'properties' => ['one' => 'the one'],
728
            'class' => MyParentClass::class,
729
            ]
730
        );
731
        $obj = $injector->get(MyParentClass::class);
732
        $this->assertInstanceOf(MyParentClass::class, $obj);
733
        $this->assertEquals($obj->one, 'the one');
734
735
        // Class isn't inherited and parent properties are ignored
736
        $obj = $injector->get(MyChildClass::class);
737
        $this->assertInstanceOf(MyChildClass::class, $obj);
738
        $this->assertNotEquals($obj->one, 'the one');
739
740
        // Set child class as alias
741
        $injector = new Injector(
742
            [
743
            'locator' => SilverStripeServiceConfigurationLocator::class
744
            ]
745
        );
746
        Config::modify()->merge(
747
            Injector::class,
748
            MyChildClass::class,
749
            '%$' . MyParentClass::class
750
        );
751
752
        // Class isn't inherited and parent properties are ignored
753
        $obj = $injector->get(MyChildClass::class);
754
        $this->assertInstanceOf(MyParentClass::class, $obj);
755
        $this->assertEquals($obj->one, 'the one');
756
    }
757
758
    public function testSameNamedSingeltonPrototype()
759
    {
760
        $injector = new Injector();
761
762
        // get a singleton object
763
        $object = $injector->get(NeedsBothCirculars::class);
764
        $object->var = 'One';
765
766
        $again = $injector->get(NeedsBothCirculars::class);
767
        $this->assertEquals($again->var, 'One');
768
769
        // create a NEW instance object
770
        $new = $injector->create(NeedsBothCirculars::class);
771
        $this->assertNull($new->var);
772
773
        // this will trigger a problem below
774
        $new->var = 'Two';
775
776
        $again = $injector->get(NeedsBothCirculars::class);
777
        $this->assertEquals($again->var, 'One');
778
    }
779
780
    public function testConvertServicePropertyOnCreate()
781
    {
782
        // make sure convert service property is not called on direct calls to create, only on configured
783
        // declarations to avoid un-needed function calls
784
        $injector = new Injector();
785
        $item = $injector->create(ConstructableObject::class, '%$' . TestObject::class);
786
        $this->assertEquals('%$' . TestObject::class, $item->property);
787
788
        // do it again but have test object configured as a constructor dependency
789
        $injector = new Injector();
790
        $config = [
791
            ConstructableObject::class => [
792
                'constructor' => [
793
                    '%$' . TestObject::class
794
                ]
795
            ]
796
        ];
797
798
        $injector->load($config);
799
        $item = $injector->get(ConstructableObject::class);
800
        $this->assertTrue($item->property instanceof InjectorTest\TestObject);
801
802
        // and with a configured object defining TestObject to be something else!
803
        $injector = new Injector(['locator' => InjectorTest\InjectorTestConfigLocator::class]);
804
        $config = [
805
            ConstructableObject::class => [
806
                'constructor' => [
807
                    '%$' . TestObject::class
808
                ]
809
            ],
810
        ];
811
812
        $injector->load($config);
813
        $item = $injector->get(ConstructableObject::class);
814
        $this->assertTrue($item->property instanceof InjectorTest\ConstructableObject);
815
816
        $this->assertInstanceOf(OtherTestObject::class, $item->property->property);
817
    }
818
819
    /**
820
     * @dataProvider provideConvertServicePropertyBackTicks
821
     */
822
    public function testConvertServicePropertyBackTicks($value, $expected)
823
    {
824
        Environment::setEnv('INJECTOR_TEST_CSP_A', 'ABC');
825
        Environment::setEnv('INJECTOR_TEST_CSP_B', 'DEF');
826
        Environment::setEnv('INJECTOR_TEST_CSP_C', 'GHI');
827
        $actual = Injector::inst()->convertServiceProperty($value);
828
        $this->assertSame($expected, $actual);
829
    }
830
831
    public function provideConvertServicePropertyBackTicks()
832
    {
833
        return [
834
            ['`INJECTOR_TEST_CSP_A`', 'ABC'],
835
            ['`INJECTOR_TEST_CSP_A`:`INJECTOR_TEST_CSP_B`', 'ABC:DEF'],
836
            ['`INJECTOR_TEST_CSP_A` some text `INJECTOR_TEST_CSP_B`', 'ABC some text DEF'],
837
            ['`INJECTOR_TEST_CSP_A``INJECTOR_TEST_CSP_B`', 'ABCDEF'],
838
            ['`INJECTOR_TEST_CSP_A`:`INJECTOR_TEST_CSP_B``INJECTOR_TEST_CSP_C`', 'ABC:DEFGHI'],
839
            ['`INJECTOR_TEST_CSP_A`:`INJECTOR_TEST_CSP_X`', 'ABC:'],
840
            ['`INJECTOR_TEST_CSP_X`', null],
841
            ['lorem `INJECTOR_TEST_CSP_A` ipsum', 'lorem `INJECTOR_TEST_CSP_A` ipsum'],
842
        ];
843
    }
844
845
    public function testNamedServices()
846
    {
847
        $injector = new Injector();
848
        $service  = new TestObject();
849
        $service->setSomething('injected');
850
851
        // Test registering with non-class name
852
        $injector->registerService($service, 'NamedService');
853
        $this->assertTrue($injector->has('NamedService'));
854
        $this->assertEquals($service, $injector->get('NamedService'));
855
856
        // Unregister service by name
857
        $injector->unregisterNamedObject('NamedService');
858
        $this->assertFalse($injector->has('NamedService'));
859
860
        // Test registered with class name
861
        $injector->registerService($service);
862
        $this->assertTrue($injector->has(TestObject::class));
863
        $this->assertEquals($service, $injector->get(TestObject::class));
864
865
        // Unregister service by class
866
        $injector->unregisterNamedObject(TestObject::class);
867
        $this->assertFalse($injector->has(TestObject::class));
868
    }
869
870
    public function testCreateConfiggedObjectWithCustomConstructorArgs()
871
    {
872
        // need to make sure that even if the config defines some constructor params,
873
        // that we take our passed in constructor args instead
874
        $injector = new Injector(['locator' => InjectorTest\InjectorTestConfigLocator::class]);
875
876
        $item = $injector->create('ConfigConstructor', 'othervalue');
877
        $this->assertEquals($item->property, 'othervalue');
878
    }
879
880
    /**
881
     * Tests creating a service with a custom factory.
882
     */
883
    public function testCustomFactory()
884
    {
885
        $injector = new Injector(
886
            [
887
            'service' => ['factory' => 'factory', 'constructor' => [1, 2, 3]]
888
            ]
889
        );
890
891
        $factory = $this->getMockBuilder(Factory::class)->getMock();
892
        $factory
893
            ->expects($this->once())
894
            ->method('create')
895
            ->with($this->equalTo('service'), $this->equalTo([1, 2, 3]))
896
            ->will(
897
                $this->returnCallback(
898
                    function ($args) {
0 ignored issues
show
Unused Code introduced by
The parameter $args 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

898
                    function (/** @scrutinizer ignore-unused */ $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...
899
                        return new InjectorTest\TestObject();
900
                    }
901
                )
902
            );
903
904
        $injector->registerService($factory, 'factory');
905
906
        $this->assertInstanceOf(TestObject::class, $injector->get('service'));
907
    }
908
909
    public function testMethods()
910
    {
911
        // do it again but have test object configured as a constructor dependency
912
        $injector = new Injector();
913
        $config = [
914
            'A' => [
915
                'class' => TestObject::class,
916
            ],
917
            'B' => [
918
                'class' => TestObject::class,
919
            ],
920
            'TestService' => [
921
                'class' => TestObject::class,
922
                'calls' => [
923
                    ['myMethod', ['%$A']],
924
                    ['myMethod', ['%$B']],
925
                    ['noArgMethod']
926
                ]
927
            ]
928
        ];
929
930
        $injector->load($config);
931
        $item = $injector->get('TestService');
932
        $this->assertTrue($item instanceof InjectorTest\TestObject);
933
        $this->assertEquals(
934
            [$injector->get('A'), $injector->get('B'), 'noArgMethod called'],
935
            $item->methodCalls
936
        );
937
    }
938
939
    public function testNonExistentMethods()
940
    {
941
        $this->expectException(\InvalidArgumentException::class);
942
        $injector = new Injector();
943
        $config = [
944
            'TestService' => [
945
                'class' => TestObject::class,
946
                'calls' => [
947
                    ['thisDoesntExist']
948
                ]
949
            ]
950
        ];
951
952
        $injector->load($config);
953
        $item = $injector->get('TestService');
0 ignored issues
show
Unused Code introduced by
The assignment to $item is dead and can be removed.
Loading history...
954
    }
955
956
    public function testProtectedMethods()
957
    {
958
        $this->expectException(\InvalidArgumentException::class);
959
        $injector = new Injector();
960
        $config = [
961
            'TestService' => [
962
                'class' => TestObject::class,
963
                'calls' => [
964
                    ['protectedMethod']
965
                ]
966
            ]
967
        ];
968
969
        $injector->load($config);
970
        $item = $injector->get('TestService');
0 ignored issues
show
Unused Code introduced by
The assignment to $item is dead and can be removed.
Loading history...
971
    }
972
973
    public function testTooManyArrayValues()
974
    {
975
        $this->expectException(\InvalidArgumentException::class);
976
        $injector = new Injector();
977
        $config = [
978
            'TestService' => [
979
                'class' => TestObject::class,
980
                'calls' => [
981
                    ['method', ['args'], 'what is this?']
982
                ]
983
            ]
984
        ];
985
986
        $injector->load($config);
987
        $item = $injector->get('TestService');
0 ignored issues
show
Unused Code introduced by
The assignment to $item is dead and can be removed.
Loading history...
988
    }
989
990
    public function testGetThrowsOnNotFound()
991
    {
992
        $this->expectException(InjectorNotFoundException::class);
993
        $injector = new Injector();
994
        $injector->get('UnknownService');
995
    }
996
997
    public function testGetTrimsWhitespaceFromNames()
998
    {
999
        $injector = new Injector;
1000
1001
        $this->assertInstanceOf(MyChildClass::class, $injector->get('    ' . MyChildClass::class . '     '));
1002
    }
1003
1004
    /**
1005
     * Test nesting of injector
1006
     */
1007
    public function testNest()
1008
    {
1009
1010
        // Outer nest to avoid interference with other
1011
        Injector::nest();
1012
        $this->nestingLevel++;
1013
1014
        // Test services
1015
        $config = [
1016
            NewRequirementsBackend::class,
1017
        ];
1018
        Injector::inst()->load($config);
1019
        $si = Injector::inst()->get(TestStaticInjections::class);
1020
        $this->assertInstanceOf(TestStaticInjections::class, $si);
1021
        $this->assertInstanceOf(NewRequirementsBackend::class, $si->backend);
1022
        $this->assertInstanceOf(MyParentClass::class, Injector::inst()->get(MyParentClass::class));
1023
        $this->assertInstanceOf(MyChildClass::class, Injector::inst()->get(MyChildClass::class));
1024
1025
        // Test that nested injector values can be overridden
1026
        Injector::nest();
1027
        $this->nestingLevel++;
1028
        Injector::inst()->unregisterObjects([
1029
            TestStaticInjections::class,
1030
            MyParentClass::class,
1031
        ]);
1032
        $newsi = Injector::inst()->get(TestStaticInjections::class);
1033
        $newsi->backend = new InjectorTest\OriginalRequirementsBackend();
1034
        Injector::inst()->registerService($newsi, TestStaticInjections::class);
1035
        Injector::inst()->registerService(new InjectorTest\MyChildClass(), MyParentClass::class);
1036
1037
        // Check that these overridden values are retrievable
1038
        $si = Injector::inst()->get(TestStaticInjections::class);
1039
        $this->assertInstanceOf(TestStaticInjections::class, $si);
1040
        $this->assertInstanceOf(OriginalRequirementsBackend::class, $si->backend);
1041
        $this->assertInstanceOf(MyParentClass::class, Injector::inst()->get(MyParentClass::class));
1042
        $this->assertInstanceOf(MyParentClass::class, Injector::inst()->get(MyChildClass::class));
1043
1044
        // Test that unnesting restores expected behaviour
1045
        Injector::unnest();
1046
        $this->nestingLevel--;
1047
        $si = Injector::inst()->get(TestStaticInjections::class);
1048
        $this->assertInstanceOf(TestStaticInjections::class, $si);
1049
        $this->assertInstanceOf(NewRequirementsBackend::class, $si->backend);
1050
        $this->assertInstanceOf(MyParentClass::class, Injector::inst()->get(MyParentClass::class));
1051
        $this->assertInstanceOf(MyChildClass::class, Injector::inst()->get(MyChildClass::class));
1052
1053
        // Test reset of cache
1054
        Injector::inst()->unregisterObjects([
1055
            TestStaticInjections::class,
1056
            MyParentClass::class,
1057
        ]);
1058
        $si = Injector::inst()->get(TestStaticInjections::class);
1059
        $this->assertInstanceOf(TestStaticInjections::class, $si);
1060
        $this->assertInstanceOf(NewRequirementsBackend::class, $si->backend);
1061
        $this->assertInstanceOf(MyParentClass::class, Injector::inst()->get(MyParentClass::class));
1062
        $this->assertInstanceOf(MyChildClass::class, Injector::inst()->get(MyChildClass::class));
1063
1064
        // Return to nestingLevel 0
1065
        Injector::unnest();
1066
        $this->nestingLevel--;
1067
    }
1068
}
1069