API CHANGE Deprecated DataObject->fieldExists() - please use hasField(), hasDatabaseField(), hasOwnTableDatabaseField()

ENHANCEMENT Added DataObject->hasOwnTableDatabaseField(), replaced legacy usage of fieldExists()
ENHANCEMENT Renamed cached static "fieldExists" to "_cache_hasOwnTableDatabaseField"
ENHANCEMENT Added DataObjectTest test cases for checking various field existence levels
API CHANGE Deprecated DataObject->listOfFields() - use custom code instead

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@63337 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2008-09-30 00:20:30 +00:00
parent 2af039785c
commit bf896a2cfd
4 changed files with 219 additions and 27 deletions

View File

@ -701,7 +701,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
foreach($ancestry as $idx => $class) {
$classSingleton = singleton($class);
foreach($this->record as $fieldName => $fieldValue) {
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName] && $fieldType = $classSingleton->fieldExists($fieldName)) {
if(isset($this->changed[$fieldName]) && $this->changed[$fieldName] && $fieldType = $classSingleton->hasOwnTableDatabaseField($fieldName)) {
$fieldObj = $this->obj($fieldName);
if(!isset($manipulation[$class])) $manipulation[$class] = array();
@ -1699,7 +1699,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* @return boolean True if the given field exists
*/
public function hasField($field) {
return array_key_exists($field, $this->record) || $this->fieldExists($field);
return array_key_exists($field, $this->record) || $this->hasOwnTableDatabaseField($field);
}
/**
@ -1712,6 +1712,46 @@ class DataObject extends ViewableData implements DataObjectInterface {
public function hasDatabaseField($field) {
return array_key_exists($field, $this->databaseFields());
}
/**
* Returns the field type of the given field, if it belongs to this class, and not a parent.
* Note that the field type will not include constructor arguments in round brackets, only the classname.
*
* @param string $field Name of the field
* @return string The field type of the given field
*/
public function hasOwnTableDatabaseField($field) {
// Add base fields which are not defined in static $db
if($field == "ID") return "Int";
if($field == "ClassName" && get_parent_class($this) == "DataObject") return "Enum";
if($field == "LastEdited" && get_parent_class($this) == "DataObject") return "Datetime";
if($field == "Created" && get_parent_class($this) == "DataObject") return "Datetime";
// Add fields from Versioned decorator
if($field == "Version") return $this->hasExtension('Versioned') ? "Int" : false;
// get cached fieldmap
$fieldMap = $this->uninherited('_cache_hasOwnTableDatabaseField');
// if no fieldmap is cached, get all fields
if(!$fieldMap) {
// all $db fields on this specific class (no parents)
$fieldMap = $this->uninherited('db', true);
// all has_one relations on this specific class,
// add foreign key
$hasOne = $this->uninherited('has_one', true);
if($hasOne) foreach($hasOne as $fieldName => $fieldSchema) {
$fieldMap[$fieldName . 'ID'] = "Int";
}
// set cached fieldmap
$this->set_uninherited('_cache_hasOwnTableDatabaseField', $fieldMap);
}
// Remove string-based "constructor-arguments" from the DBField definition
return isset($fieldMap[$field]) ? strtok($fieldMap[$field],'(') : null;
}
/**
* Returns true if the member is allowed to do the given action.
@ -1833,32 +1873,13 @@ class DataObject extends ViewableData implements DataObjectInterface {
}
/**
* Returns the field type of the given field, if it belongs to this class, and not a parent.
* Can be used to detect whether the given field exists.
* Note that the field type will not include constructor arguments; only the classname.
* @deprecated 2.3 (For external use) Please use hasField(), hasDatabaseField(), hasOwnTableDatabaseField() instead
*
* @param string $field Name of the field
*
* @return string The field type of the given field
*/
public function fieldExists($field) {
if($field == "ID") return "Int";
if($field == "ClassName" && get_parent_class($this) == "DataObject") return "Enum";
if($field == "LastEdited" && get_parent_class($this) == "DataObject") return "Datetime";
if($field == "Created" && get_parent_class($this) == "DataObject") return "Datetime";
if($field == "Version") return $this->hasExtension('Versioned') ? "Int" : false;
$fieldMap = $this->uninherited('fieldExists');
if(!$fieldMap) {
$fieldMap = $this->uninherited('db', true);
$has = $this->uninherited('has_one', true);
if($has) foreach($has as $fieldName => $fieldSchema) {
$fieldMap[$fieldName . 'ID'] = "Int";
}
$this->set_uninherited('fieldExists', $fieldMap);
}
return isset($fieldMap[$field]) ? strtok($fieldMap[$field],'(') : null;
return $this->hasOwnTableDatabaseField($field);
}
/**
@ -2057,6 +2078,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
* - name: value
* - name: value
*
* @deprecated 2.3 Use custom code
* @return string The fields as an HTML unordered list
*/
function listOfFields() {
@ -2390,7 +2412,10 @@ class DataObject extends ViewableData implements DataObjectInterface {
}
/**
* Get the custom database fields for this object, from self::$db and self::$has_one
* Get the custom database fields for this object, from self::$db and self::$has_one,
* but not built-in fields like ID, ClassName, Created, LastEdited.
*
* @return array
*/
public function customDatabaseFields() {
$db = $this->uninherited('db',true);
@ -2437,15 +2462,22 @@ class DataObject extends ViewableData implements DataObjectInterface {
public function searchableFields() {
// can have mixed format, need to make consistent in most verbose form
$fields = $this->stat('searchable_fields');
$labels = $this->fieldLabels();
// fallback to summary fields
if(!$fields) $fields = array_keys($this->summaryFields());
// we need to make sure the format is unified before
// augumenting fields, so decorators can apply consistent checks
// but also after augumenting fields, because the decorator
// might use the shorthand notation as well
// rewrite array, if it is using shorthand syntax
$rewrite = array();
foreach($fields as $name => $specOrName) {
$identifer = (is_int($name)) ? $specOrName : $name;
if(is_int($name)) {
// Format: array('MyFieldName')
$rewrite[$identifer] = array();
@ -2454,7 +2486,7 @@ class DataObject extends ViewableData implements DataObjectInterface {
// 'filter => 'ExactMatchFilter',
// 'field' => 'NumericField', // optional
// 'title' => 'My Title', // optiona.
// )
// ))
$rewrite[$identifer] = array_merge(
array('filter' => $this->relObject($identifer)->stat('default_search_filter_class')),
(array)$specOrName
@ -2472,8 +2504,10 @@ class DataObject extends ViewableData implements DataObjectInterface {
$rewrite[$identifer]['filter'] = 'PartialMatchFilter';
}
}
$fields = $rewrite;
// apply DataObjectDecorators if present
$this->extend('updateSearchableFields', $fields);
return $fields;

View File

@ -45,7 +45,9 @@ abstract class DataObjectDecorator extends Extension {
eval("$className::\$$relationType = array_merge((array){$className}::\$$relationType, (array)\$fields);");
$this->owner->set_stat($relationType, eval("return $className::\$$relationType;"));
}
$this->owner->set_uninherited('fieldExists', null);
// clear previously set caches from DataObject->hasOwnTableDatabaseField()
$this->owner->set_uninherited('_cache_hasOwnTableDatabaseField', null);
}
}
}

View File

@ -288,6 +288,126 @@ class DataObjectTest extends SapphireTest {
$existingTeam->write();
$this->assertEquals(0, DB::query("SELECT CaptainID FROM DataObjectTest_Team WHERE ID = $existingTeam->ID")->value());
}
function testFieldExistence() {
$teamInstance = $this->objFromFixture('DataObjectTest_Team', 'team1');
$teamSingleton = singleton('DataObjectTest_Team');
$subteamInstance = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
$subteamSingleton = singleton('DataObjectTest_SubTeam');
/* hasField() singleton checks */
$this->assertTrue($teamSingleton->hasField('ID'), 'hasField() finds built-in fields in singletons');
$this->assertTrue($teamSingleton->hasField('Title'), 'hasField() finds custom fields in singletons');
/* hasField() instance checks */
$this->assertFalse($teamInstance->hasField('NonExistingField'), 'hasField() doesnt find non-existing fields in instances');
$this->assertTrue($teamInstance->hasField('ID'), 'hasField() finds built-in fields in instances');
$this->assertTrue($teamInstance->hasField('Created'), 'hasField() finds built-in fields in instances');
$this->assertTrue($teamInstance->hasField('DatabaseField'), 'hasField() finds custom fields in instances');
//$this->assertFalse($teamInstance->hasField('SubclassDatabaseField'), 'hasField() doesnt find subclass fields in parentclass instances');
//$this->assertTrue($teamInstance->hasField('DynamicField'), 'hasField() finds dynamic getters in instances');
$this->assertTrue($teamInstance->hasField('HasOneRelationshipID'), 'hasField() finds foreign keys in instances');
$this->assertTrue($teamInstance->hasField('DecoratedDatabaseField'), 'hasField() finds decorated fields in instances');
$this->assertTrue($teamInstance->hasField('DecoratedHasOneRelationshipID'), 'hasField() finds decorated foreign keys in instances');
//$this->assertTrue($teamInstance->hasField('DecoratedDynamicField'), 'hasField() includes decorated dynamic getters in instances');
/* hasField() subclass checks */
$this->assertTrue($subteamInstance->hasField('ID'), 'hasField() finds built-in fields in subclass instances');
$this->assertTrue($subteamInstance->hasField('Created'), 'hasField() finds built-in fields in subclass instances');
$this->assertTrue($subteamInstance->hasField('DatabaseField'), 'hasField() finds custom fields in subclass instances');
$this->assertTrue($subteamInstance->hasField('SubclassDatabaseField'), 'hasField() finds custom fields in subclass instances');
//$this->assertTrue($subteamInstance->hasField('DynamicField'), 'hasField() finds dynamic getters in subclass instances');
$this->assertTrue($subteamInstance->hasField('HasOneRelationshipID'), 'hasField() finds foreign keys in subclass instances');
$this->assertTrue($subteamInstance->hasField('DecoratedDatabaseField'), 'hasField() finds decorated fields in subclass instances');
$this->assertTrue($subteamInstance->hasField('DecoratedHasOneRelationshipID'), 'hasField() finds decorated foreign keys in subclass instances');
/* hasDatabaseField() singleton checks */
//$this->assertTrue($teamSingleton->hasDatabaseField('ID'), 'hasDatabaseField() finds built-in fields in singletons');
$this->assertTrue($teamSingleton->hasDatabaseField('Title'), 'hasDatabaseField() finds custom fields in singletons');
/* hasDatabaseField() instance checks */
$this->assertFalse($teamInstance->hasDatabaseField('NonExistingField'), 'hasDatabaseField() doesnt find non-existing fields in instances');
//$this->assertTrue($teamInstance->hasDatabaseField('ID'), 'hasDatabaseField() finds built-in fields in instances');
$this->assertTrue($teamInstance->hasDatabaseField('Created'), 'hasDatabaseField() finds built-in fields in instances');
$this->assertTrue($teamInstance->hasDatabaseField('DatabaseField'), 'hasDatabaseField() finds custom fields in instances');
$this->assertFalse($teamInstance->hasDatabaseField('SubclassDatabaseField'), 'hasDatabaseField() doesnt find subclass fields in parentclass instances');
//$this->assertFalse($teamInstance->hasDatabaseField('DynamicField'), 'hasDatabaseField() doesnt dynamic getters in instances');
$this->assertTrue($teamInstance->hasDatabaseField('HasOneRelationshipID'), 'hasDatabaseField() finds foreign keys in instances');
$this->assertTrue($teamInstance->hasDatabaseField('DecoratedDatabaseField'), 'hasDatabaseField() finds decorated fields in instances');
$this->assertTrue($teamInstance->hasDatabaseField('DecoratedHasOneRelationshipID'), 'hasDatabaseField() finds decorated foreign keys in instances');
$this->assertFalse($teamInstance->hasDatabaseField('DecoratedDynamicField'), 'hasDatabaseField() doesnt include decorated dynamic getters in instances');
/* hasDatabaseField() subclass checks */
$this->assertTrue($subteamInstance->hasField('DatabaseField'), 'hasField() finds custom fields in subclass instances');
$this->assertTrue($subteamInstance->hasField('SubclassDatabaseField'), 'hasField() finds custom fields in subclass instances');
}
function testFieldInheritance() {
$teamInstance = $this->objFromFixture('DataObjectTest_Team', 'team1');
$subteamInstance = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
$this->assertEquals(
array_keys($teamInstance->inheritedDatabaseFields()),
array(
//'ID',
//'ClassName',
//'Created',
//'LastEdited',
'Title',
'DatabaseField',
'DecoratedDatabaseField',
'CaptainID',
'HasOneRelationshipID',
'DecoratedHasOneRelationshipID'
),
'inheritedDatabaseFields() contains all fields defined on instance, including base fields, decorated fields and foreign keys'
);
$this->assertEquals(
array_keys($teamInstance->databaseFields()),
array(
//'ID',
'ClassName',
'Created',
'LastEdited',
'Title',
'DatabaseField',
'DecoratedDatabaseField',
'CaptainID',
'HasOneRelationshipID',
'DecoratedHasOneRelationshipID'
),
'databaseFields() contains only fields defined on instance, including base fields, decorated fields and foreign keys'
);
$this->assertEquals(
array_keys($subteamInstance->inheritedDatabaseFields()),
array(
//'ID',
//'ClassName',
//'Created',
//'LastEdited',
'SubclassDatabaseField',
'Title',
'DatabaseField',
'DecoratedDatabaseField',
'CaptainID',
'HasOneRelationshipID',
'DecoratedHasOneRelationshipID',
),
'inheritedDatabaseFields() on subclass contains all fields defined on instance, including base fields, decorated fields and foreign keys'
);
$this->assertEquals(
array_keys($subteamInstance->databaseFields()),
array(
'SubclassDatabaseField',
),
'databaseFields() on subclass contains only fields defined on instance'
);
}
}
class DataObjectTest_Player extends Member implements TestOnly {
@ -302,16 +422,49 @@ class DataObjectTest_Team extends DataObject implements TestOnly {
static $db = array(
'Title' => 'Text',
'DatabaseField' => 'Text'
);
static $has_one = array(
"Captain" => 'DataObjectTest_Player',
'HasOneRelationship' => 'DataObjectTest_Player',
);
static $many_many = array(
'Players' => 'DataObjectTest_Player'
);
function getDynamicField() {
return 'dynamicfield';
}
}
class DataObjectTest_SubTeam extends DataObjectTest_Team implements TestOnly {
static $db = array(
'SubclassDatabaseField' => 'Text'
);
}
class DataObjectTest_Team_Decorator extends DataObjectDecorator implements TestOnly {
function extraDBFields() {
return array(
'db' => array(
'DecoratedDatabaseField' => 'Text'
),
'has_one' => array(
'DecoratedHasOneRelationship' => 'DataObjectTest_Player'
)
);
}
function getDecoratedDynamicField() {
return "decorated dynamic field";
}
}
DataObject::add_extension('DataObjectTest_Team', 'DataObjectTest_Team_Decorator');
?>

View File

@ -46,4 +46,7 @@ DataObjectTest_Player:
FirstName: Player 1
player2:
FirstName: Player 2
Teams: =>DataObjectTest_Team.team1,=>DataObjectTest_Team.team2
Teams: =>DataObjectTest_Team.team1,=>DataObjectTest_Team.team2
DataObjectTest_SubTeam:
subteam1:
Title: Subteam 1