mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
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:
parent
2af039785c
commit
bf896a2cfd
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
||||
?>
|
||||
|
@ -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
|
Loading…
Reference in New Issue
Block a user