Passed
Push — pulls/4.6/service-extensions ( 65a4a8 )
by Robbie
07:58
created

testExtensionsAppliedViaANonExistentServiceAsAControl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Tests;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Core\ClassInfo;
7
use SilverStripe\Core\Config\Config;
8
use SilverStripe\Core\Extension;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\Core\Tests\ObjectTest\BaseObject;
11
use SilverStripe\Core\Tests\ObjectTest\ExtendTest1;
12
use SilverStripe\Core\Tests\ObjectTest\ExtendTest2;
13
use SilverStripe\Core\Tests\ObjectTest\ExtendTest3;
14
use SilverStripe\Core\Tests\ObjectTest\ExtendTest4;
15
use SilverStripe\Core\Tests\ObjectTest\ExtendTest5;
16
use SilverStripe\Core\Tests\ObjectTest\ExtensionRemoveTest;
17
use SilverStripe\Core\Tests\ObjectTest\ExtensionService;
18
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest;
19
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest2;
20
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest3;
21
use SilverStripe\Core\Tests\ObjectTest\MyObject;
22
use SilverStripe\Core\Tests\ObjectTest\MySubObject;
23
use SilverStripe\Core\Tests\ObjectTest\TestExtension;
24
use SilverStripe\Dev\SapphireTest;
25
use SilverStripe\Versioned\Versioned;
26
27
/**
28
 * @todo tests for addStaticVars()
29
 * @todo tests for setting statics which are not defined on the object as built-in PHP statics
30
 * @todo tests for setting statics through extensions (#2387)
31
 * @skipUpgrade
32
 */
