Passed
Push — master ( 9b9c6c...ef704e )
by Daniel
35:52 queued 24:20
created

tests/php/Core/ObjectTest.php (1 issue)

1
<?php
2
3
namespace SilverStripe\Core\Tests;
4
5
use SilverStripe\Control\Controller;
6
use SilverStripe\Core\ClassInfo;
7
use SilverStripe\Core\Extension;
8
use SilverStripe\Core\Injector\Injector;
9
use SilverStripe\Core\Tests\ObjectTest\BaseObject;
10
use SilverStripe\Core\Tests\ObjectTest\ExtendTest1;
11
use SilverStripe\Core\Tests\ObjectTest\ExtendTest2;
12
use SilverStripe\Core\Tests\ObjectTest\ExtendTest3;
13
use SilverStripe\Core\Tests\ObjectTest\ExtendTest4;
14
use SilverStripe\Core\Tests\ObjectTest\ExtensionRemoveTest;
15
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest;
16
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest2;
17
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest3;
18
use SilverStripe\Core\Tests\ObjectTest\MyObject;
19
use SilverStripe\Core\Tests\ObjectTest\MySubObject;
20
use SilverStripe\Core\Tests\ObjectTest\TestExtension;
21
use SilverStripe\Dev\SapphireTest;
22
use SilverStripe\Versioned\Versioned;
23
24
/**
25
 * @todo tests for addStaticVars()
26
 * @todo tests for setting statics which are not defined on the object as built-in PHP statics
27
 * @todo tests for setting statics through extensions (#2387)
28
 * @skipUpgrade
29
 */
