Ensuring classinfo is case insensitive

This commit is contained in:
Daniel Hensby 2015-02-27 00:10:32 +00:00 committed by Daniel Hensby
parent 5f0d0ab66a
commit ffbeac6b7d
6 changed files with 554 additions and 480 deletions

View File

@ -26,7 +26,7 @@ class SilverStripeServiceConfigurationLocator extends ServiceConfigurationLocato
// do parent lookup if it's a class // do parent lookup if it's a class
if (class_exists($name)) { if (class_exists($name)) {
$parents = array_reverse(array_keys(ClassInfo::ancestry($name))); $parents = array_reverse(array_values(ClassInfo::ancestry($name)));
array_shift($parents); array_shift($parents);
foreach ($parents as $parent) { foreach ($parents as $parent) {

View File

@ -63,6 +63,7 @@ class ClassInfo {
* @param string $class Class name to check enum values for ClassName field * @param string $class Class name to check enum values for ClassName field
*/ */
public static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) { public static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) {
$class = self::class_name($class);
$classes = DB::getConn()->enumValuesForField($class, 'ClassName'); $classes = DB::getConn()->enumValuesForField($class, 'ClassName');
if (!$includeUnbacked) $classes = array_filter($classes, array('ClassInfo', 'exists')); if (!$includeUnbacked) $classes = array_filter($classes, array('ClassInfo', 'exists'));
return $classes; return $classes;
@ -79,9 +80,7 @@ class ClassInfo {
public static function dataClassesFor($class) { public static function dataClassesFor($class) {
$result = array(); $result = array();
if (is_object($class)) { $class = self::class_name($class);
$class = get_class($class);
}
$classes = array_merge( $classes = array_merge(
self::ancestry($class), self::ancestry($class),
@ -102,7 +101,7 @@ class ClassInfo {
* @return string * @return string
*/ */
public static function baseDataClass($class) { public static function baseDataClass($class) {
if (is_object($class)) $class = get_class($class); $class = self::class_name($class);
if (!is_subclass_of($class, 'DataObject')) { if (!is_subclass_of($class, 'DataObject')) {
throw new InvalidArgumentException("$class is not a subclass of DataObject"); throw new InvalidArgumentException("$class is not a subclass of DataObject");
@ -126,7 +125,7 @@ class ClassInfo {
* <code> * <code>
* ClassInfo::subclassesFor('BaseClass'); * ClassInfo::subclassesFor('BaseClass');
* array( * array(
* 0 => 'BaseClass', * 'BaseClass' => 'BaseClass',
* 'ChildClass' => 'ChildClass', * 'ChildClass' => 'ChildClass',
* 'GrandChildClass' => 'GrandChildClass' * 'GrandChildClass' => 'GrandChildClass'
* ) * )
@ -136,8 +135,10 @@ class ClassInfo {
* @return array Names of all subclasses as an associative array. * @return array Names of all subclasses as an associative array.
*/ */
public static function subclassesFor($class) { public static function subclassesFor($class) {
//normalise class case
$className = self::class_name($class);
$descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($class); $descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($class);
$result = array($class => $class); $result = array($className => $className);
if ($descendants) { if ($descendants) {
return $result + ArrayLib::valuekey($descendants); return $result + ArrayLib::valuekey($descendants);
@ -146,6 +147,23 @@ class ClassInfo {
} }
} }
/**
* Convert a class name in any case and return it as it was defined in PHP
*
* eg: self::class_name('dataobJEct'); //returns 'DataObject'
*
* @param string|object $nameOrObject The classname or object you want to normalise
*
* @return string The normalised class name
*/
public static function class_name($nameOrObject) {
if (is_object($nameOrObject)) {
return get_class($nameOrObject);
}
$reflection = new ReflectionClass($nameOrObject);
return $reflection->getName();
}
/** /**
* Returns the passed class name along with all its parent class names in an * Returns the passed class name along with all its parent class names in an
* array, sorted with the root class first. * array, sorted with the root class first.
@ -155,9 +173,11 @@ class ClassInfo {
* @return array * @return array
*/ */
public static function ancestry($class, $tablesOnly = false) { public static function ancestry($class, $tablesOnly = false) {
if (!is_string($class)) $class = get_class($class); $class = self::class_name($class);
$cacheKey = $class . '_' . (string)$tablesOnly; $lClass = strtolower($class);
$cacheKey = $lClass . '_' . (string)$tablesOnly;
$parent = $class; $parent = $class;
if(!isset(self::$_cache_ancestry[$cacheKey])) { if(!isset(self::$_cache_ancestry[$cacheKey])) {
$ancestry = array(); $ancestry = array();
@ -184,7 +204,7 @@ class ClassInfo {
* Returns true if the given class implements the given interface * Returns true if the given class implements the given interface
*/ */
public static function classImplements($className, $interfaceName) { public static function classImplements($className, $interfaceName) {
return in_array($className, SS_ClassLoader::instance()->getManifest()->getImplementorsOf($interfaceName)); return in_array($className, self::implementorsOf($interfaceName));
} }
/** /**
@ -233,20 +253,23 @@ class ClassInfo {
private static $method_from_cache = array(); private static $method_from_cache = array();
public static function has_method_from($class, $method, $compclass) { public static function has_method_from($class, $method, $compclass) {
if (!isset(self::$method_from_cache[$class])) self::$method_from_cache[$class] = array(); $lClass = strtolower($class);
$lMethod = strtolower($method);
$lCompclass = strtolower($compclass);
if (!isset(self::$method_from_cache[$lClass])) self::$method_from_cache[$lClass] = array();
if (!array_key_exists($method, self::$method_from_cache[$class])) { if (!array_key_exists($lMethod, self::$method_from_cache[$lClass])) {
self::$method_from_cache[$class][$method] = false; self::$method_from_cache[$lClass][$lMethod] = false;
$classRef = new ReflectionClass($class); $classRef = new ReflectionClass($class);
if ($classRef->hasMethod($method)) { if ($classRef->hasMethod($method)) {
$methodRef = $classRef->getMethod($method); $methodRef = $classRef->getMethod($method);
self::$method_from_cache[$class][$method] = $methodRef->getDeclaringClass()->getName(); self::$method_from_cache[$lClass][$lMethod] = $methodRef->getDeclaringClass()->getName();
} }
} }
return self::$method_from_cache[$class][$method] == $compclass; return strtolower(self::$method_from_cache[$lClass][$lMethod]) == $lCompclass;
} }
@ -261,11 +284,14 @@ class ClassInfo {
* @return string * @return string
*/ */
public static function table_for_object_field($candidateClass, $fieldName) { public static function table_for_object_field($candidateClass, $fieldName) {
if(!$candidateClass || !$fieldName) { if(!$candidateClass || !$fieldName || !is_subclass_of($candidateClass, 'DataObject')) {
return null; return null;
} }
$exists = class_exists($candidateClass); //normalise class name
$candidateClass = self::class_name($candidateClass);
$exists = self::exists($candidateClass);
while($candidateClass && $candidateClass != 'DataObject' && $exists) { while($candidateClass && $candidateClass != 'DataObject' && $exists) {
if(DataObject::has_own_table($candidateClass)) { if(DataObject::has_own_table($candidateClass)) {
@ -277,7 +303,7 @@ class ClassInfo {
} }
$candidateClass = get_parent_class($candidateClass); $candidateClass = get_parent_class($candidateClass);
$exists = class_exists($candidateClass); $exists = $candidateClass && self::exists($candidateClass);
} }
if(!$candidateClass || !$exists) { if(!$candidateClass || !$exists) {

View File

@ -2441,8 +2441,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return 'Int'; return 'Int';
} }
// get cached fieldmap // get cached fieldmap
$fieldMap = isset(DataObject::$cache_has_own_table_field[$this->class]) $lClass = strtolower($this->class);
? DataObject::$cache_has_own_table_field[$this->class] : null; $fieldMap = isset(DataObject::$cache_has_own_table_field[$lClass])
? DataObject::$cache_has_own_table_field[$lClass] : null;
// if no fieldmap is cached, get all fields // if no fieldmap is cached, get all fields
if(!$fieldMap) { if(!$fieldMap) {
@ -2464,7 +2465,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
} }
// set cached fieldmap // set cached fieldmap
DataObject::$cache_has_own_table_field[$this->class] = $fieldMap; DataObject::$cache_has_own_table_field[$lClass] = $fieldMap;
} }
// Remove string-based "constructor-arguments" from the DBField definition // Remove string-based "constructor-arguments" from the DBField definition
@ -2483,17 +2484,17 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*/ */
public static function has_own_table($dataClass) { public static function has_own_table($dataClass) {
if(!is_subclass_of($dataClass, 'DataObject')) return false; if(!is_subclass_of($dataClass, 'DataObject')) return false;
$lDataClass = strtolower($dataClass);
if(!isset(DataObject::$cache_has_own_table[$dataClass])) { if(!isset(DataObject::$cache_has_own_table[$lDataClass])) {
if(get_parent_class($dataClass) == 'DataObject') { if(get_parent_class($dataClass) == 'DataObject' || Config::inst()->get($dataClass, 'db', Config::UNINHERITED)
DataObject::$cache_has_own_table[$dataClass] = true; || Config::inst()->get($dataClass, 'has_one', Config::UNINHERITED)) {
DataObject::$cache_has_own_table[$lDataClass] = $dataClass;
} else { } else {
DataObject::$cache_has_own_table[$dataClass] DataObject::$cache_has_own_table[$lDataClass] = false;
= Config::inst()->get($dataClass, 'db', Config::UNINHERITED)
|| Config::inst()->get($dataClass, 'has_one', Config::UNINHERITED);
} }
} }
return DataObject::$cache_has_own_table[$dataClass]; return (bool)DataObject::$cache_has_own_table[$lDataClass];
} }
/** /**

View File

@ -169,7 +169,7 @@ class DataQuery {
$tableClasses = $ancestorTables; $tableClasses = $ancestorTables;
} }
$tableNames = array_keys($tableClasses); $tableNames = array_values($tableClasses);
$baseClass = $tableNames[0]; $baseClass = $tableNames[0];
// Iterate over the tables and check what we need to select from them. If any selects are made (or the table is // Iterate over the tables and check what we need to select from them. If any selects are made (or the table is

View File

@ -6,10 +6,18 @@
*/ */
class ClassInfoTest extends SapphireTest { class ClassInfoTest extends SapphireTest {
public function setUp() {
parent::setUp();
ClassInfo::reset_db_cache();
}
public function testExists() { public function testExists() {
$this->assertTrue(ClassInfo::exists('Object')); $this->assertTrue(ClassInfo::exists('Object'));
$this->assertTrue(ClassInfo::exists('object'));
$this->assertTrue(ClassInfo::exists('ClassInfoTest')); $this->assertTrue(ClassInfo::exists('ClassInfoTest'));
$this->assertTrue(ClassInfo::exists('CLASSINFOTEST'));
$this->assertTrue(ClassInfo::exists('stdClass')); $this->assertTrue(ClassInfo::exists('stdClass'));
$this->assertTrue(ClassInfo::exists('stdCLASS'));
} }
public function testSubclassesFor() { public function testSubclassesFor() {
@ -22,6 +30,16 @@ class ClassInfoTest extends SapphireTest {
), ),
'ClassInfo::subclassesFor() returns only direct subclasses and doesnt include base class' 'ClassInfo::subclassesFor() returns only direct subclasses and doesnt include base class'
); );
ClassInfo::reset_db_cache();
$this->assertEquals(
ClassInfo::subclassesFor('classinfotest_baseclass'),
array(
'ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass',
'ClassInfoTest_ChildClass' => 'ClassInfoTest_ChildClass',
'ClassInfoTest_GrandChildClass' => 'ClassInfoTest_GrandChildClass'
),
'ClassInfo::subclassesFor() is acting in a case sensitive way when it should not'
);
} }
public function testClassesForFolder() { public function testClassesForFolder() {
@ -34,11 +52,11 @@ class ClassInfoTest extends SapphireTest {
$classes, $classes,
'ClassInfo::classes_for_folder() returns classes matching the filename' 'ClassInfo::classes_for_folder() returns classes matching the filename'
); );
// $this->assertContains( $this->assertContains(
// 'ClassInfoTest_BaseClass', 'classinfotest_baseclass',
// $classes, $classes,
// 'ClassInfo::classes_for_folder() returns additional classes not matching the filename' 'ClassInfo::classes_for_folder() returns additional classes not matching the filename'
// ); );
} }
/** /**
@ -46,8 +64,11 @@ class ClassInfoTest extends SapphireTest {
*/ */
public function testBaseDataClass() { public function testBaseDataClass() {
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_BaseClass')); $this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_BaseClass'));
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('classinfotest_baseclass'));
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_ChildClass')); $this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_ChildClass'));
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('CLASSINFOTEST_CHILDCLASS'));
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_GrandChildClass')); $this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_GrandChildClass'));
$this->assertEquals('ClassInfoTest_BaseClass', ClassInfo::baseDataClass('ClassInfoTest_GRANDChildClass'));
$this->setExpectedException('InvalidArgumentException'); $this->setExpectedException('InvalidArgumentException');
ClassInfo::baseDataClass('DataObject'); ClassInfo::baseDataClass('DataObject');
@ -67,6 +88,13 @@ class ClassInfoTest extends SapphireTest {
)); ));
$this->assertEquals($expect, $ancestry); $this->assertEquals($expect, $ancestry);
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::ancestry('classINFOTest_Childclass'));
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::ancestry('classINFOTest_Childclass'));
ClassInfo::reset_db_cache();
$ancestry = ClassInfo::ancestry('ClassInfoTest_ChildClass', true); $ancestry = ClassInfo::ancestry('ClassInfoTest_ChildClass', true);
$this->assertEquals(array('ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass'), $ancestry, $this->assertEquals(array('ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass'), $ancestry,
'$tablesOnly option excludes memory-only inheritance classes' '$tablesOnly option excludes memory-only inheritance classes'
@ -89,8 +117,11 @@ class ClassInfoTest extends SapphireTest {
'ClassInfoTest_HasFields', 'ClassInfoTest_HasFields',
); );
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[0])); $this->assertEquals($expect, ClassInfo::dataClassesFor($classes[0]));
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor(strtoupper($classes[0])));
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[1])); $this->assertEquals($expect, ClassInfo::dataClassesFor($classes[1]));
$expect = array( $expect = array(
@ -98,7 +129,10 @@ class ClassInfoTest extends SapphireTest {
'ClassInfoTest_HasFields' => 'ClassInfoTest_HasFields', 'ClassInfoTest_HasFields' => 'ClassInfoTest_HasFields',
); );
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[2])); $this->assertEquals($expect, ClassInfo::dataClassesFor($classes[2]));
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor(strtolower($classes[2])));
} }
public function testTableForObjectField() { public function testTableForObjectField() {
@ -106,6 +140,10 @@ class ClassInfoTest extends SapphireTest {
ClassInfo::table_for_object_field('ClassInfoTest_WithRelation', 'RelationID') ClassInfo::table_for_object_field('ClassInfoTest_WithRelation', 'RelationID')
); );
$this->assertEquals('ClassInfoTest_WithRelation',
ClassInfo::table_for_object_field('ClassInfoTest_withrelation', 'RelationID')
);
$this->assertEquals('ClassInfoTest_BaseDataClass', $this->assertEquals('ClassInfoTest_BaseDataClass',
ClassInfo::table_for_object_field('ClassInfoTest_BaseDataClass', 'Title') ClassInfo::table_for_object_field('ClassInfoTest_BaseDataClass', 'Title')
); );
@ -118,6 +156,10 @@ class ClassInfoTest extends SapphireTest {
ClassInfo::table_for_object_field('ClassInfoTest_NoFields', 'Title') ClassInfo::table_for_object_field('ClassInfoTest_NoFields', 'Title')
); );
$this->assertEquals('ClassInfoTest_BaseDataClass',
ClassInfo::table_for_object_field('classinfotest_nofields', 'Title')
);
$this->assertEquals('ClassInfoTest_HasFields', $this->assertEquals('ClassInfoTest_HasFields',
ClassInfo::table_for_object_field('ClassInfoTest_HasFields', 'Description') ClassInfo::table_for_object_field('ClassInfoTest_HasFields', 'Description')
); );

View File

@ -138,6 +138,11 @@ class DataListTest extends SapphireTest {
$this->assertEquals('DataObjectTest_TeamComment',$list->dataClass()); $this->assertEquals('DataObjectTest_TeamComment',$list->dataClass());
} }
public function testDataClassCaseInsensitive() {
$list = DataList::create('dataobjecttest_teamcomment');
$this->assertTrue($list->exists());
}
public function testClone() { public function testClone() {
$list = DataObjectTest_TeamComment::get(); $list = DataObjectTest_TeamComment::get();
$this->assertEquals($list, clone($list)); $this->assertEquals($list, clone($list));