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
if (class_exists($name)) {
$parents = array_reverse(array_keys(ClassInfo::ancestry($name)));
$parents = array_reverse(array_values(ClassInfo::ancestry($name)));
array_shift($parents);
foreach ($parents as $parent) {

View File

@ -63,6 +63,7 @@ class ClassInfo {
* @param string $class Class name to check enum values for ClassName field
*/
public static function getValidSubClasses($class = 'SiteTree', $includeUnbacked = false) {
$class = self::class_name($class);
$classes = DB::getConn()->enumValuesForField($class, 'ClassName');
if (!$includeUnbacked) $classes = array_filter($classes, array('ClassInfo', 'exists'));
return $classes;
@ -79,9 +80,7 @@ class ClassInfo {
public static function dataClassesFor($class) {
$result = array();
if (is_object($class)) {
$class = get_class($class);
}
$class = self::class_name($class);
$classes = array_merge(
self::ancestry($class),
@ -102,7 +101,7 @@ class ClassInfo {
* @return string
*/
public static function baseDataClass($class) {
if (is_object($class)) $class = get_class($class);
$class = self::class_name($class);
if (!is_subclass_of($class, 'DataObject')) {
throw new InvalidArgumentException("$class is not a subclass of DataObject");
@ -126,7 +125,7 @@ class ClassInfo {
* <code>
* ClassInfo::subclassesFor('BaseClass');
* array(
* 0 => 'BaseClass',
* 'BaseClass' => 'BaseClass',
* 'ChildClass' => 'ChildClass',
* 'GrandChildClass' => 'GrandChildClass'
* )
@ -136,8 +135,10 @@ class ClassInfo {
* @return array Names of all subclasses as an associative array.
*/
public static function subclassesFor($class) {
//normalise class case
$className = self::class_name($class);
$descendants = SS_ClassLoader::instance()->getManifest()->getDescendantsOf($class);
$result = array($class => $class);
$result = array($className => $className);
if ($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
* array, sorted with the root class first.
@ -155,9 +173,11 @@ class ClassInfo {
* @return array
*/
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;
if(!isset(self::$_cache_ancestry[$cacheKey])) {
$ancestry = array();
@ -184,7 +204,7 @@ class ClassInfo {
* Returns true if the given class implements the given interface
*/
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();
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])) {
self::$method_from_cache[$class][$method] = false;
if (!array_key_exists($lMethod, self::$method_from_cache[$lClass])) {
self::$method_from_cache[$lClass][$lMethod] = false;
$classRef = new ReflectionClass($class);
if ($classRef->hasMethod($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
*/
public static function table_for_object_field($candidateClass, $fieldName) {
if(!$candidateClass || !$fieldName) {
if(!$candidateClass || !$fieldName || !is_subclass_of($candidateClass, 'DataObject')) {
return null;
}
$exists = class_exists($candidateClass);
//normalise class name
$candidateClass = self::class_name($candidateClass);
$exists = self::exists($candidateClass);
while($candidateClass && $candidateClass != 'DataObject' && $exists) {
if(DataObject::has_own_table($candidateClass)) {
@ -277,7 +303,7 @@ class ClassInfo {
}
$candidateClass = get_parent_class($candidateClass);
$exists = class_exists($candidateClass);
$exists = $candidateClass && self::exists($candidateClass);
}
if(!$candidateClass || !$exists) {

View File

@ -2441,8 +2441,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return 'Int';
}
// get cached fieldmap
$fieldMap = isset(DataObject::$cache_has_own_table_field[$this->class])
? DataObject::$cache_has_own_table_field[$this->class] : null;
$lClass = strtolower($this->class);
$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(!$fieldMap) {
@ -2464,7 +2465,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
// 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
@ -2482,18 +2483,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @return bool
*/
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(get_parent_class($dataClass) == 'DataObject') {
DataObject::$cache_has_own_table[$dataClass] = true;
if(!isset(DataObject::$cache_has_own_table[$lDataClass])) {
if(get_parent_class($dataClass) == 'DataObject' || Config::inst()->get($dataClass, 'db', Config::UNINHERITED)
|| Config::inst()->get($dataClass, 'has_one', Config::UNINHERITED)) {
DataObject::$cache_has_own_table[$lDataClass] = $dataClass;
} else {
DataObject::$cache_has_own_table[$dataClass]
= Config::inst()->get($dataClass, 'db', Config::UNINHERITED)
|| Config::inst()->get($dataClass, 'has_one', Config::UNINHERITED);
DataObject::$cache_has_own_table[$lDataClass] = false;
}
}
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;
}
$tableNames = array_keys($tableClasses);
$tableNames = array_values($tableClasses);
$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

View File

@ -6,10 +6,18 @@
*/
class ClassInfoTest extends SapphireTest {
public function setUp() {
parent::setUp();
ClassInfo::reset_db_cache();
}
public function testExists() {
$this->assertTrue(ClassInfo::exists('Object'));
$this->assertTrue(ClassInfo::exists('object'));
$this->assertTrue(ClassInfo::exists('ClassInfoTest'));
$this->assertTrue(ClassInfo::exists('CLASSINFOTEST'));
$this->assertTrue(ClassInfo::exists('stdClass'));
$this->assertTrue(ClassInfo::exists('stdCLASS'));
}
public function testSubclassesFor() {
@ -22,6 +30,16 @@ class ClassInfoTest extends SapphireTest {
),
'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() {
@ -34,11 +52,11 @@ class ClassInfoTest extends SapphireTest {
$classes,
'ClassInfo::classes_for_folder() returns classes matching the filename'
);
// $this->assertContains(
// 'ClassInfoTest_BaseClass',
// $classes,
// 'ClassInfo::classes_for_folder() returns additional classes not matching the filename'
// );
$this->assertContains(
'classinfotest_baseclass',
$classes,
'ClassInfo::classes_for_folder() returns additional classes not matching the filename'
);
}
/**
@ -46,8 +64,11 @@ class ClassInfoTest extends SapphireTest {
*/
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_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->setExpectedException('InvalidArgumentException');
ClassInfo::baseDataClass('DataObject');
@ -67,6 +88,13 @@ class ClassInfoTest extends SapphireTest {
));
$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);
$this->assertEquals(array('ClassInfoTest_BaseClass' => 'ClassInfoTest_BaseClass'), $ancestry,
'$tablesOnly option excludes memory-only inheritance classes'
@ -89,8 +117,11 @@ class ClassInfoTest extends SapphireTest {
'ClassInfoTest_HasFields',
);
ClassInfo::reset_db_cache();
$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]));
$expect = array(
@ -98,7 +129,10 @@ class ClassInfoTest extends SapphireTest {
'ClassInfoTest_HasFields' => 'ClassInfoTest_HasFields',
);
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor($classes[2]));
ClassInfo::reset_db_cache();
$this->assertEquals($expect, ClassInfo::dataClassesFor(strtolower($classes[2])));
}
public function testTableForObjectField() {
@ -106,6 +140,10 @@ class ClassInfoTest extends SapphireTest {
ClassInfo::table_for_object_field('ClassInfoTest_WithRelation', 'RelationID')
);
$this->assertEquals('ClassInfoTest_WithRelation',
ClassInfo::table_for_object_field('ClassInfoTest_withrelation', 'RelationID')
);
$this->assertEquals('ClassInfoTest_BaseDataClass',
ClassInfo::table_for_object_field('ClassInfoTest_BaseDataClass', 'Title')
);
@ -118,6 +156,10 @@ class ClassInfoTest extends SapphireTest {
ClassInfo::table_for_object_field('ClassInfoTest_NoFields', 'Title')
);
$this->assertEquals('ClassInfoTest_BaseDataClass',
ClassInfo::table_for_object_field('classinfotest_nofields', 'Title')
);
$this->assertEquals('ClassInfoTest_HasFields',
ClassInfo::table_for_object_field('ClassInfoTest_HasFields', 'Description')
);

View File

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