30
class ObjectTest extends SapphireTest
31
{
32
33
    protected function setUp()
34
    {
35
        parent::setUp();
36
        Injector::inst()->unregisterObjects([
37
            Extension::class,
38
            BaseObject::class,
39
        ]);
40
    }
41
42
    public function testHasmethodBehaviour()
43
    {
44
        $obj = new ObjectTest\ExtendTest();
45
46
        $this->assertTrue($obj->hasMethod('extendableMethod'), "Extension method found in original spelling");
47
        $this->assertTrue($obj->hasMethod('ExTendableMethod'), "Extension method found case-insensitive");
48
49
        $objs = array();
50
        $objs[] = new ObjectTest\T2();
51
        $objs[] = new ObjectTest\T2();
52
        $objs[] = new ObjectTest\T2();
53
54
        // All these methods should exist and return true
55
        $trueMethods = [
56
            'testMethod',
57
            'otherMethod',
58
            'someMethod',
59
            't1cMethod',
60
            'normalMethod',
61
            'failoverCallback'
62
        ];
63
64
        foreach ($objs as $i => $obj) {
65
            foreach ($trueMethods as $method) {
66
                $methodU = strtoupper($method);
67
                $methodL = strtoupper($method);
68
                $this->assertTrue($obj->hasMethod($method), "Test that obj#$i has method $method");
69
                $this->assertTrue($obj->hasMethod($methodU), "Test that obj#$i has method $methodU");
70
                $this->assertTrue($obj->hasMethod($methodL), "Test that obj#$i has method $methodL");
71
72
                $this->assertTrue($obj->$method(), "Test that obj#$i can call method $method");
73
                $this->assertTrue($obj->$methodU(), "Test that obj#$i can call method $methodU");
74
                $this->assertTrue($obj->$methodL(), "Test that obj#$i can call method $methodL");
75
            }
76
77
            $this->assertTrue($obj->hasMethod('Wrapping'), "Test that obj#$i has method Wrapping");
78
            $this->assertTrue($obj->hasMethod('WRAPPING'), "Test that obj#$i has method WRAPPING");
79
            $this->assertTrue($obj->hasMethod('wrapping'), "Test that obj#$i has method wrapping");
80
81
            $this->assertEquals("Wrapping", $obj->Wrapping(), "Test that obj#$i can call method Wrapping");
82
            $this->assertEquals("Wrapping", $obj->WRAPPING(), "Test that obj#$i can call method WRAPPIGN");
83
            $this->assertEquals("Wrapping", $obj->wrapping(), "Test that obj#$i can call method wrapping");
84
        }
85
    }
86
87
    public function testSingletonCreation()
88
    {
89
        $myObject = MyObject::singleton();
90
        $this->assertInstanceOf(
91
            MyObject::class,
92
            $myObject,
93
            'singletons are creating a correct class instance'
94
        );
95
        $mySubObject = MySubObject::singleton();
96
        $this->assertInstanceOf(
97
            MySubObject::class,
98
            $mySubObject,
99
            'singletons are creating a correct subclass instance'
100
        );
101
102
        $myFirstObject = MyObject::singleton();
103
        $mySecondObject = MyObject::singleton();
104
        $this->assertTrue(
105
            $myFirstObject === $mySecondObject,
106
            'singletons are using the same object on subsequent calls'
107
        );
108
    }
109
110
    public function testStaticGetterMethod()
111
    {
112
        $obj = singleton(MyObject::class);
113
        $this->assertEquals(
114
            'MyObject',
115
            $obj->stat('mystaticProperty'),
116
            'Uninherited statics through stat() on a singleton behave the same as built-in PHP statics'
117
        );
118
    }
119
120
    public function testStaticInheritanceGetters()
121
    {
122
        $subObj = singleton(MyObject::class);
123
        $this->assertEquals(
124
            $subObj->stat('mystaticProperty'),
125
            'MyObject',
126
            'Statics defined on a parent class are available through stat() on a subclass'
127
        );
128
    }
129
130
    public function testStaticSettingOnSingletons()
131
    {
132
        $singleton1 = singleton(MyObject::class);
133
        $singleton2 = singleton(MyObject::class);
134
        $singleton1->set_stat('mystaticProperty', 'changed');
135
        $this->assertEquals(
136
            $singleton2->stat('mystaticProperty'),
137
            'changed',
138
            'Statics setting is populated throughout singletons without explicitly clearing cache'
139
        );
140
    }
141
142
    public function testStaticSettingOnInstances()
143
    {
144
        $instance1 = new ObjectTest\MyObject();
145
        $instance2 = new ObjectTest\MyObject();
146
        $instance1->set_stat('mystaticProperty', 'changed');
147
        $this->assertEquals(
148
            $instance2->stat('mystaticProperty'),
149
            'changed',
150
            'Statics setting through set_stat() is populated throughout instances without explicitly clearing cache'
151
        );
152
    }
153
154
    /**
155
     * Tests that {@link Object::create()} correctly passes all arguments to the new object
156
     */
157
    public function testCreateWithArgs()
158
    {
159
        $createdObj = ObjectTest\CreateTest::create('arg1', 'arg2', array(), null, 'arg5');
160
        $this->assertEquals($createdObj->constructArguments, array('arg1', 'arg2', array(), null, 'arg5'));
161
    }
162
163
    public function testCreateLateStaticBinding()
164
    {
165
        $createdObj = ObjectTest\CreateTest::create('arg1', 'arg2', array(), null, 'arg5');
166
        $this->assertEquals($createdObj->constructArguments, array('arg1', 'arg2', array(), null, 'arg5'));
167
    }
168
169
    /**
170
     * Tests {@link Object::singleton()}
171
     */
172
    public function testSingleton()
173
    {
174
        $inst = Controller::singleton();
175
        $this->assertInstanceOf(Controller::class, $inst);
176
        $inst2 = Controller::singleton();
177
        $this->assertSame($inst2, $inst);
178
    }
179
180
    public function testGetExtensions()
181
    {
182
        $this->assertEquals(
183
            array(
184
                'SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1',
185
                "SilverStripe\\Core\\Tests\\ObjectTest\\ExtendTest2",
186
            ),
187
            ExtensionTest::get_extensions()
188
        );
189
        $this->assertEquals(
190
            array(
191
                'SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1',
192
                "SilverStripe\\Core\\Tests\\ObjectTest\\ExtendTest2('FOO', 'BAR')",
193
            ),
194
            ExtensionTest::get_extensions(null, true)
195
        );
196
        $inst = new ExtensionTest();
197
        $extensions = $inst->getExtensionInstances();
198
        $this->assertCount(2, $extensions);
199
        $this->assertArrayHasKey(ExtendTest1::class, $extensions);
200
        $this->assertInstanceOf(
201
            ExtendTest1::class,
202
            $extensions[ExtendTest1::class]
203
        );
204
        $this->assertArrayHasKey(ExtendTest2::class, $extensions);
205
        $this->assertInstanceOf(
206
            ExtendTest2::class,
207
            $extensions[ExtendTest2::class]
208
        );
209
        $this->assertInstanceOf(
210
            ExtendTest1::class,
211
            $inst->getExtensionInstance(ExtendTest1::class)
212
        );
213
        $this->assertInstanceOf(
214
            ExtendTest2::class,
215
            $inst->getExtensionInstance(ExtendTest2::class)
216
        );
217
    }
218
219
    /**
220
     * Tests {@link Object::has_extension()}, {@link Object::add_extension()}
221
     */
222
    public function testHasAndAddExtension()
223
    {
224
        // ObjectTest_ExtendTest1 is built in via $extensions
225
        $this->assertTrue(
226
            ExtensionTest::has_extension('SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1'),
227
            "Extensions are detected when set on Object::\$extensions on has_extension() without case-sensitivity"
228
        );
229
        $this->assertTrue(
230
            ExtensionTest::has_extension(ExtendTest1::class),
231
            "Extensions are detected when set on Object::\$extensions on has_extension() without case-sensitivity"
232
        );
233
        $this->assertTrue(
234
            singleton(ExtensionTest::class)->hasExtension(ExtendTest1::class),
235
            "Extensions are detected when set on Object::\$extensions on instance hasExtension() without"
236
            . " case-sensitivity"
237
        );
238
239
        // ObjectTest_ExtendTest2 is built in via $extensions (with parameters)
240
        $this->assertTrue(
241
            ExtensionTest::has_extension(ExtendTest2::class),
242
            "Extensions are detected with static has_extension() when set on Object::\$extensions with"
243
            . " additional parameters"
244
        );
245
        $this->assertTrue(
246
            singleton(ExtensionTest::class)->hasExtension(ExtendTest2::class),
247
            "Extensions are detected with instance hasExtension() when set on Object::\$extensions with"
248
            . " additional parameters"
249
        );
250
        $this->assertFalse(
251
            ExtensionTest::has_extension(ExtendTest3::class),
252
            "Other extensions available in the system are not present unless explicitly added to this object"
253
            . " when checking through has_extension()"
254
        );
255
        $this->assertFalse(
256
            singleton(ExtensionTest::class)->hasExtension(ExtendTest3::class),
257
            "Other extensions available in the system are not present unless explicitly added to this object"
258
            . " when checking through instance hasExtension()"
259
        );
260
261
        // ObjectTest_ExtendTest3 is added manually
262
        ExtensionTest::add_extension(ExtendTest3::class . '("Param")');
263
        $this->assertTrue(
264
            ExtensionTest::has_extension(ExtendTest3::class),
265
            "Extensions are detected with static has_extension() when added through add_extension()"
266
        );
267
        // ExtendTest4 is added manually
268
        ExtensionTest3::add_extension(ExtendTest4::class . '("Param")');
269
        // test against ObjectTest_ExtendTest3, not ObjectTest_ExtendTest3
270
        $this->assertTrue(
271
            ExtensionTest3::has_extension(ExtendTest4::class),
272
            "Extensions are detected with static has_extension() when added through add_extension()"
273
        );
274
        // test against ObjectTest_ExtendTest3, not ExtendTest4 to test if it picks up
275
        // the sub classes of ObjectTest_ExtendTest3
276
        $this->assertTrue(
277
            ExtensionTest3::has_extension(ExtendTest3::class),
278
            "Sub-Extensions are detected with static has_extension() when added through add_extension()"
279
        );
280
        // strictly test against ObjectTest_ExtendTest3, not ExtendTest4 to test if it picks up
281
        // the sub classes of ObjectTest_ExtendTest3
282
        $this->assertFalse(
283
            ExtensionTest3::has_extension(ExtendTest3::class, null, true),
284
            "Sub-Extensions are detected with static has_extension() when added through add_extension()"
285
        );
286
        // a singleton() wouldn't work as its already initialized
287
        $objectTest_ExtensionTest = new ExtensionTest();
288
        $this->assertTrue(
289
            $objectTest_ExtensionTest->hasExtension(ExtendTest3::class),
290
            "Extensions are detected with instance hasExtension() when added through add_extension()"
291
        );
292
293
        // @todo At the moment, this does NOT remove the extension due to parameterized naming,
294
        //  meaning the extension will remain added in further test cases
295
        ExtensionTest::remove_extension(ExtendTest3::class);
296
    }
297
298
    public function testRemoveExtension()
299
    {
300
        // manually add ObjectTest_ExtendTest2
301
        ObjectTest\ExtensionRemoveTest::add_extension(ExtendTest2::class);
302
        $this->assertTrue(
303
            ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class),
304
            "Extension added through \$add_extension() are added correctly"
305
        );
306
307
        ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest2::class);
