unregisterObjects([ Extension::class, BaseObject::class, ]); } public function testHasmethodBehaviour() { $obj = new ObjectTest\ExtendTest(); $this->assertTrue($obj->hasMethod('extendableMethod'), "Extension method found in original spelling"); $this->assertTrue($obj->hasMethod('ExTendableMethod'), "Extension method found case-insensitive"); $objs = []; $objs[] = new ObjectTest\T2(); $objs[] = new ObjectTest\T2(); $objs[] = new ObjectTest\T2(); // All these methods should exist and return true $trueMethods = [ 'testMethod', 'otherMethod', 'someMethod', 't1cMethod', 'normalMethod', 'failoverCallback' ]; foreach ($objs as $i => $obj) { foreach ($trueMethods as $method) { $methodU = strtoupper($method); $methodL = strtoupper($method); $this->assertTrue($obj->hasMethod($method), "Test that obj#$i has method $method"); $this->assertTrue($obj->hasMethod($methodU), "Test that obj#$i has method $methodU"); $this->assertTrue($obj->hasMethod($methodL), "Test that obj#$i has method $methodL"); $this->assertTrue($obj->$method(), "Test that obj#$i can call method $method"); $this->assertTrue($obj->$methodU(), "Test that obj#$i can call method $methodU"); $this->assertTrue($obj->$methodL(), "Test that obj#$i can call method $methodL"); } $this->assertTrue($obj->hasMethod('Wrapping'), "Test that obj#$i has method Wrapping"); $this->assertTrue($obj->hasMethod('WRAPPING'), "Test that obj#$i has method WRAPPING"); $this->assertTrue($obj->hasMethod('wrapping'), "Test that obj#$i has method wrapping"); $this->assertEquals("Wrapping", $obj->Wrapping(), "Test that obj#$i can call method Wrapping"); $this->assertEquals("Wrapping", $obj->WRAPPING(), "Test that obj#$i can call method WRAPPIGN"); $this->assertEquals("Wrapping", $obj->wrapping(), "Test that obj#$i can call method wrapping"); } } public function testSingletonCreation() { $myObject = MyObject::singleton(); $this->assertInstanceOf( MyObject::class, $myObject, 'singletons are creating a correct class instance' ); $mySubObject = MySubObject::singleton(); $this->assertInstanceOf( MySubObject::class, $mySubObject, 'singletons are creating a correct subclass instance' ); $myFirstObject = MyObject::singleton(); $mySecondObject = MyObject::singleton(); $this->assertTrue( $myFirstObject === $mySecondObject, 'singletons are using the same object on subsequent calls' ); } public function testStaticGetterMethod() { $obj = singleton(MyObject::class); $this->assertEquals( 'MyObject', $obj->stat('mystaticProperty'), 'Uninherited statics through stat() on a singleton behave the same as built-in PHP statics' ); } public function testStaticInheritanceGetters() { $subObj = singleton(MyObject::class); $this->assertEquals( $subObj->stat('mystaticProperty'), 'MyObject', 'Statics defined on a parent class are available through stat() on a subclass' ); } public function testStaticSettingOnSingletons() { $singleton1 = singleton(MyObject::class); $singleton2 = singleton(MyObject::class); $singleton1->set_stat('mystaticProperty', 'changed'); $this->assertEquals( $singleton2->stat('mystaticProperty'), 'changed', 'Statics setting is populated throughout singletons without explicitly clearing cache' ); } public function testStaticSettingOnInstances() { $instance1 = new ObjectTest\MyObject(); $instance2 = new ObjectTest\MyObject(); $instance1->set_stat('mystaticProperty', 'changed'); $this->assertEquals( $instance2->stat('mystaticProperty'), 'changed', 'Statics setting through set_stat() is populated throughout instances without explicitly clearing cache' ); } /** * Tests that {@link Object::create()} correctly passes all arguments to the new object */ public function testCreateWithArgs() { $createdObj = ObjectTest\CreateTest::create('arg1', 'arg2', [], null, 'arg5'); $this->assertEquals($createdObj->constructArguments, ['arg1', 'arg2', [], null, 'arg5']); } public function testCreateLateStaticBinding() { $createdObj = ObjectTest\CreateTest::create('arg1', 'arg2', [], null, 'arg5'); $this->assertEquals($createdObj->constructArguments, ['arg1', 'arg2', [], null, 'arg5']); } /** * Tests {@link Object::singleton()} */ public function testSingleton() { $inst = Controller::singleton(); $this->assertInstanceOf(Controller::class, $inst); $inst2 = Controller::singleton(); $this->assertSame($inst2, $inst); } public function testGetExtensions() { $this->assertEquals( [ 'SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1', "SilverStripe\\Core\\Tests\\ObjectTest\\ExtendTest2", ], ExtensionTest::get_extensions() ); $this->assertEquals( [ 'SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1', "SilverStripe\\Core\\Tests\\ObjectTest\\ExtendTest2('FOO', 'BAR')", ], ExtensionTest::get_extensions(null, true) ); $inst = new ExtensionTest(); $extensions = $inst->getExtensionInstances(); $this->assertCount(2, $extensions); $this->assertArrayHasKey(ExtendTest1::class, $extensions); $this->assertInstanceOf( ExtendTest1::class, $extensions[ExtendTest1::class] ); $this->assertArrayHasKey(ExtendTest2::class, $extensions); $this->assertInstanceOf( ExtendTest2::class, $extensions[ExtendTest2::class] ); $this->assertInstanceOf( ExtendTest1::class, $inst->getExtensionInstance(ExtendTest1::class) ); $this->assertInstanceOf( ExtendTest2::class, $inst->getExtensionInstance(ExtendTest2::class) ); } /** * Tests {@link Object::has_extension()}, {@link Object::add_extension()} */ public function testHasAndAddExtension() { // ObjectTest_ExtendTest1 is built in via $extensions $this->assertTrue( ExtensionTest::has_extension('SilverStripe\\Core\\Tests\\oBjEcTTEST\\EXTENDTest1'), "Extensions are detected when set on Object::\$extensions on has_extension() without case-sensitivity" ); $this->assertTrue( ExtensionTest::has_extension(ExtendTest1::class), "Extensions are detected when set on Object::\$extensions on has_extension() without case-sensitivity" ); $this->assertTrue( singleton(ExtensionTest::class)->hasExtension(ExtendTest1::class), "Extensions are detected when set on Object::\$extensions on instance hasExtension() without" . " case-sensitivity" ); // ObjectTest_ExtendTest2 is built in via $extensions (with parameters) $this->assertTrue( ExtensionTest::has_extension(ExtendTest2::class), "Extensions are detected with static has_extension() when set on Object::\$extensions with" . " additional parameters" ); $this->assertTrue( singleton(ExtensionTest::class)->hasExtension(ExtendTest2::class), "Extensions are detected with instance hasExtension() when set on Object::\$extensions with" . " additional parameters" ); $this->assertFalse( ExtensionTest::has_extension(ExtendTest3::class), "Other extensions available in the system are not present unless explicitly added to this object" . " when checking through has_extension()" ); $this->assertFalse( singleton(ExtensionTest::class)->hasExtension(ExtendTest3::class), "Other extensions available in the system are not present unless explicitly added to this object" . " when checking through instance hasExtension()" ); // ObjectTest_ExtendTest3 is added manually ExtensionTest::add_extension(ExtendTest3::class . '("Param")'); $this->assertTrue( ExtensionTest::has_extension(ExtendTest3::class), "Extensions are detected with static has_extension() when added through add_extension()" ); // ExtendTest4 is added manually ExtensionTest3::add_extension(ExtendTest4::class . '("Param")'); // test against ObjectTest_ExtendTest3, not ObjectTest_ExtendTest3 $this->assertTrue( ExtensionTest3::has_extension(ExtendTest4::class), "Extensions are detected with static has_extension() when added through add_extension()" ); // test against ObjectTest_ExtendTest3, not ExtendTest4 to test if it picks up // the sub classes of ObjectTest_ExtendTest3 $this->assertTrue( ExtensionTest3::has_extension(ExtendTest3::class), "Sub-Extensions are detected with static has_extension() when added through add_extension()" ); // strictly test against ObjectTest_ExtendTest3, not ExtendTest4 to test if it picks up // the sub classes of ObjectTest_ExtendTest3 $this->assertFalse( ExtensionTest3::has_extension(ExtendTest3::class, null, true), "Sub-Extensions are detected with static has_extension() when added through add_extension()" ); // a singleton() wouldn't work as its already initialized $objectTest_ExtensionTest = new ExtensionTest(); $this->assertTrue( $objectTest_ExtensionTest->hasExtension(ExtendTest3::class), "Extensions are detected with instance hasExtension() when added through add_extension()" ); // load in a custom implementation Injector::inst()->registerService(new ExtendTest5(), ExtendTest4::class); $this->assertTrue( ExtensionTest3::has_extension(ExtendTest5::class), "Injected sub-extensions are detected with static has_extension() when added through add_extension()" ); // @todo At the moment, this does NOT remove the extension due to parameterized naming, // meaning the extension will remain added in further test cases ExtensionTest::remove_extension(ExtendTest3::class); } public function testRemoveExtension() { // manually add ObjectTest_ExtendTest2 ObjectTest\ExtensionRemoveTest::add_extension(ExtendTest2::class); $this->assertTrue( ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class), "Extension added through \$add_extension() are added correctly" ); ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest2::class); $this->assertFalse( ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class), "Extension added through \$add_extension() are detected as removed in has_extension()" ); $this->assertFalse( singleton(ExtensionRemoveTest::class)->hasExtension(ExtendTest2::class), "Extensions added through \$add_extension() are detected as removed in instances through hasExtension()" ); // ObjectTest_ExtendTest1 is already present in $extensions ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest1::class); $this->assertFalse( ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest1::class), "Extension added through \$extensions are detected as removed in has_extension()" ); $objectTest_ExtensionRemoveTest = new ObjectTest\ExtensionRemoveTest(); $this->assertFalse( $objectTest_ExtensionRemoveTest->hasExtension(ExtendTest1::class), "Extensions added through \$extensions are detected as removed in instances through hasExtension()" ); } public function testRemoveExtensionWithParameters() { ObjectTest\ExtensionRemoveTest::add_extension(ExtendTest2::class . '("MyParam")'); $this->assertTrue( ObjectTest\ExtensionRemoveTest::has_extension(ExtendTest2::class), "Extension added through \$add_extension() are added correctly" ); ObjectTest\ExtensionRemoveTest::remove_extension(ExtendTest2::class); $this->assertFalse( ExtensionRemoveTest::has_extension(ExtendTest2::class), "Extension added through \$add_extension() are detected as removed in has_extension()" ); $objectTest_ExtensionRemoveTest = new ObjectTest\ExtensionRemoveTest(); $this->assertFalse( $objectTest_ExtensionRemoveTest->hasExtension(ExtendTest2::class), "Extensions added through \$extensions are detected as removed in instances through hasExtension()" ); } public function testIsA() { $this->assertTrue(ObjectTest\MyObject::create() instanceof ObjectTest\BaseObject); $this->assertTrue(ObjectTest\MyObject::create() instanceof ObjectTest\MyObject); } /** * Tests {@link Object::hasExtension() and Object::getExtensionInstance()} */ public function testExtInstance() { $obj = new ExtensionTest2(); $this->assertTrue($obj->hasExtension(TestExtension::class)); $this->assertTrue($obj->getExtensionInstance(TestExtension::class) instanceof ObjectTest\TestExtension); } public function testExtend() { $object = new ObjectTest\ExtendTest(); $argument = 'test'; $this->assertEquals($object->extend('extendableMethod'), ['ExtendTest2()']); $this->assertEquals($object->extend('extendableMethod', $argument), ['ExtendTest2(modified)']); $this->assertEquals($argument, 'modified'); $this->assertEquals( ['ExtendTest()', 'ExtendTest2()'], $object->invokeWithExtensions('extendableMethod') ); $arg1 = 'test'; $arg2 = 'bob'; $this->assertEquals( ['ExtendTest(test,bob)', 'ExtendTest2(modified,objectmodified)'], $object->invokeWithExtensions('extendableMethod', $arg1, $arg2) ); $this->assertEquals('modified', $arg1); $this->assertEquals('objectmodified', $arg2); $object2 = new ObjectTest\Extending(); $first = 1; $second = 2; $third = 3; $result = $object2->getResults($first, $second, $third); $this->assertEquals( [['before', 'extension', 'after']], $result ); $this->assertEquals(31, $first); $this->assertEquals(32, $second); $this->assertEquals(33, $third); } public function testParseClassSpec() { // Simple case $this->assertEquals( [Versioned::class, ['Stage', 'Live']], ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage','Live')") ); // Case with service identifier $this->assertEquals( [ Versioned::class . '.versioned', ['Versioned'], ], ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned.versioned('Versioned')") ); // String with commas $this->assertEquals( [Versioned::class, ['Stage,Live', 'Stage']], ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage,Live','Stage')") ); // String with quotes $this->assertEquals( [Versioned::class, ['Stage\'Stage,Live\'Live', 'Live']], ClassInfo::parse_class_spec("SilverStripe\\Versioned\\Versioned('Stage\\'Stage,Live\\'Live','Live')") ); // True, false and null values $this->assertEquals( ['ClassName', ['string', true, ['string', false]]], ClassInfo::parse_class_spec('ClassName("string", true, array("string", false))') ); $this->assertEquals( ['ClassName', [true, false, null]], ClassInfo::parse_class_spec('ClassName(true, false, null)') ); // Array $this->assertEquals( ['Enum', [['Accepted', 'Pending', 'Declined', 'Unsubmitted'], 'Unsubmitted']], ClassInfo::parse_class_spec("Enum(array('Accepted', 'Pending', 'Declined', 'Unsubmitted'), 'Unsubmitted')") ); // Nested array $this->assertEquals( [ 'Enum', [ ['Accepted', 'Pending', 'Declined', ['UnsubmittedA', 'UnsubmittedB']], 'Unsubmitted' ] ], ClassInfo::parse_class_spec( "Enum(array('Accepted', 'Pending', 'Declined', array('UnsubmittedA','UnsubmittedB')), 'Unsubmitted')" ) ); // 5.4 Shorthand Array $this->assertEquals( ['Enum', [['Accepted', 'Pending', 'Declined', 'Unsubmitted'], 'Unsubmitted']], ClassInfo::parse_class_spec("Enum(['Accepted', 'Pending', 'Declined', 'Unsubmitted'], 'Unsubmitted')") ); // 5.4 Nested shorthand array $this->assertEquals( [ 'Enum', [ ['Accepted', 'Pending', 'Declined', ['UnsubmittedA', 'UnsubmittedB']], 'Unsubmitted' ] ], ClassInfo::parse_class_spec( "Enum(['Accepted', 'Pending', 'Declined', ['UnsubmittedA','UnsubmittedB']], 'Unsubmitted')" ) ); // Associative array $this->assertEquals( ['Varchar', [255, ['nullifyEmpty' => false]]], ClassInfo::parse_class_spec("Varchar(255, array('nullifyEmpty' => false))") ); // Nested associative array $this->assertEquals( ['Test', ['string', ['nested' => ['foo' => 'bar']]]], ClassInfo::parse_class_spec("Test('string', array('nested' => array('foo' => 'bar')))") ); // 5.4 shorthand associative array $this->assertEquals( ['Varchar', [255, ['nullifyEmpty' => false]]], ClassInfo::parse_class_spec("Varchar(255, ['nullifyEmpty' => false])") ); // 5.4 shorthand nested associative array $this->assertEquals( ['Test', ['string', ['nested' => ['foo' => 'bar']]]], ClassInfo::parse_class_spec("Test('string', ['nested' => ['foo' => 'bar']])") ); // Namespaced class $this->assertEquals( ['Test\MyClass', []], ClassInfo::parse_class_spec('Test\MyClass') ); // Fully qualified namespaced class $this->assertEquals( ['\Test\MyClass', []], ClassInfo::parse_class_spec('\Test\MyClass') ); } public function testInjectedExtensions() { $mockExtension = $this->createMock(TestExtension::class); $mockClass = get_class($mockExtension); $object = new ExtensionTest2(); // sanity check $this->assertNotEquals(TestExtension::class, $mockClass); $this->assertTrue($object->hasExtension(TestExtension::class)); $this->assertFalse($object->hasExtension($mockClass)); $this->assertCount(1, $object->getExtensionInstances()); $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance(TestExtension::class)); $this->assertNotInstanceOf($mockClass, $object->getExtensionInstance(TestExtension::class)); Injector::inst()->registerService($mockExtension, TestExtension::class); $object = new ExtensionTest2(); $this->assertTrue($object->hasExtension(TestExtension::class)); $this->assertTrue($object->hasExtension($mockClass)); $this->assertCount(1, $object->getExtensionInstances()); $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance(TestExtension::class)); $this->assertInstanceOf($mockClass, $object->getExtensionInstance(TestExtension::class)); $this->assertInstanceOf(TestExtension::class, $object->getExtensionInstance($mockClass)); $this->assertInstanceOf($mockClass, $object->getExtensionInstance($mockClass)); } }