Passed
Push — master ( 545e22...b70257 )
by Robbie
11:19 queued 10s
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));
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

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

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