308
        $this->assertFalse(
309
            ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class),
310
            "Extension added through \$add_extension() are detected as removed in has_extension()"
311
        );
312
        $this->assertFalse(
313
            singleton(ExtensionRemoveTest::class)->hasExtension(ExtendTest2::class),
314
            "Extensions added through \$add_extension() are detected as removed in instances through hasExtension()"
315
        );
316
317
        // ObjectTest_ExtendTest1 is already present in $extensions
318
        ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest1::class);
319
320
        $this->assertFalse(
321
            ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest1::class),
322
            "Extension added through \$extensions are detected as removed in has_extension()"
323
        );
324
325
        $objectTest_ExtensionRemoveTest = new ObjectTest\ExtensionRemoveTest();
326
        $this->assertFalse(
327
            $objectTest_ExtensionRemoveTest->hasExtension(ExtendTest1::class),
328
            "Extensions added through \$extensions are detected as removed in instances through hasExtension()"
329
        );
330
    }
331
332
    public function testRemoveExtensionWithParameters()
333
    {
334
        ObjectTest\ExtensionRemoveTest::add_extension(ExtendTest2::class . '("MyParam")');
335
336
        $this->assertTrue(
337
            ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class),
338
            "Extension added through \$add_extension() are added correctly"
339
        );
340
341
        ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest2::class);