33
class ObjectTest extends SapphireTest
34
{
35
36
    protected function setUp()
37
    {
38
        parent::setUp();
39
        Injector::inst()->unregisterObjects([
40
            Extension::class,
41
            BaseObject::class,
42
        ]);
43
    }
44
45
    public function testHasmethodBehaviour()
46
    {
47
        $obj = new ObjectTest\ExtendTest();
48
49
        $this->assertTrue($obj->hasMethod('extendableMethod'), "Extension method found in original spelling");
50
        $this->assertTrue($obj->hasMethod('ExTendableMethod'), "Extension method found case-insensitive");
51
52
        $objs = array();
53
        $objs[] = new ObjectTest\T2();
54
        $objs[] = new ObjectTest\T2();
55
        $objs[] = new ObjectTest\T2();
56
57
        // All these methods should exist and return true
58
        $trueMethods = [
59
            'testMethod',
60
            'otherMethod',
61
            'someMethod',
62
            't1cMethod',
63
            'normalMethod',
64
            'failoverCallback'
65
        ];
66
67
        foreach ($objs as $i => $obj) {
68
            foreach ($trueMethods as $method) {
69
                $methodU = strtoupper($method);
70
                $methodL = strtoupper($method);
71
                $this->assertTrue($obj->hasMethod($method), "Test that obj#$i has method $method");
72
                $this->assertTrue($obj->hasMethod($methodU), "Test that obj#$i has method $methodU");
73
                $this->assertTrue($obj->hasMethod($methodL), "Test that obj#$i has method $methodL");
74
75
                $this->assertTrue($obj->$method(), "Test that obj#$i can call method $method");
76
                $this->assertTrue($obj->$methodU(), "Test that obj#$i can call method $methodU");
77
                $this->assertTrue($obj->$methodL(), "Test that obj#$i can call method $methodL");
78
            }
79
80
            $this->assertTrue($obj->hasMethod('Wrapping'), "Test that obj#$i has method Wrapping");
81
            $this->assertTrue($obj->hasMethod('WRAPPING'), "Test that obj#$i has method WRAPPING");
82
            $this->assertTrue($obj->hasMethod('wrapping'), "Test that obj#$i has method wrapping");
83
84
            $this->assertEquals("Wrapping", $obj->Wrapping(), "Test that obj#$i can call method Wrapping");
0 ignored issues
show
Bug introduced by
The method Wrapping() does not exist on SilverStripe\Core\Tests\ObjectTest\T2. 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

84
            $this->assertEquals("Wrapping", $obj->/** @scrutinizer ignore-call */ Wrapping(), "Test that obj#$i can call method Wrapping");
Loading history...
85
            $this->assertEquals("Wrapping", $obj->WRAPPING(), "Test that obj#$i can call method WRAPPIGN");
0 ignored issues
show
Bug introduced by
The method WRAPPING() does not exist on SilverStripe\Core\Tests\ObjectTest\T2. 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

85
            $this->assertEquals("Wrapping", $obj->/** @scrutinizer ignore-call */ WRAPPING(), "Test that obj#$i can call method WRAPPIGN");
Loading history...
86
            $this->assertEquals("Wrapping", $obj->wrapping(), "Test that obj#$i can call method wrapping");
0 ignored issues
show
Bug introduced by
The method wrapping() does not exist on SilverStripe\Core\Tests\ObjectTest\T2. 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

86
            $this->assertEquals("Wrapping", $obj->/** @scrutinizer ignore-call */ wrapping(), "Test that obj#$i can call method wrapping");
Loading history...
87
        }
88
    }
89
90
    public function testSingletonCreation()
91
    {
92
        $myObject = MyObject::singleton();
93
        $this->assertInstanceOf(
94
            MyObject::class,
95
            $myObject,
96
            'singletons are creating a correct class instance'
97
        );
98
        $mySubObject = MySubObject::singleton();
99
        $this->assertInstanceOf(
100
            MySubObject::class,
101
            $mySubObject,
102
            'singletons are creating a correct subclass instance'
103
        );
104
105
        $myFirstObject = MyObject::singleton();
106
        $mySecondObject = MyObject::singleton();
107
        $this->assertTrue(
108
            $myFirstObject === $mySecondObject,
109
            'singletons are using the same object on subsequent calls'
110
        );
111
    }
112
113
    public function testStaticGetterMethod()
114
    {
115
        $obj = singleton(MyObject::class);
116
        $this->assertEquals(
117
            'MyObject',
118
            $obj->stat('mystaticProperty'),
119
            'Uninherited statics through stat() on a singleton behave the same as built-in PHP statics'
120
        );
121
    }
122
123
    public function testStaticInheritanceGetters()
124
    {
125
        $subObj = singleton(MyObject::class);
126
        $this->assertEquals(
127
            $subObj->stat('mystaticProperty'),
128
            'MyObject',
129
            'Statics defined on a parent class are available through stat() on a subclass'
130
        );
131
    }
132
133
    public function testStaticSettingOnSingletons()
134
    {
135
        $singleton1 = singleton(MyObject::class);
136
        $singleton2 = singleton(MyObject::class);
137
        $singleton1->set_stat('mystaticProperty', 'changed');
138
        $this->assertEquals(
139
            $singleton2->stat('mystaticProperty'),
140
            'changed',
141
            'Statics setting is populated throughout singletons without explicitly clearing cache'
142
        );
143
    }
144
145
    public function testStaticSettingOnInstances()
146
    {
147
        $instance1 = new ObjectTest\MyObject();
148
        $instance2 = new ObjectTest\MyObject();
149
        $instance1->set_stat('mystaticProperty', 'changed');
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Core\Tests\...\BaseObject::set_stat() has been deprecated: 5.0 Use ->config()->set() instead ( Ignorable by Annotation )

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

149
        /** @scrutinizer ignore-deprecated */ $instance1->set_stat('mystaticProperty', 'changed');

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
150
        $this->assertEquals(
151
            $instance2->stat('mystaticProperty'),
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Core\Tests\...Test\BaseObject::stat() has been deprecated: 5.0 Use ->config()->get() instead ( Ignorable by Annotation )

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

151
            /** @scrutinizer ignore-deprecated */ $instance2->stat('mystaticProperty'),

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
152
            'changed',
153
            'Statics setting through set_stat() is populated throughout instances without explicitly clearing cache'
154
        );
155
    }
156
157
    /**
158
     * Tests that {@link Object::create()} correctly passes all arguments to the new object
159
     */
160
    public function testCreateWithArgs()
161
    {
162
        $createdObj = ObjectTest\CreateTest::create('arg1', 'arg2', array(), null, 'arg5');
163
        $this->assertEquals($createdObj->constructArguments, array('arg1', 'arg2', array(), null, 'arg5'));
164
    }
165
166
    public function testCreateLateStaticBinding()
167
    {
168
        $createdObj = ObjectTest\CreateTest::create('arg1', 'arg2', array(), null, 'arg5');
169
        $this->assertEquals($createdObj->constructArguments, array('arg1', 'arg2', array(), null, 'arg5'));
170
    }
171
172
    /**
173
     * Tests {@link Object::singleton()}
174
     */
175
    public function testSingleton()
176
    {
177
        $inst = Controller::singleton();
178
        $this->assertInstanceOf(Controller::class, $inst);
179
        $inst2 = Controller::singleton();
180
        $this->assertSame($inst2, $inst);
181
    }
182
183
    public function testGetExtensions()
184
    {
185
        $this->assertEquals(
186
            array(
187
                'SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1',
188
                "SilverStripe\\Core\\Tests\\ObjectTest\\ExtendTest2",
189
            ),
190
            ExtensionTest::get_extensions()
191
        );
192
        $this->assertEquals(
193
            array(
194
                'SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1',
195
                "SilverStripe\\Core\\Tests\\ObjectTest\\ExtendTest2('FOO', 'BAR')",
196
            ),
197
            ExtensionTest::get_extensions(null, true)
198
        );
199
        $inst = new ExtensionTest();
200
        $extensions = $inst->getExtensionInstances();
201
        $this->assertCount(2, $extensions);
202
        $this->assertArrayHasKey(ExtendTest1::class, $extensions);
203
        $this->assertInstanceOf(
204
            ExtendTest1::class,
205
            $extensions[ExtendTest1::class]
206
        );
207
        $this->assertArrayHasKey(ExtendTest2::class, $extensions);
208
        $this->assertInstanceOf(
209
            ExtendTest2::class,
210
            $extensions[ExtendTest2::class]
211
        );
212
        $this->assertInstanceOf(
213
            ExtendTest1::class,
214
            $inst->getExtensionInstance(ExtendTest1::class)
215
        );
216
        $this->assertInstanceOf(
217
            ExtendTest2::class,
218
            $inst->getExtensionInstance(ExtendTest2::class)
219
        );
220
    }
221
222
    /**
223
     * Tests {@link Object::has_extension()}, {@link Object::add_extension()}
224
     */
225
    public function testHasAndAddExtension()
226
    {
227
        // ObjectTest_ExtendTest1 is built in via $extensions
228
        $this->assertTrue(
229
            ExtensionTest::has_extension('SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1'),
230
            "Extensions are detected when set on Object::\$extensions on has_extension() without case-sensitivity"
231
        );
232
        $this->assertTrue(
233
            ExtensionTest::has_extension(ExtendTest1::class),
234
            "Extensions are detected when set on Object::\$extensions on has_extension() without case-sensitivity"
235
        );
236
        $this->assertTrue(
237
            singleton(ExtensionTest::class)->hasExtension(ExtendTest1::class),
238
            "Extensions are detected when set on Object::\$extensions on instance hasExtension() without"
239
            . " case-sensitivity"
240
        );
241
242
        // ObjectTest_ExtendTest2 is built in via $extensions (with parameters)
243
        $this->assertTrue(
244
            ExtensionTest::has_extension(ExtendTest2::class),
245
            "Extensions are detected with static has_extension() when set on Object::\$extensions with"
246
            . " additional parameters"
247
        );
248
        $this->assertTrue(
249
            singleton(ExtensionTest::class)->hasExtension(ExtendTest2::class),
250
            "Extensions are detected with instance hasExtension() when set on Object::\$extensions with"
251
            . " additional parameters"
252
        );
253
        $this->assertFalse(
254
            ExtensionTest::has_extension(ExtendTest3::class),
255
            "Other extensions available in the system are not present unless explicitly added to this object"
256
            . " when checking through has_extension()"
257
        );
258
        $this->assertFalse(
259
            singleton(ExtensionTest::class)->hasExtension(ExtendTest3::class),
260
            "Other extensions available in the system are not present unless explicitly added to this object"
261
            . " when checking through instance hasExtension()"
262
        );
263
264
        // ObjectTest_ExtendTest3 is added manually
265
        ExtensionTest::add_extension(ExtendTest3::class . '("Param")');
266
        $this->assertTrue(
267
            ExtensionTest::has_extension(ExtendTest3::class),
268
            "Extensions are detected with static has_extension() when added through add_extension()"
269
        );
270
        // ExtendTest4 is added manually
271
        ExtensionTest3::add_extension(ExtendTest4::class . '("Param")');
272
        // test against ObjectTest_ExtendTest3, not ObjectTest_ExtendTest3
273
        $this->assertTrue(
274
            ExtensionTest3::has_extension(ExtendTest4::class),
275
            "Extensions are detected with static has_extension() when added through add_extension()"
276
        );
277
        // test against ObjectTest_ExtendTest3, not ExtendTest4 to test if it picks up
278
        // the sub classes of ObjectTest_ExtendTest3
279
        $this->assertTrue(
280
            ExtensionTest3::has_extension(ExtendTest3::class),
281
            "Sub-Extensions are detected with static has_extension() when added through add_extension()"
282
        );
283
        // strictly test against ObjectTest_ExtendTest3, not ExtendTest4 to test if it picks up
284
        // the sub classes of ObjectTest_ExtendTest3
285
        $this->assertFalse(
286
            ExtensionTest3::has_extension(ExtendTest3::class, null, true),
287
            "Sub-Extensions are detected with static has_extension() when added through add_extension()"
288
        );
289
        // a singleton() wouldn't work as its already initialized
290
        $objectTest_ExtensionTest = new ExtensionTest();
291
        $this->assertTrue(
292
            $objectTest_ExtensionTest->hasExtension(ExtendTest3::class),
293
            "Extensions are detected with instance hasExtension() when added through add_extension()"
294
        );
295
296
        // load in a custom implementation
297
        Injector::inst()->registerService(new ExtendTest5(), ExtendTest4::class);
298
        $this->assertTrue(
299
            ExtensionTest3::has_extension(ExtendTest5::class),
300
            "Injected sub-extensions are detected with static has_extension() when added through add_extension()"
301
        );
302
303
        // @todo At the moment, this does NOT remove the extension due to parameterized naming,
304
        //  meaning the extension will remain added in further test cases
305
        ExtensionTest::remove_extension(ExtendTest3::class);
306
    }
307
308
    public function testRemoveExtension()
309
    {
310
        // manually add ObjectTest_ExtendTest2
311
        ObjectTest\ExtensionRemoveTest::add_extension(ExtendTest2::class);
312
        $this->assertTrue(
313
            ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class),
314
            "Extension added through \$add_extension() are added correctly"
315
        );
316
317
        ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest2::class);
