Passed
Push — master ( 102f9d...99342a )
by Robbie
09:22
created

InjectorTest::testSimpleInstantiation()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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

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

1077
        $this->assertSame('bar', $member->/** @scrutinizer ignore-call */ someMethod());
Loading history...
1078
    }
1079
}
1080