mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
API CHANGE: Added DataObject::is_composite_field() and DataObject::composite_fields() to efficiently keep track of which fields are composite.
API CHANGE: Deprecated DataObject::databaseFields() in favour of the static DataObject::database_fields() git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@84161 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
d8113a667e
commit
5790ddf708
@ -195,20 +195,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
public static function custom_database_fields($class) {
|
public static function custom_database_fields($class) {
|
||||||
$fields = Object::uninherited_static($class, 'db');
|
$fields = Object::uninherited_static($class, 'db');
|
||||||
|
|
||||||
// Remove CompositeDBField instances, and replace with the actually used fields
|
foreach(self::composite_fields($class, false) as $fieldName => $fieldClass) {
|
||||||
if($fields) foreach($fields as $fieldName => $fieldClass) {
|
|
||||||
// Strip off any parameters
|
|
||||||
if(strpos('(', $fieldClass) !== FALSE) $fieldClass = substr(0,strpos('(', $fieldClass), $fieldClass);
|
|
||||||
if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
|
|
||||||
// Remove the original fieldname, its not an actual database column
|
// Remove the original fieldname, its not an actual database column
|
||||||
unset($fields[$fieldName]);
|
unset($fields[$fieldName]);
|
||||||
|
|
||||||
// Add all composite columns
|
// Add all composite columns
|
||||||
$compositeFields = singleton($fieldClass)->compositeDatabaseFields();
|
$compositeFields = singleton($fieldClass)->compositeDatabaseFields();
|
||||||
if($compositeFields) foreach($compositeFields as $compositeName => $spec) {
|
if($compositeFields) foreach($compositeFields as $compositeName => $spec) {
|
||||||
$fields["{$fieldName}{$compositeName}"] = $spec;
|
$fields["{$fieldName}{$compositeName}"] = $spec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Add has_one relationships
|
// Add has_one relationships
|
||||||
$hasOne = Object::uninherited_static($class, 'has_one');
|
$hasOne = Object::uninherited_static($class, 'has_one');
|
||||||
@ -219,6 +215,64 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
return (array)$fields;
|
return (array)$fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static $_cache_custom_database_fields = array();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the field class if the given db field on the class is a composite field.
|
||||||
|
* Will check all applicable ancestor classes and aggregate results.
|
||||||
|
*/
|
||||||
|
static function is_composite_field($class, $name, $aggregated = true) {
|
||||||
|
if(!isset(self::$_cache_composite_fields[$class])) self::cache_composite_fields($class);
|
||||||
|
|
||||||
|
if(isset(self::$_cache_composite_fields[$class][$name])) {
|
||||||
|
return self::$_cache_composite_fields[$class][$name];
|
||||||
|
|
||||||
|
} else if($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
|
||||||
|
return self::is_composite_field($parentClass, $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all the composite if the given db field on the class is a composite field.
|
||||||
|
* Will check all applicable ancestor classes and aggregate results.
|
||||||
|
*/
|
||||||
|
static function composite_fields($class, $aggregated = true) {
|
||||||
|
if(!isset(self::$_cache_composite_fields[$class])) self::cache_composite_fields($class);
|
||||||
|
|
||||||
|
$compositeFields = self::$_cache_composite_fields[$class];
|
||||||
|
|
||||||
|
if($aggregated && $class != 'DataObject' && ($parentClass=get_parent_class($class)) != 'DataObject') {
|
||||||
|
$compositeFields = array_merge($compositeFields,
|
||||||
|
self::composite_fields($parentClass));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $compositeFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal cacher for the composite field information
|
||||||
|
*/
|
||||||
|
private static function cache_composite_fields($class) {
|
||||||
|
$compositeFields = array();
|
||||||
|
|
||||||
|
$fields = Object::uninherited_static($class, 'db');
|
||||||
|
if($fields) foreach($fields as $fieldName => $fieldClass) {
|
||||||
|
// Strip off any parameters
|
||||||
|
$bPos = strpos('(', $fieldClass);
|
||||||
|
if($bPos !== FALSE) $fieldClass = substr(0,$bPos, $fieldClass);
|
||||||
|
|
||||||
|
// Test to see if it implements CompositeDBField
|
||||||
|
if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
|
||||||
|
$compositeFields[$fieldName] = $fieldClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$_cache_composite_fields[$class] = $compositeFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static $_cache_composite_fields = array();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new DataObject.
|
* Construct a new DataObject.
|
||||||
@ -1494,29 +1548,34 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
* @return string|array The class of the one-to-many component, or an array of all one-to-many components and their classes.
|
* @return string|array The class of the one-to-many component, or an array of all one-to-many components and their classes.
|
||||||
*/
|
*/
|
||||||
public function has_many($component = null) {
|
public function has_many($component = null) {
|
||||||
$classes = ClassInfo::ancestry($this);
|
static $ignoreClasses = array('ViewableData', 'Object', 'DataObject');
|
||||||
|
$classes = ClassInfo::ancestry($this->class);
|
||||||
foreach($classes as $class) {
|
|
||||||
if(in_array($class, array('ViewableData', 'Object', 'DataObject'))) continue;
|
|
||||||
|
|
||||||
if($component) {
|
if($component) {
|
||||||
$hasMany = Object::uninherited_static($class, 'has_many');
|
foreach($classes as $class) {
|
||||||
|
if(in_array($class, $ignoreClasses)) continue;
|
||||||
|
|
||||||
if(isset($hasMany[$component])) {
|
$hasMany = (array)Object::uninherited_static($class, 'has_many');
|
||||||
return $hasMany[$component];
|
if(isset($hasMany[$component])) return $hasMany[$component];
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
$items = array();
|
||||||
|
foreach($classes as $class) {
|
||||||
|
if(in_array($class, $ignoreClasses)) continue;
|
||||||
|
|
||||||
$newItems = (array)Object::uninherited_static($class, 'has_many');
|
$newItems = (array)Object::uninherited_static($class, 'has_many');
|
||||||
// Validate the data
|
// Validate the data
|
||||||
foreach($newItems as $k => $v) {
|
foreach($newItems as $k => $v) {
|
||||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) user_error("$class::\$has_many has a bad entry: "
|
if(!is_string($k) || is_numeric($k) || !is_string($v)) user_error("$class::\$has_many has a bad entry: "
|
||||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
|
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
|
||||||
}
|
}
|
||||||
$items = isset($items) ? array_merge($newItems, (array)$items) : $newItems;
|
$items = array_merge($newItems, (array)$items);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isset($items) ? $items : null;
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1868,8 +1927,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
if(isset($this->record[$field]) && is_object($this->record[$field])) return $this->record[$field];
|
if(isset($this->record[$field]) && is_object($this->record[$field])) return $this->record[$field];
|
||||||
|
|
||||||
// Otherwise, we need to determine if this is a complex field
|
// Otherwise, we need to determine if this is a complex field
|
||||||
$fieldClass = $this->db($field);
|
if(self::is_composite_field($this->class, $field)) {
|
||||||
if($fieldClass && ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
|
|
||||||
$helperPair = $this->castingHelperPair($field);
|
$helperPair = $this->castingHelperPair($field);
|
||||||
$constructor = $helperPair['castingHelper'];
|
$constructor = $helperPair['castingHelper'];
|
||||||
$fieldName = $field;
|
$fieldName = $field;
|
||||||
@ -1927,7 +1985,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
}
|
}
|
||||||
|
|
||||||
if($databaseFieldsOnly) {
|
if($databaseFieldsOnly) {
|
||||||
$customDatabaseFields = $this->customDatabaseFields();
|
$customDatabaseFields = self::custom_database_fields($this->class);
|
||||||
$fields = array_intersect_key($this->changed, $customDatabaseFields);
|
$fields = array_intersect_key($this->changed, $customDatabaseFields);
|
||||||
} else {
|
} else {
|
||||||
$fields = $this->changed;
|
$fields = $this->changed;
|
||||||
@ -2088,20 +2146,19 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
|
|
||||||
// if no fieldmap is cached, get all fields
|
// if no fieldmap is cached, get all fields
|
||||||
if(!$fieldMap) {
|
if(!$fieldMap) {
|
||||||
|
$fieldMap = Object::uninherited_static($this->class, 'db');
|
||||||
|
|
||||||
// all $db fields on this specific class (no parents)
|
// all $db fields on this specific class (no parents)
|
||||||
$fieldMap = $this->uninherited('db', true);
|
foreach(self::composite_fields($this->class, false) as $fieldname => $fieldtype) {
|
||||||
if($fieldMap) foreach($fieldMap as $fieldname => $fieldtype){
|
|
||||||
if(ClassInfo::classImplements($fieldtype, 'CompositeDBField')){
|
|
||||||
$combined_db = singleton($fieldtype)->compositeDatabaseFields();
|
$combined_db = singleton($fieldtype)->compositeDatabaseFields();
|
||||||
foreach($combined_db as $name => $type){
|
foreach($combined_db as $name => $type){
|
||||||
$fieldMap[$fieldname.$name] = $type;
|
$fieldMap[$fieldname.$name] = $type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// all has_one relations on this specific class,
|
// all has_one relations on this specific class,
|
||||||
// add foreign key
|
// add foreign key
|
||||||
$hasOne = $this->uninherited('has_one', true);
|
$hasOne = Object::uninherited_static($this->class, 'has_one');
|
||||||
if($hasOne) foreach($hasOne as $fieldName => $fieldSchema) {
|
if($hasOne) foreach($hasOne as $fieldName => $fieldSchema) {
|
||||||
$fieldMap[$fieldName . 'ID'] = "ForeignKey";
|
$fieldMap[$fieldName . 'ID'] = "ForeignKey";
|
||||||
}
|
}
|
||||||
@ -2400,13 +2457,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
|
|
||||||
// Add SQL for multi-value fields
|
// Add SQL for multi-value fields
|
||||||
$databaseFields = self::database_fields($tableClass);
|
$databaseFields = self::database_fields($tableClass);
|
||||||
|
$compositeFields = self::composite_fields($tableClass, false);
|
||||||
if($databaseFields) foreach($databaseFields as $k => $v) {
|
if($databaseFields) foreach($databaseFields as $k => $v) {
|
||||||
if(ClassInfo::classImplements($v, 'CompositeDBField')) {
|
if(!isset($compositeFields[$k])) {
|
||||||
singleton($tableClass)->dbObject($k)->addToQuery($query);
|
|
||||||
} else {
|
|
||||||
$query->select[] = "\"$tableClass\".\"$k\"";
|
$query->select[] = "\"$tableClass\".\"$k\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if($compositeFields) foreach($compositeFields as $k => $v) {
|
||||||
|
$this->dbObject($k)->addToQuery($query);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2771,7 +2830,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
*/
|
*/
|
||||||
public function requireTable() {
|
public function requireTable() {
|
||||||
// Only build the table if we've actually got fields
|
// Only build the table if we've actually got fields
|
||||||
$fields = $this->databaseFields();
|
$fields = self::database_fields($this->class);
|
||||||
$indexes = $this->databaseIndexes();
|
$indexes = $this->databaseIndexes();
|
||||||
|
|
||||||
if($fields) {
|
if($fields) {
|
||||||
@ -2842,6 +2901,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
* @see DataObject::database_fields()
|
* @see DataObject::database_fields()
|
||||||
*/
|
*/
|
||||||
public function databaseFields() {
|
public function databaseFields() {
|
||||||
|
user_error("databaseFields() is deprecated; use self::database_fields() "
|
||||||
|
. "instead", E_USER_NOTICE);
|
||||||
return self::database_fields($this->class);
|
return self::database_fields($this->class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2849,6 +2910,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
* @see DataObject::custom_database_fields()
|
* @see DataObject::custom_database_fields()
|
||||||
*/
|
*/
|
||||||
public function customDatabaseFields() {
|
public function customDatabaseFields() {
|
||||||
|
user_error("customDatabaseFields() is deprecated; use self::custom_database_fields() "
|
||||||
|
. "instead", E_USER_NOTICE);
|
||||||
return self::custom_database_fields($this->class);
|
return self::custom_database_fields($this->class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ class DatabaseAdmin extends Controller {
|
|||||||
$subclasses = ClassInfo::subclassesFor($baseClass);
|
$subclasses = ClassInfo::subclassesFor($baseClass);
|
||||||
unset($subclasses[0]);
|
unset($subclasses[0]);
|
||||||
foreach($subclasses as $k => $subclass) {
|
foreach($subclasses as $k => $subclass) {
|
||||||
if(!singleton($subclass)->databaseFields()) {
|
if(DataObject::database_fields($subclass)) {
|
||||||
unset($subclasses[$k]);
|
unset($subclasses[$k]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -211,7 +211,7 @@ class Versioned extends DataObjectDecorator {
|
|||||||
// Build a list of suffixes whose tables need versioning
|
// Build a list of suffixes whose tables need versioning
|
||||||
$allSuffixes = array();
|
$allSuffixes = array();
|
||||||
foreach (Versioned::$versionableExtensions as $versionableExtension => $suffixes) {
|
foreach (Versioned::$versionableExtensions as $versionableExtension => $suffixes) {
|
||||||
if ($this->owner->hasExtension($versionableExtension) && singleton($versionableExtension)->stat('enabled')) {
|
if ($this->owner->hasExtension($versionableExtension)) {
|
||||||
$allSuffixes = array_merge($allSuffixes, (array)$suffixes);
|
$allSuffixes = array_merge($allSuffixes, (array)$suffixes);
|
||||||
foreach ((array)$suffixes as $suffix) {
|
foreach ((array)$suffixes as $suffix) {
|
||||||
$allSuffixes[$suffix] = $versionableExtension;
|
$allSuffixes[$suffix] = $versionableExtension;
|
||||||
@ -229,7 +229,7 @@ class Versioned extends DataObjectDecorator {
|
|||||||
if ($suffix) $table = "{$classTable}_$suffix";
|
if ($suffix) $table = "{$classTable}_$suffix";
|
||||||
else $table = $classTable;
|
else $table = $classTable;
|
||||||
|
|
||||||
if(($fields = $this->owner->databaseFields())) {
|
if($fields = DataObject::database_fields($this->owner->class)) {
|
||||||
$indexes = $this->owner->databaseIndexes();
|
$indexes = $this->owner->databaseIndexes();
|
||||||
if ($suffix && ($ext = $this->owner->extInstance($allSuffixes[$suffix]))) {
|
if ($suffix && ($ext = $this->owner->extInstance($allSuffixes[$suffix]))) {
|
||||||
if (!$ext->isVersionedTable($table)) continue;
|
if (!$ext->isVersionedTable($table)) continue;
|
||||||
@ -392,8 +392,9 @@ class Versioned extends DataObjectDecorator {
|
|||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
function canBeVersioned($table) {
|
function canBeVersioned($table) {
|
||||||
$dbFields = singleton($table)->databaseFields();
|
return ClassInfo::exists($table)
|
||||||
return !(!ClassInfo::exists($table) || !is_subclass_of($table, 'DataObject' ) || empty( $dbFields ));
|
&& ClassInfo::is_subclass_of($table, 'DataObject')
|
||||||
|
&& DataObject::has_own_table($table);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -460,7 +460,7 @@ class DataObjectTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
array_keys($teamInstance->databaseFields()),
|
array_keys(DataObject::database_fields('DataObjectTest_Team')),
|
||||||
array(
|
array(
|
||||||
//'ID',
|
//'ID',
|
||||||
'ClassName',
|
'ClassName',
|
||||||
@ -495,7 +495,7 @@ class DataObjectTest extends SapphireTest {
|
|||||||
);
|
);
|
||||||
|
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
array_keys($subteamInstance->databaseFields()),
|
array_keys(DataObject::database_fields('DataObjectTest_SubTeam')),
|
||||||
array(
|
array(
|
||||||
'SubclassDatabaseField',
|
'SubclassDatabaseField',
|
||||||
),
|
),
|
||||||
|
@ -11,12 +11,37 @@ class CompositeDBFieldTest extends SapphireTest {
|
|||||||
$this->assertTrue($obj->hasDatabaseField('MyMoneyCurrency'));
|
$this->assertTrue($obj->hasDatabaseField('MyMoneyCurrency'));
|
||||||
$this->assertFalse($obj->hasDatabaseField('MyMoney'));
|
$this->assertFalse($obj->hasDatabaseField('MyMoney'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test DataObject::composite_fields() and DataObject::is_composite_field()
|
||||||
|
*/
|
||||||
|
function testCompositeFieldMetaDataFunctions() {
|
||||||
|
$this->assertEquals('Money', DataObject::is_composite_field('CompositeDBFieldTest_DataObject', 'MyMoney'));
|
||||||
|
$this->assertNull(DataObject::is_composite_field('CompositeDBFieldTest_DataObject', 'Title'));
|
||||||
|
$this->assertEquals(array('MyMoney' => 'Money'),
|
||||||
|
DataObject::composite_fields('CompositeDBFieldTest_DataObject'));
|
||||||
|
|
||||||
|
|
||||||
|
$this->assertEquals('Money', DataObject::is_composite_field('SubclassedDBFieldObject', 'MyMoney'));
|
||||||
|
$this->assertEquals('Money', DataObject::is_composite_field('SubclassedDBFieldObject', 'OtherMoney'));
|
||||||
|
$this->assertNull(DataObject::is_composite_field('SubclassedDBFieldObject', 'Title'));
|
||||||
|
$this->assertNull(DataObject::is_composite_field('SubclassedDBFieldObject', 'OtherField'));
|
||||||
|
$this->assertEquals(array('MyMoney' => 'Money', 'OtherMoney' => 'Money'),
|
||||||
|
DataObject::composite_fields('SubclassedDBFieldObject'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CompositeDBFieldTest_DataObject extends DataObject implements TestOnly {
|
class CompositeDBFieldTest_DataObject extends DataObject {
|
||||||
static $db = array(
|
static $db = array(
|
||||||
'Title' => 'Text',
|
'Title' => 'Text',
|
||||||
'MyMoney' => 'Money',
|
'MyMoney' => 'Money',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SubclassedDBFieldObject extends CompositeDBFieldTest_DataObject {
|
||||||
|
static $db = array(
|
||||||
|
'OtherField' => 'Text',
|
||||||
|
'OtherMoney' => 'Money',
|
||||||
|
);
|
||||||
|
}
|
||||||
?>
|
?>
|
Loading…
x
Reference in New Issue
Block a user