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

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

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

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

1069
        $this->assertSame('bar', $member->/** @scrutinizer ignore-call */ someMethod());
Loading history...
1070
    }
1071
}
1072