318
        $this->assertFalse(
319
            ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class),
320
            "Extension added through \$add_extension() are detected as removed in has_extension()"
321
        );
322
        $this->assertFalse(
323
            singleton(ExtensionRemoveTest::class)->hasExtension(ExtendTest2::class),
324
            "Extensions added through \$add_extension() are detected as removed in instances through hasExtension()"
325
        );
326
327
        // ObjectTest_ExtendTest1 is already present in $extensions
328
        ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest1::class);
329
330
        $this->assertFalse(
331
            ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest1::class),
332
            "Extension added through \$extensions are detected as removed in has_extension()"
333
        );
334
335
        $objectTest_ExtensionRemoveTest = new ObjectTest\ExtensionRemoveTest();
336
        $this->assertFalse(
337
            $objectTest_ExtensionRemoveTest->hasExtension(ExtendTest1::class),
338
            "Extensions added through \$extensions are detected as removed in instances through hasExtension()"
339
        );
340
    }
341
342
    public function testRemoveExtensionWithParameters()
343
    {
344
        ObjectTest\ExtensionRemoveTest::add_extension(ExtendTest2::class . '("MyParam")');
345
346
        $this->assertTrue(
347
            ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class),
348
            "Extension added through \$add_extension() are added correctly"
349
        );
350
351
        ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest2::class);
