Completed
Pull Request — master (#6927)
by Damian
11:37 queued 03:26
created

ObjectTest::testStaticSettingOnInstances()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 11
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Core\Tests;
4
5
use SilverStripe\Core\ClassInfo;
6
use SilverStripe\Core\Injector\Injector;
7
use SilverStripe\Core\Tests\ObjectTest\ExtendTest1;
8
use SilverStripe\Core\Tests\ObjectTest\ExtendTest2;
9
use SilverStripe\Core\Tests\ObjectTest\ExtendTest3;
10
use SilverStripe\Core\Tests\ObjectTest\ExtendTest4;
11
use SilverStripe\Core\Tests\ObjectTest\ExtensionRemoveTest;
12
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest;
13
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest2;
14
use SilverStripe\Core\Tests\ObjectTest\ExtensionTest3;
15
use SilverStripe\Core\Tests\ObjectTest\MyObject;
16
use SilverStripe\Core\Tests\ObjectTest\MySubObject;
17
use SilverStripe\Core\Tests\ObjectTest\TestExtension;
18
use SilverStripe\Dev\SapphireTest;
19
use SilverStripe\Control\Controller;
20
use SilverStripe\Versioned\Versioned;
21
22
/**
23
 * @todo tests for addStaticVars()
24
 * @todo tests for setting statics which are not defined on the object as built-in PHP statics
25
 * @todo tests for setting statics through extensions (#2387)
26
 */
27
class ObjectTest extends SapphireTest
28
{
29
30
    protected function setUp()
31
    {
32
        parent::setUp();
33
        Injector::inst()->unregisterAllObjects();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Psr\Container\ContainerInterface as the method unregisterAllObjects() does only exist in the following implementations of said interface: SilverStripe\Core\Injector\Injector.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

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