342
        $this->assertFalse(
343
            ExtensionRemoveTest::has_extension(ExtendTest2::class),
344
            "Extension added through \$add_extension() are detected as removed in has_extension()"
345
        );
346
347
        $objectTest_ExtensionRemoveTest = new ObjectTest\ExtensionRemoveTest();
348
        $this->assertFalse(
349
            $objectTest_ExtensionRemoveTest->hasExtension(ExtendTest2::class),
350
            "Extensions added through \$extensions are detected as removed in instances through hasExtension()"
351
        );
352
    }
353
354
    public function testIsA()
355
    {
356
        $this->assertTrue(ObjectTest\MyObject::create() instanceof ObjectTest\BaseObject);
357
        $this->assertTrue(ObjectTest\MyObject::create() instanceof ObjectTest\MyObject);
358
    }
359
360
    /**
361
     * Tests {@link Object::hasExtension() and Object::getExtensionInstance()}
362
     */
363
    public function testExtInstance()
364
    {
365
        $obj = new ExtensionTest2();
366
367
        $this->assertTrue($obj->hasExtension(TestExtension::class));
368
        $this->assertTrue($obj->getExtensionInstance(TestExtension::class) instanceof ObjectTest\TestExtension);
369
    }
370
371
    public function testExtend()
372
    {
373
        $object = new ObjectTest\ExtendTest();
374
        $argument = 'test';
375
376
        $this->assertEquals($object->extend('extendableMethod'), array('ExtendTest2()'));
377
        $this->assertEquals($object->extend('extendableMethod', $argument), array('ExtendTest2(modified)'));
378
        $this->assertEquals($argument, 'modified');
379
380
        $this->assertEquals(
381
            array('ExtendTest()', 'ExtendTest2()'),
382
            $object->invokeWithExtensions('extendableMethod')
383
        );
384
        $arg1 = 'test';
385
        $arg2 = 'bob';
386
        $this->assertEquals(
387
            array('ExtendTest(test,bob)', 'ExtendTest2(modified,objectmodified)'),
388
            $object->invokeWithExtensions('extendableMethod', $arg1, $arg2)
389
        );
390
        $this->assertEquals('modified', $arg1);
391
        $this->assertEquals('objectmodified', $arg2);
392
393
        $object2 = new ObjectTest\Extending();
394
        $first = 1;
395
        $second = 2;
396
        $third = 3;
397
        $result = $object2->getResults($first, $second, $third);
398
        $this->assertEquals(
399
            array(array('before', 'extension', 'after')),
400
            $result
401
        );
402
        $this->assertEquals(31, $first);
403
        $this->assertEquals(32, $second);
404
        $this->assertEquals(33, $third);
405
    }
406
407
    public function testParseClassSpec()