352
        $this->assertFalse(
353
            ExtensionRemoveTest::has_extension(ExtendTest2::class),
354
            "Extension added through \$add_extension() are detected as removed in has_extension()"
355
        );
356
357
        $objectTest_ExtensionRemoveTest = new ObjectTest\ExtensionRemoveTest();
358
        $this->assertFalse(
359
            $objectTest_ExtensionRemoveTest->hasExtension(ExtendTest2::class),
360
            "Extensions added through \$extensions are detected as removed in instances through hasExtension()"
361
        );
362
    }
363
364
    public function testIsA()
365
    {
366
        $this->assertTrue(ObjectTest\MyObject::create() instanceof ObjectTest\BaseObject);
367
        $this->assertTrue(ObjectTest\MyObject::create() instanceof ObjectTest\MyObject);
368
    }
369
370
    /**
371
     * Tests {@link Object::hasExtension() and Object::getExtensionInstance()}
372
     */
373
    public function testExtInstance()
374
    {
375
        $obj = new ExtensionTest2();
376
377
        $this->assertTrue($obj->hasExtension(TestExtension::class));
378
        $this->assertTrue($obj->getExtensionInstance(TestExtension::class) instanceof ObjectTest\TestExtension);
379
    }
380
381
    public function testExtend()
382
    {
383
        $object = new ObjectTest\ExtendTest();
384
        $argument = 'test';
385
386
        $this->assertEquals($object->extend('extendableMethod'), array('ExtendTest2()'));
387
        $this->assertEquals($object->extend('extendableMethod', $argument), array('ExtendTest2(modified)'));
388
        $this->assertEquals($argument, 'modified');
389
390
        $this->assertEquals(
391
            array('ExtendTest()', 'ExtendTest2()'),
392
            $object->invokeWithExtensions('extendableMethod')
393
        );
394
        $arg1 = 'test';
395
        $arg2 = 'bob';
396
        $this->assertEquals(
397
            array('ExtendTest(test,bob)', 'ExtendTest2(modified,objectmodified)'),
398
            $object->invokeWithExtensions('extendableMethod', $arg1, $arg2)
399
        );
400
        $this->assertEquals('modified', $arg1);
401
        $this->assertEquals('objectmodified', $arg2);
402
403
        $object2 = new ObjectTest\Extending();
404
        $first = 1;
405
        $second = 2;
406
        $third = 3;
407
        $result = $object2->getResults($first, $second, $third);
408
        $this->assertEquals(
409
            array(array('before', 'extension', 'after')),
410
            $result
411
        );
412
        $this->assertEquals(31, $first);
413
        $this->assertEquals(32, $second);
414
        $this->assertEquals(33, $third);
415
    }
