Passed
Push — 4.3 ( 84c8da...41dc92 )
by
unknown
09:45
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(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