408
    {
409
        // Simple case
410
        $this->assertEquals(
411
            array(Versioned::class, array('Stage', 'Live')),
412
            ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage','Live')")
413
        );
414
        // Case with service identifier
415
        $this->assertEquals(
416
            [
417
                Versioned::class . '.versioned',
418
                ['Versioned'],
419
            ],
420
            ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned.versioned('Versioned')")
421
        );
422
        // String with commas
423
        $this->assertEquals(
424
            array(Versioned::class, array('Stage,Live', 'Stage')),
425
            ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage,Live','Stage')")
426
        );
427
        // String with quotes
428
        $this->assertEquals(
429
            array(Versioned::class, array('Stage\'Stage,Live\'Live', 'Live')),
430
            ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage\\'Stage,Live\\'Live','Live')")
431
        );
432
433
        // True, false and null values
0 ignored issues
show
Unused Code Comprehensibility introduced by
46% 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...
434
        $this->assertEquals(
435
            array('ClassName', array('string', true, array('string', false))),
436
            ClassInfo::parse_class_spec('ClassName("string", true, array("string", false))')
437
        );
438
        $this->assertEquals(
439
            array('ClassName', array(true, false, null)),
440
            ClassInfo::parse_class_spec('ClassName(true, false, null)')
441
        );
442
443
        // Array
444
        $this->assertEquals(
445
            array('Enum', array(array('Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted')),
446
            ClassInfo::parse_class_spec("Enum(array('Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted')")
447
        );
448
        // Nested array
449
        $this->assertEquals(
450
            [
451
                'Enum',
452
                [
453
                    ['Accepted', 'Pending', 'Declined', ['UnsubmittedA', 'UnsubmittedB']],
454
                    'Unsubmitted'
455
                ]
456
            ],
457
            ClassInfo::parse_class_spec(
458
                "Enum(array('Accepted', 'Pending', 'Declined', array('UnsubmittedA','UnsubmittedB')), 'Unsubmitted')"
459
            )
460
        );
461
        // 5.4 Shorthand Array
462
        $this->assertEquals(
463
            array('Enum', array(array('Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted')),
464
            ClassInfo::parse_class_spec("Enum(['Accepted', 'Pending', 'Declined', 'Unsubmitted'], 'Unsubmitted')")
465
        );
466
        // 5.4 Nested shorthand array
467
        $this->assertEquals(
468
            [
469
                'Enum',
470
                [
471
                    ['Accepted', 'Pending', 'Declined', ['UnsubmittedA', 'UnsubmittedB']],
472
                    'Unsubmitted'
473
                ]
474
            ],
475
            ClassInfo::parse_class_spec(
476
                "Enum(['Accepted', 'Pending', 'Declined', ['UnsubmittedA','UnsubmittedB']], 'Unsubmitted')"
477
            )
478
        );
479
480
        // Associative array
481
        $this->assertEquals(
482
            array('Varchar', array(255, array('nullifyEmpty' => false))),
483
            ClassInfo::parse_class_spec("Varchar(255, array('nullifyEmpty' => false))")
484
        );
485
        // Nested associative array
486
        $this->assertEquals(
487
            array('Test', array('string', array('nested' => array('foo' => 'bar')))),
488
            ClassInfo::parse_class_spec("Test('string', array('nested' => array('foo' => 'bar')))")
489
        );
490
        // 5.4 shorthand associative array
491
        $this->assertEquals(
492
            array('Varchar', array(255, array('nullifyEmpty' => false))),
493
            ClassInfo::parse_class_spec("Varchar(255, ['nullifyEmpty' => false])")
494
        );
495
        // 5.4 shorthand nested associative array
496
        $this->assertEquals(
497
            array('Test', array('string', array('nested' => array('foo' => 'bar')))),
498
            ClassInfo::parse_class_spec("Test('string', ['nested' => ['foo' => 'bar']])")
499
        );
500
501
        // Namespaced class
502
        $this->assertEquals(
503
            array('Test\MyClass', array()),
504
            ClassInfo::parse_class_spec('Test\MyClass')
505
        );
506
        // Fully qualified namespaced class
507
        $this->assertEquals(
508
            array('\Test\MyClass', array()),
509
            ClassInfo::parse_class_spec('\Test\MyClass')
510
        );
511
    }
512
513
    public function testInjectedExtensions()
514
    {
515
        $mockExtension = $this->createMock(TestExtension::class);
516
        $mockClass = get_class($mockExtension);
517
518
        $object = new ExtensionTest2();
519
520
        // sanity check
521
        $this->assertNotEquals(TestExtension::class, $mockClass);
522
523
        $this->assertTrue($object->hasExtension(TestExtension::class));
524
        $this->assertFalse($object->hasExtension($mockClass));
525
        $this->assertCount(1, $object->getExtensionInstances());
526
        $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance(TestExtension::class));
527
        $this->assertNotInstanceOf($mockClass, $object->getExtensionInstance(TestExtension::class));
528
529
        Injector::inst()->registerService($mockExtension, TestExtension::class);
530
531
        $object = new ExtensionTest2();
532
533
        $this->assertTrue($object->hasExtension(TestExtension::class));
534
        $this->assertTrue($object->hasExtension($mockClass));
535
        $this->assertCount(1, $object->getExtensionInstances());
536
        $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance(TestExtension::class));
537
        $this->assertInstanceOf($mockClass, $object->getExtensionInstance(TestExtension::class));
538
        $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance($mockClass));
539
        $this->assertInstanceOf($mockClass, $object->getExtensionInstance($mockClass));
540
    }
541
}
542