Passed
Push — zero-is-false ( 5bf3f2...d7e1e1 )
by Sam
08:16
created

InjectorTest::testExtendedExtensions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 9
nc 1
nop 0
dl 0
loc 15
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Tests\Injector;
4
5
use InvalidArgumentException;
6
use SilverStripe\Core\Config\Config;
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()
48
    {
49
        parent::setUp();
50
51
        $this->nestingLevel = 0;
52
    }
53
54
    protected function tearDown()
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 = array(
79
            'SampleService' => array(
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 = array(
111
            'SomeClass' => array(
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 = array(
125
            'AnotherService' => array(
126
                'class' => AnotherService::class,
127
                'src' => TEST_SERVICES . '/AnotherService.php',
128
                'properties' => array('config_property' => 'Value'),
129
            ),
130
            'SampleService' => array(
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 = array(
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 = array(
181
            'SampleService' => array(
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 = array(
201
            'SampleService' => array(
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 = array(
221
            AnotherService::class => array(
222
                'src' => TEST_SERVICES . '/AnotherService.php',
223
                'properties' => array(
224
                    'filters' => array(
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 = array(
245
            AnotherService::class => array(
246
                'properties' => array(
247
                    'filters' => array(
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 = array(
276
            'SampleService' => array(
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 = array(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 = array(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 = array(
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 = array(
361
            'SampleService' => array(
362
                'src' => TEST_SERVICES . '/SampleService.php',
363
                'class' => SampleService::class,
364
                'constructor' => array(
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 = array(
378
            'AnotherService' => AnotherService::class,
379
            'SampleService' => array(
380
                'src' => TEST_SERVICES . '/SampleService.php',
381
                'class' => SampleService::class,
382
                'constructor' => array(
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 = array(
399
            'SampleService' => array(
400
                'src' => TEST_SERVICES . '/SampleService.php',
401
                'class' => SampleService::class,
402
                'constructor' => array(
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 = array(
417
            'SampleService' => array(
418
                'type'  => 'prototype',
419
                'src' => TEST_SERVICES . '/SampleService.php',
420
                'class' => SampleService::class,
421
                'constructor' => array(
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 = array(
445
            'SampleService' => array(
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 = array(
480
            'SampleService' => array(
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 = array(
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 = array(
520
            'CircularOne' => CircularOne::class,
521
            'CircularTwo' => CircularTwo::class,
522
            'NeedsBothCirculars' => array(
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 = array(
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 = array(
590
            'SampleService' => array(
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 = array(
607
            array(
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 = array(
631
            OriginalRequirementsBackend::class,
632
            NewRequirementsBackend::class,
633
            DummyRequirements::class => array(
634
                'constructor' => array(
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
            array(
651
            DummyRequirements::class => array(
652
                'constructor' => array(
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 = array(
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 = array(
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 = array(
707
            OriginalRequirementsBackend::class,
708
            DummyRequirements::class => array(
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(array('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
            array(
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 = array(
791
            ConstructableObject::class => array(
792
                'constructor' => array(
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(array('locator' => InjectorTest\InjectorTestConfigLocator::class));
804
        $config = array(
805
            ConstructableObject::class => array(
806
                'constructor' => array(
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
    public function testNamedServices()
820
    {
821
        $injector = new Injector();
822
        $service  = new TestObject();
823
        $service->setSomething('injected');
824
825
        // Test registering with non-class name
826
        $injector->registerService($service, 'NamedService');
827
        $this->assertTrue($injector->has('NamedService'));
828
        $this->assertEquals($service, $injector->get('NamedService'));
829
830
        // Unregister service by name
831
        $injector->unregisterNamedObject('NamedService');
832
        $this->assertFalse($injector->has('NamedService'));
833
834
        // Test registered with class name
835
        $injector->registerService($service);
836
        $this->assertTrue($injector->has(TestObject::class));
837
        $this->assertEquals($service, $injector->get(TestObject::class));
838
839
        // Unregister service by class
840
        $injector->unregisterNamedObject(TestObject::class);
841
        $this->assertFalse($injector->has(TestObject::class));
842
    }
843
844
    public function testCreateConfiggedObjectWithCustomConstructorArgs()
845
    {
846
        // need to make sure that even if the config defines some constructor params,
847
        // that we take our passed in constructor args instead
848
        $injector = new Injector(array('locator' => InjectorTest\InjectorTestConfigLocator::class));
849
850
        $item = $injector->create('ConfigConstructor', 'othervalue');
851
        $this->assertEquals($item->property, 'othervalue');
852
    }
853
854
    /**
855
     * Tests creating a service with a custom factory.
856
     */
857
    public function testCustomFactory()
858
    {
859
        $injector = new Injector(
860
            array(
861
            'service' => array('factory' => 'factory', 'constructor' => array(1, 2, 3))
862
            )
863
        );
864
865
        $factory = $this->getMockBuilder(Factory::class)->getMock();
866
        $factory
867
            ->expects($this->once())
868
            ->method('create')
869
            ->with($this->equalTo('service'), $this->equalTo(array(1, 2, 3)))
870
            ->will(
871
                $this->returnCallback(
872
                    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

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