416
417
    public function testParseClassSpec()
418
    {
419
        // Simple case
420
        $this->assertEquals(
421
            array(Versioned::class, array('Stage', 'Live')),
422
            ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage','Live')")
423
        );
424
        // Case with service identifier
425
        $this->assertEquals(
426
            [
427
                Versioned::class . '.versioned',
428
                ['Versioned'],
429
            ],
430
            ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned.versioned('Versioned')")
431
        );
432
        // String with commas
433
        $this->assertEquals(
434
            array(Versioned::class, array('Stage,Live', 'Stage')),
435
            ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage,Live','Stage')")
436
        );
437
        // String with quotes
438
        $this->assertEquals(
439
            array(Versioned::class, array('Stage\'Stage,Live\'Live', 'Live')),
440
            ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage\\'Stage,Live\\'Live','Live')")
441
        );
442
443
        // True, false and null values
444
        $this->assertEquals(
445
            array('ClassName', array('string', true, array('string', false))),
446
            ClassInfo::parse_class_spec('ClassName("string", true, array("string", false))')
447
        );
448
        $this->assertEquals(
449
            array('ClassName', array(true, false, null)),
450
            ClassInfo::parse_class_spec('ClassName(true, false, null)')
451
        );
452
453
        // Array
454
        $this->assertEquals(
455
            array('Enum', array(array('Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted')),
456
            ClassInfo::parse_class_spec("Enum(array('Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted')")
457
        );
458
        // Nested array
459
        $this->assertEquals(
460
            [
461
                'Enum',
462
                [
463
                    ['Accepted', 'Pending', 'Declined', ['UnsubmittedA', 'UnsubmittedB']],
464
                    'Unsubmitted'
465
                ]
466
            ],
467
            ClassInfo::parse_class_spec(
468
                "Enum(array('Accepted', 'Pending', 'Declined', array('UnsubmittedA','UnsubmittedB')), 'Unsubmitted')"
469
            )
470
        );
471
        // 5.4 Shorthand Array
472
        $this->assertEquals(
473
            array('Enum', array(array('Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted')),
474
            ClassInfo::parse_class_spec("Enum(['Accepted', 'Pending', 'Declined', 'Unsubmitted'], 'Unsubmitted')")
475
        );
476
        // 5.4 Nested shorthand array
477
        $this->assertEquals(
478
            [
479
                'Enum',
480
                [
481
                    ['Accepted', 'Pending', 'Declined', ['UnsubmittedA', 'UnsubmittedB']],
482
                    'Unsubmitted'
483
                ]
484
            ],
485
            ClassInfo::parse_class_spec(
486
                "Enum(['Accepted', 'Pending', 'Declined', ['UnsubmittedA','UnsubmittedB']], 'Unsubmitted')"
487
            )
488
        );
489
490
        // Associative array
491
        $this->assertEquals(
492
            array('Varchar', array(255, array('nullifyEmpty' => false))),
493
            ClassInfo::parse_class_spec("Varchar(255, array('nullifyEmpty' => false))")
494
        );
495
        // Nested associative array
496
        $this->assertEquals(
497
            array('Test', array('string', array('nested' => array('foo' => 'bar')))),
498
            ClassInfo::parse_class_spec("Test('string', array('nested' => array('foo' => 'bar')))")
499
        );
500
        // 5.4 shorthand associative array
501
        $this->assertEquals(
502
            array('Varchar', array(255, array('nullifyEmpty' => false))),
503
            ClassInfo::parse_class_spec("Varchar(255, ['nullifyEmpty' => false])")
504
        );
505
        // 5.4 shorthand nested associative array
506
        $this->assertEquals(
507
            array('Test', array('string', array('nested' => array('foo' => 'bar')))),
508
            ClassInfo::parse_class_spec("Test('string', ['nested' => ['foo' => 'bar']])")
509
        );
510
511
        // Namespaced class
512
        $this->assertEquals(
513
            array('Test\MyClass', array()),
514
            ClassInfo::parse_class_spec('Test\MyClass')
515
        );
516
        // Fully qualified namespaced class
517
        $this->assertEquals(
518
            array('\Test\MyClass', array()),
519
            ClassInfo::parse_class_spec('\Test\MyClass')
520
        );
521
    }
522
523
    public function testInjectedExtensions()
524
    {
525
        $mockExtension = $this->createMock(TestExtension::class);
526
        $mockClass = get_class($mockExtension);
527
528
        $object = new ExtensionTest2();
529
530
        // sanity check
531
        $this->assertNotEquals(TestExtension::class, $mockClass);
532
533
        $this->assertTrue($object->hasExtension(TestExtension::class));
534
        $this->assertFalse($object->hasExtension($mockClass));
535
        $this->assertCount(1, $object->getExtensionInstances());
536
        $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance(TestExtension::class));
537
        $this->assertNotInstanceOf($mockClass, $object->getExtensionInstance(TestExtension::class));
538
539
        Injector::inst()->registerService($mockExtension, TestExtension::class);
540
541
        $object = new ExtensionTest2();
542
543
        $this->assertTrue($object->hasExtension(TestExtension::class));
544
        $this->assertTrue($object->hasExtension($mockClass));
545
        $this->assertCount(1, $object->getExtensionInstances());
546
        $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance(TestExtension::class));
547
        $this->assertInstanceOf($mockClass, $object->getExtensionInstance(TestExtension::class));
548
        $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance($mockClass));
549
        $this->assertInstanceOf($mockClass, $object->getExtensionInstance($mockClass));
550
    }
551
552
    /**
553
     * @expectedException InvalidArgumentException
554
     */
555
    public function testExtensionsAppliedViaANonExistentServiceAsAControl()
556
    {
557
        Config::modify()->set(BaseObject::class, 'extensions', ['NotAService']);
558
        BaseObject::create();
559
    }
560
561
    /**
562
     * @dataProvider serviceNameProvider
563
     */
564
    public function testExtensionsCanBeAppliedViaInjectorServices($serviceClass, $expected)
565
    {
566
        Config::modify()
567
            ->set(Injector::class, 'ATestService', [
568
                'class' => ExtensionService::class,
569
                'constructor' => ['default'],
570
            ])
571
            ->set(Injector::class, 'ATestService.secondary', [
572
                'class' => ExtensionService::class,
573
                'constructor' => ['secondary'],
574
            ])
575
            ->set(Injector::class, 'AnotherTestService', '%$ATestService')
576
            ->set(BaseObject::class, 'extensions', [$serviceClass]);
577
578
        $baseObject = BaseObject::create();
579
        $this->assertSame('This extension is ' . $expected, $baseObject->aMethod());
580
    }
581
582
    public function serviceNameProvider()
583
    {
584
        yield ['ATestService', 'default'];
585
        yield ['ATestService.secondary', 'secondary'];
586
        yield ['%$ATestService', 'default'];
587
        yield ['%$ATestService.secondary', 'secondary'];
588
        yield ['AnotherTestService', 'default'];
589
    }
590
}
591