mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Refactor CompositeDBField into an abstract class
API Refactor ClassName into DBClassName API Update PolymorphicForeignKey to use new CompositeDBField and DBClassName CompositeDBField is now an interface to nested fields on an underlying dataobject, allowing field manipulation to be performed at the field and dataobject level without having to synchronise them manually.
This commit is contained in:
parent
43430e27cf
commit
9872fbef4d
@ -106,15 +106,18 @@ class MoneyField extends FormField {
|
|||||||
* (see @link MoneyFieldTest_CustomSetter_Object for more information)
|
* (see @link MoneyFieldTest_CustomSetter_Object for more information)
|
||||||
*/
|
*/
|
||||||
public function saveInto(DataObjectInterface $dataObject) {
|
public function saveInto(DataObjectInterface $dataObject) {
|
||||||
$fieldName = $this->name;
|
$fieldName = $this->getName();
|
||||||
if($dataObject->hasMethod("set$fieldName")) {
|
if($dataObject->hasMethod("set$fieldName")) {
|
||||||
$dataObject->$fieldName = DBField::create_field('Money', array(
|
$dataObject->$fieldName = DBField::create_field('Money', array(
|
||||||
"Currency" => $this->fieldCurrency->dataValue(),
|
"Currency" => $this->fieldCurrency->dataValue(),
|
||||||
"Amount" => $this->fieldAmount->dataValue()
|
"Amount" => $this->fieldAmount->dataValue()
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
$dataObject->$fieldName->setCurrency($this->fieldCurrency->dataValue());
|
$currencyField = "{$fieldName}Currency";
|
||||||
$dataObject->$fieldName->setAmount($this->fieldAmount->dataValue());
|
$amountField = "{$fieldName}Amount";
|
||||||
|
|
||||||
|
$dataObject->$currencyField = $this->fieldCurrency->dataValue();
|
||||||
|
$dataObject->$amountField = $this->fieldAmount->dataValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,8 +186,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
|
|
||||||
// base fields which are not defined in static $db
|
// base fields which are not defined in static $db
|
||||||
private static $fixed_fields = array(
|
private static $fixed_fields = array(
|
||||||
'ID' => 'Int',
|
'ID' => 'PrimaryKey',
|
||||||
'ClassName' => 'Enum',
|
'ClassName' => 'DBClassName',
|
||||||
'LastEdited' => 'SS_Datetime',
|
'LastEdited' => 'SS_Datetime',
|
||||||
'Created' => 'SS_Datetime',
|
'Created' => 'SS_Datetime',
|
||||||
);
|
);
|
||||||
@ -229,45 +229,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
Config::inst()->update('DataObject', 'validation_enabled', (bool)$enable);
|
Config::inst()->update('DataObject', 'validation_enabled', (bool)$enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @var [string] - class => ClassName field definition cache for self::database_fields
|
|
||||||
*/
|
|
||||||
private static $classname_spec_cache = array();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all cached classname specs. It's necessary to clear all cached subclassed names
|
* Clear all cached classname specs. It's necessary to clear all cached subclassed names
|
||||||
* for any classes if a new class manifest is generated.
|
* for any classes if a new class manifest is generated.
|
||||||
*/
|
*/
|
||||||
public static function clear_classname_spec_cache() {
|
public static function clear_classname_spec_cache() {
|
||||||
self::$classname_spec_cache = array();
|
DBClassName::clear_classname_cache();
|
||||||
PolymorphicForeignKey::clear_classname_spec_cache();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the specification for the ClassName field for the given class
|
|
||||||
*
|
|
||||||
* @param string $class
|
|
||||||
* @param boolean $queryDB Determine if the DB may be queried for additional information
|
|
||||||
* @return string Resulting ClassName spec. If $queryDB is true this will include all
|
|
||||||
* legacy types that no longer have concrete classes in PHP
|
|
||||||
*/
|
|
||||||
public static function get_classname_spec($class, $queryDB = true) {
|
|
||||||
// Check cache
|
|
||||||
if(!empty(self::$classname_spec_cache[$class])) return self::$classname_spec_cache[$class];
|
|
||||||
|
|
||||||
// Build known class names
|
|
||||||
$classNames = ClassInfo::subclassesFor($class);
|
|
||||||
|
|
||||||
// Enhance with existing classes in order to prevent legacy details being lost
|
|
||||||
if($queryDB && DB::get_schema()->hasField($class, 'ClassName')) {
|
|
||||||
$existing = DB::query("SELECT DISTINCT \"ClassName\" FROM \"{$class}\"")->column();
|
|
||||||
$classNames = array_unique(array_merge($classNames, $existing));
|
|
||||||
}
|
|
||||||
$spec = "Enum('" . implode(', ', $classNames) . "')";
|
|
||||||
|
|
||||||
// Only cache full information if queried
|
|
||||||
if($queryDB) self::$classname_spec_cache[$class] = $spec;
|
|
||||||
return $spec;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -285,7 +252,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
unset($fixed['ID']);
|
unset($fixed['ID']);
|
||||||
return array_merge(
|
return array_merge(
|
||||||
$fixed,
|
$fixed,
|
||||||
array('ClassName' => self::get_classname_spec($class, $queryDB)),
|
|
||||||
self::custom_database_fields($class)
|
self::custom_database_fields($class)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -318,28 +284,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
// Remove the original fieldname, it's not an actual database column
|
// Remove the original fieldname, it's not an actual database column
|
||||||
unset($fields[$fieldName]);
|
unset($fields[$fieldName]);
|
||||||
|
|
||||||
// Add all composite columns
|
// Add all composite columns, including polymorphic relationships
|
||||||
$compositeFields = singleton($fieldClass)->compositeDatabaseFields();
|
$fieldObj = Object::create_from_string($fieldClass, $fieldName);
|
||||||
if($compositeFields) foreach($compositeFields as $compositeName => $spec) {
|
$fieldObj->setTable($class);
|
||||||
|
$compositeFields = $fieldObj->compositeDatabaseFields();
|
||||||
|
foreach($compositeFields as $compositeName => $spec) {
|
||||||
$fields["{$fieldName}{$compositeName}"] = $spec;
|
$fields["{$fieldName}{$compositeName}"] = $spec;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add has_one relationships
|
// Add has_one relationships
|
||||||
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
||||||
if($hasOne) foreach(array_keys($hasOne) as $field) {
|
if($hasOne) foreach($hasOne as $field => $hasOneClass) {
|
||||||
|
// exclude polymorphic relationships
|
||||||
// Check if this is a polymorphic relation, in which case the relation
|
if($hasOneClass !== 'DataObject') {
|
||||||
// is a composite field
|
|
||||||
if($hasOne[$field] === 'DataObject') {
|
|
||||||
$relationField = DBField::create_field('PolymorphicForeignKey', null, $field);
|
|
||||||
$relationField->setTable($class);
|
|
||||||
if($compositeFields = $relationField->compositeDatabaseFields()) {
|
|
||||||
foreach($compositeFields as $compositeName => $spec) {
|
|
||||||
$fields["{$field}{$compositeName}"] = $spec;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$fields[$field . 'ID'] = 'ForeignKey';
|
$fields[$field . 'ID'] = 'ForeignKey';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,6 +343,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
/**
|
/**
|
||||||
* Returns a list of all the composite if the given db field on the class is a composite field.
|
* 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.
|
* Will check all applicable ancestor classes and aggregate results.
|
||||||
|
*
|
||||||
|
* Includes composite has_one (Polymorphic) fields
|
||||||
|
*
|
||||||
|
* @param string $class Name of class to check
|
||||||
|
* @param bool $aggregated Include fields in entire hierarchy, rather than just on this table
|
||||||
|
* @return array
|
||||||
*/
|
*/
|
||||||
public static function composite_fields($class, $aggregated = true) {
|
public static function composite_fields($class, $aggregated = true) {
|
||||||
if(!isset(DataObject::$_cache_composite_fields[$class])) self::cache_composite_fields($class);
|
if(!isset(DataObject::$_cache_composite_fields[$class])) self::cache_composite_fields($class);
|
||||||
@ -405,6 +369,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
private static function cache_composite_fields($class) {
|
private static function cache_composite_fields($class) {
|
||||||
$compositeFields = array();
|
$compositeFields = array();
|
||||||
|
|
||||||
|
// Check db
|
||||||
$fields = Config::inst()->get($class, 'db', Config::UNINHERITED);
|
$fields = Config::inst()->get($class, 'db', Config::UNINHERITED);
|
||||||
if($fields) foreach($fields as $fieldName => $fieldClass) {
|
if($fields) foreach($fields as $fieldName => $fieldClass) {
|
||||||
if(!is_string($fieldClass)) continue;
|
if(!is_string($fieldClass)) continue;
|
||||||
@ -413,12 +378,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
$bPos = strpos($fieldClass, '(');
|
$bPos = strpos($fieldClass, '(');
|
||||||
if($bPos !== FALSE) $fieldClass = substr($fieldClass, 0, $bPos);
|
if($bPos !== FALSE) $fieldClass = substr($fieldClass, 0, $bPos);
|
||||||
|
|
||||||
// Test to see if it implements CompositeDBField
|
// Test to see if it extends CompositeDBField
|
||||||
if(ClassInfo::classImplements($fieldClass, 'CompositeDBField')) {
|
if(is_subclass_of($fieldClass, 'CompositeDBField')) {
|
||||||
$compositeFields[$fieldName] = $fieldClass;
|
$compositeFields[$fieldName] = $fieldClass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check has_one PolymorphicForeignKey
|
||||||
|
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
||||||
|
if($hasOne) foreach($hasOne as $fieldName => $hasOneClass) {
|
||||||
|
if($hasOneClass === 'DataObject') {
|
||||||
|
$compositeFields[$fieldName] = 'PolymorphicForeignKey';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DataObject::$_cache_composite_fields[$class] = $compositeFields;
|
DataObject::$_cache_composite_fields[$class] = $compositeFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1274,8 +1247,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
$fieldObj = DBField::create_field('Varchar', $fieldValue, $fieldName);
|
$fieldObj = DBField::create_field('Varchar', $fieldValue, $fieldName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure DBField is repopulated and written to the manipulation
|
// Write to manipulation
|
||||||
$fieldObj->setValue($fieldValue, $this->record);
|
|
||||||
$fieldObj->writeToManipulation($manipulation[$class]);
|
$fieldObj->writeToManipulation($manipulation[$class]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1829,15 +1801,20 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
/**
|
/**
|
||||||
* Return data for a specific has_one component.
|
* Return data for a specific has_one component.
|
||||||
* @param string $component
|
* @param string $component
|
||||||
|
* @param string $table Out parameter of the table this has_one field belongs to
|
||||||
* @return string|null
|
* @return string|null
|
||||||
*/
|
*/
|
||||||
public function hasOneComponent($component) {
|
public function hasOneComponent($component, &$table = null) {
|
||||||
$hasOnes = (array)Config::inst()->get($this->class, 'has_one', Config::INHERITED);
|
$classes = ClassInfo::ancestry($this, true);
|
||||||
|
|
||||||
|
foreach(array_reverse($classes) as $class) {
|
||||||
|
$hasOnes = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
||||||
if(isset($hasOnes[$component])) {
|
if(isset($hasOnes[$component])) {
|
||||||
|
$table = $class;
|
||||||
return $hasOnes[$component];
|
return $hasOnes[$component];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated 4.0 Method has been replaced by belongsTo() and belongsToComponent()
|
* @deprecated 4.0 Method has been replaced by belongsTo() and belongsToComponent()
|
||||||
@ -1906,9 +1883,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
* Doesn't include any fields specified by self::$has_one. Use $this->hasOne() to get these fields
|
* Doesn't include any fields specified by self::$has_one. Use $this->hasOne() to get these fields
|
||||||
*
|
*
|
||||||
* @param string $fieldName Limit the output to a specific field name
|
* @param string $fieldName Limit the output to a specific field name
|
||||||
|
* @param string $table Out parameter of the table this db field is set to
|
||||||
* @return array The database fields
|
* @return array The database fields
|
||||||
*/
|
*/
|
||||||
public function db($fieldName = null) {
|
public function db($fieldName = null, &$table = null) {
|
||||||
$classes = ClassInfo::ancestry($this, true);
|
$classes = ClassInfo::ancestry($this, true);
|
||||||
|
|
||||||
// If we're looking for a specific field, we want to hit subclasses first as they may override field types
|
// If we're looking for a specific field, we want to hit subclasses first as they may override field types
|
||||||
@ -1927,6 +1905,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
|
|
||||||
if($fieldName) {
|
if($fieldName) {
|
||||||
if(isset($dbItems[$fieldName])) {
|
if(isset($dbItems[$fieldName])) {
|
||||||
|
$table = $class;
|
||||||
return $dbItems[$fieldName];
|
return $dbItems[$fieldName];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -2405,7 +2384,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
*/
|
*/
|
||||||
public function getField($field) {
|
public function getField($field) {
|
||||||
// If we already have an object in $this->record, then we should just return that
|
// If we already have an object in $this->record, then we should just return that
|
||||||
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];
|
||||||
|
}
|
||||||
|
|
||||||
// Do we have a field that needs to be lazy loaded?
|
// Do we have a field that needs to be lazy loaded?
|
||||||
if(isset($this->record[$field.'_Lazy'])) {
|
if(isset($this->record[$field.'_Lazy'])) {
|
||||||
@ -2413,27 +2394,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
$this->loadLazyFields($tableClass);
|
$this->loadLazyFields($tableClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we need to determine if this is a complex field
|
// In case of complex fields, return the DBField object
|
||||||
if(self::is_composite_field($this->class, $field)) {
|
if(self::is_composite_field($this->class, $field)) {
|
||||||
$helper = $this->castingHelper($field);
|
$helper = $this->db($field);
|
||||||
$fieldObj = Object::create_from_string($helper, $field);
|
$obj = Object::create_from_string($helper, $field);
|
||||||
|
$obj->setValue(null, $this, false);
|
||||||
$compositeFields = $fieldObj->compositeDatabaseFields();
|
$this->record[$field] = $obj;
|
||||||
foreach ($compositeFields as $compositeName => $compositeType) {
|
|
||||||
if(isset($this->record[$field.$compositeName.'_Lazy'])) {
|
|
||||||
$tableClass = $this->record[$field.$compositeName.'_Lazy'];
|
|
||||||
$this->loadLazyFields($tableClass);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// write value only if either the field value exists,
|
|
||||||
// or a valid record has been loaded from the database
|
|
||||||
$value = (isset($this->record[$field])) ? $this->record[$field] : null;
|
|
||||||
if($value || $this->exists()) $fieldObj->setValue($value, $this->record, false);
|
|
||||||
|
|
||||||
$this->record[$field] = $fieldObj;
|
|
||||||
|
|
||||||
return $this->record[$field];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return isset($this->record[$field]) ? $this->record[$field] : null;
|
return isset($this->record[$field]) ? $this->record[$field] : null;
|
||||||
@ -2530,7 +2496,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
* )
|
* )
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
* @param boolean $databaseFieldsOnly Get only database fields that have changed
|
* @param boolean|array $databaseFieldsOnly Filter to determine which fields to return. Set to true
|
||||||
|
* to return all database fields, or an array for an explicit filter. false returns all fields.
|
||||||
* @param int $changeLevel The strictness of what is defined as change. Defaults to strict
|
* @param int $changeLevel The strictness of what is defined as change. Defaults to strict
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
@ -2539,12 +2506,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
|
|
||||||
// Update the changed array with references to changed obj-fields
|
// Update the changed array with references to changed obj-fields
|
||||||
foreach($this->record as $k => $v) {
|
foreach($this->record as $k => $v) {
|
||||||
|
// Prevents CompositeDBFields infinite looping on isChanged
|
||||||
|
if(is_array($databaseFieldsOnly) && !in_array($k, $databaseFieldsOnly)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if(is_object($v) && method_exists($v, 'isChanged') && $v->isChanged()) {
|
if(is_object($v) && method_exists($v, 'isChanged') && $v->isChanged()) {
|
||||||
$this->changed[$k] = self::CHANGE_VALUE;
|
$this->changed[$k] = self::CHANGE_VALUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($databaseFieldsOnly) {
|
if(is_array($databaseFieldsOnly)) {
|
||||||
|
$fields = array_intersect_key((array)$this->changed, array_flip($databaseFieldsOnly));
|
||||||
|
} elseif($databaseFieldsOnly) {
|
||||||
$databaseFields = $this->inheritedDatabaseFields();
|
$databaseFields = $this->inheritedDatabaseFields();
|
||||||
$databaseFields['ID'] = true;
|
$databaseFields['ID'] = true;
|
||||||
$databaseFields['LastEdited'] = true;
|
$databaseFields['LastEdited'] = true;
|
||||||
@ -2584,7 +2557,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function isChanged($fieldName = null, $changeLevel = self::CHANGE_STRICT) {
|
public function isChanged($fieldName = null, $changeLevel = self::CHANGE_STRICT) {
|
||||||
$changed = $this->getChangedFields(false, $changeLevel);
|
$fields = $fieldName ? array($fieldName) : false;
|
||||||
|
$changed = $this->getChangedFields($fields, $changeLevel);
|
||||||
if(!isset($fieldName)) {
|
if(!isset($fieldName)) {
|
||||||
return !empty($changed);
|
return !empty($changed);
|
||||||
}
|
}
|
||||||
@ -2606,16 +2580,24 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
if (substr($fieldName, -2) == 'ID') {
|
if (substr($fieldName, -2) == 'ID') {
|
||||||
unset($this->components[substr($fieldName, 0, -2)]);
|
unset($this->components[substr($fieldName, 0, -2)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we've just lazy-loaded the column, then we need to populate the $original array
|
||||||
|
if(isset($this->record[$fieldName.'_Lazy'])) {
|
||||||
|
$tableClass = $this->record[$fieldName.'_Lazy'];
|
||||||
|
$this->loadLazyFields($tableClass);
|
||||||
|
}
|
||||||
|
|
||||||
// Situation 1: Passing an DBField
|
// Situation 1: Passing an DBField
|
||||||
if($val instanceof DBField) {
|
if($val instanceof DBField) {
|
||||||
$val->Name = $fieldName;
|
$val->setName($fieldName);
|
||||||
|
$val->saveInto($this);
|
||||||
// If we've just lazy-loaded the column, then we need to populate the $original array by
|
|
||||||
// called getField(). Too much overhead? Could this be done by a quicker method? Maybe only
|
|
||||||
// on a call to getChanged()?
|
|
||||||
$this->getField($fieldName);
|
|
||||||
|
|
||||||
|
// Situation 1a: Composite fields should remain bound in case they are
|
||||||
|
// later referenced to update the parent dataobject
|
||||||
|
if($val instanceof CompositeDBField) {
|
||||||
|
$val->bindTo($this);
|
||||||
$this->record[$fieldName] = $val;
|
$this->record[$fieldName] = $val;
|
||||||
|
}
|
||||||
// Situation 2: Passing a literal or non-DBField object
|
// Situation 2: Passing a literal or non-DBField object
|
||||||
} else {
|
} else {
|
||||||
// If this is a proper database field, we shouldn't be getting non-DBField objects
|
// If this is a proper database field, we shouldn't be getting non-DBField objects
|
||||||
@ -2637,11 +2619,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
$this->changed[$fieldName] = self::CHANGE_VALUE;
|
$this->changed[$fieldName] = self::CHANGE_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've just lazy-loaded the column, then we need to populate the $original array by
|
|
||||||
// called getField(). Too much overhead? Could this be done by a quicker method? Maybe only
|
|
||||||
// on a call to getChanged()?
|
|
||||||
$this->getField($fieldName);
|
|
||||||
|
|
||||||
// Value is always saved back when strict check succeeds.
|
// Value is always saved back when strict check succeeds.
|
||||||
$this->record[$fieldName] = $val;
|
$this->record[$fieldName] = $val;
|
||||||
}
|
}
|
||||||
@ -2657,23 +2634,30 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
*
|
*
|
||||||
* @param string $fieldName Name of the field
|
* @param string $fieldName Name of the field
|
||||||
* @param mixed $value New field value
|
* @param mixed $value New field value
|
||||||
* @return DataObject $this
|
* @return $this
|
||||||
*/
|
*/
|
||||||
public function setCastedField($fieldName, $val) {
|
public function setCastedField($fieldName, $value) {
|
||||||
if(!$fieldName) {
|
if(!$fieldName) {
|
||||||
user_error("DataObject::setCastedField: Called without a fieldName", E_USER_ERROR);
|
user_error("DataObject::setCastedField: Called without a fieldName", E_USER_ERROR);
|
||||||
}
|
}
|
||||||
$castingHelper = $this->castingHelper($fieldName);
|
$fieldObj = $this->dbObject($fieldName);
|
||||||
if($castingHelper) {
|
if($fieldObj) {
|
||||||
$fieldObj = Object::create_from_string($castingHelper, $fieldName);
|
$fieldObj->setValue($value);
|
||||||
$fieldObj->setValue($val);
|
|
||||||
$fieldObj->saveInto($this);
|
$fieldObj->saveInto($this);
|
||||||
} else {
|
} else {
|
||||||
$this->$fieldName = $val;
|
$this->$fieldName = $value;
|
||||||
}
|
}
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function castingHelper($field) {
|
||||||
|
// Allows db to act as implicit casting override
|
||||||
|
if($fieldSpec = $this->dbHelper($field)) {
|
||||||
|
return $fieldSpec;
|
||||||
|
}
|
||||||
|
return parent::castingHelper($field);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given field exists in a database column on any of
|
* Returns true if the given field exists in a database column on any of
|
||||||
* the objects tables and optionally look up a dynamic getter with
|
* the objects tables and optionally look up a dynamic getter with
|
||||||
@ -2938,39 +2922,55 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
* @return DBField The field as a DBField object
|
* @return DBField The field as a DBField object
|
||||||
*/
|
*/
|
||||||
public function dbObject($fieldName) {
|
public function dbObject($fieldName) {
|
||||||
// If we have a CompositeDBField object in $this->record, then return that
|
// If we have a DBField object in $this->record, then return that
|
||||||
if(isset($this->record[$fieldName]) && is_object($this->record[$fieldName])) {
|
if(isset($this->record[$fieldName]) && is_object($this->record[$fieldName])) {
|
||||||
return $this->record[$fieldName];
|
return $this->record[$fieldName];
|
||||||
|
}
|
||||||
|
|
||||||
// Special case for ID field
|
// Build and populate new field otherwise
|
||||||
} else if($fieldName == 'ID') {
|
$helper = $this->dbHelper($fieldName, $table);
|
||||||
return new PrimaryKey($fieldName, $this);
|
if($helper) {
|
||||||
|
$obj = Object::create_from_string($helper, $fieldName);
|
||||||
|
$obj->setTable($table);
|
||||||
|
$obj->setValue($this->$fieldName, $this, false);
|
||||||
|
return $obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Special case for ClassName
|
/**
|
||||||
} else if($fieldName == 'ClassName') {
|
* Get helper class spec for the given db field.
|
||||||
$val = get_class($this);
|
*
|
||||||
return DBField::create_field('Varchar', $val, $fieldName);
|
* Note that child fields of composite db fields will not be detectable via this method.
|
||||||
|
* These should be resolved indirectly by referencing 'CompositeField.Child' instead of 'CompositeFieldChild'
|
||||||
} else if(array_key_exists($fieldName, self::$fixed_fields)) {
|
* in your templates
|
||||||
return DBField::create_field(self::$fixed_fields[$fieldName], $this->$fieldName, $fieldName);
|
*
|
||||||
|
* @param string $fieldName
|
||||||
|
* @param string $table Out parameter of the table this has_one field belongs to
|
||||||
|
* @return DBField
|
||||||
|
*/
|
||||||
|
public function dbHelper($fieldName, &$table = null) {
|
||||||
|
// Fixed fields
|
||||||
|
if(array_key_exists($fieldName, self::$fixed_fields)) {
|
||||||
|
$table = $this->baseTable();
|
||||||
|
return self::$fixed_fields[$fieldName];
|
||||||
|
}
|
||||||
|
|
||||||
// General casting information for items in $db
|
// General casting information for items in $db
|
||||||
} else if($helper = $this->db($fieldName)) {
|
if($helper = $this->db($fieldName, $table)) {
|
||||||
$obj = Object::create_from_string($helper, $fieldName);
|
return $helper;
|
||||||
$obj->setValue($this->$fieldName, $this->record, false);
|
}
|
||||||
return $obj;
|
|
||||||
|
|
||||||
// Special case for has_one relationships
|
// Special case for has_one relationships
|
||||||
} else if(preg_match('/ID$/', $fieldName) && $this->hasOneComponent(substr($fieldName,0,-2))) {
|
if(preg_match('/.+ID$/', $fieldName) && $this->hasOneComponent(substr($fieldName,0,-2), $table)) {
|
||||||
$val = $this->$fieldName;
|
return 'ForeignKey';
|
||||||
return DBField::create_field('ForeignKey', $val, $fieldName, $this);
|
}
|
||||||
|
|
||||||
// has_one for polymorphic relations do not end in ID
|
// has_one for polymorphic relations do not end in ID
|
||||||
} else if(($type = $this->hasOneComponent($fieldName)) && ($type === 'DataObject')) {
|
if(($type = $this->hasOneComponent($fieldName, $table)) && ($type === 'DataObject')) {
|
||||||
$val = $this->$fieldName();
|
return 'PolymorphicForeignKey';
|
||||||
return DBField::create_field('PolymorphicForeignKey', $val, $fieldName, $this);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3736,11 +3736,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
|||||||
/**
|
/**
|
||||||
* Use a casting object for a field. This is a map from
|
* Use a casting object for a field. This is a map from
|
||||||
* field name to class name of the casting object.
|
* field name to class name of the casting object.
|
||||||
|
*
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
private static $casting = array(
|
private static $casting = array(
|
||||||
"ID" => 'Int',
|
"ID" => "PrimaryKey",
|
||||||
"ClassName" => 'Varchar',
|
"ClassName" => "DBClassName",
|
||||||
"LastEdited" => "SS_Datetime",
|
"LastEdited" => "SS_Datetime",
|
||||||
"Created" => "SS_Datetime",
|
"Created" => "SS_Datetime",
|
||||||
"Title" => 'Text',
|
"Title" => 'Text',
|
||||||
|
@ -462,6 +462,7 @@ class DataQuery {
|
|||||||
if($compositeFields) foreach($compositeFields as $k => $v) {
|
if($compositeFields) foreach($compositeFields as $k => $v) {
|
||||||
if((is_null($columns) || in_array($k, $columns)) && $v) {
|
if((is_null($columns) || in_array($k, $columns)) && $v) {
|
||||||
$dbO = Object::create_from_string($v, $k);
|
$dbO = Object::create_from_string($v, $k);
|
||||||
|
$dbO->setTable($tableClass);
|
||||||
$dbO->addToQuery($query);
|
$dbO->addToQuery($query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,18 +122,17 @@ class ManyManyList extends RelationList {
|
|||||||
// convert joined extra fields into their composite field types.
|
// convert joined extra fields into their composite field types.
|
||||||
$value = array();
|
$value = array();
|
||||||
|
|
||||||
foreach($composed as $subField => $subSpec) {
|
foreach($composed as $subField) {
|
||||||
if(isset($row[$fieldName . $subSpec])) {
|
if(isset($row[$fieldName . $subField])) {
|
||||||
$value[$subSpec] = $row[$fieldName . $subSpec];
|
$value[$subField] = $row[$fieldName . $subField];
|
||||||
|
|
||||||
// don't duplicate data in the record
|
// don't duplicate data in the record
|
||||||
unset($row[$fieldName . $subSpec]);
|
unset($row[$fieldName . $subField]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$obj = Object::create_from_string($this->extraFields[$fieldName], $fieldName);
|
$obj = Object::create_from_string($this->extraFields[$fieldName], $fieldName);
|
||||||
$obj->setValue($value, null, false);
|
$obj->setValue($value, null, false);
|
||||||
|
|
||||||
$add[$fieldName] = $obj;
|
$add[$fieldName] = $obj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,96 +7,18 @@
|
|||||||
*
|
*
|
||||||
* Example with a combined street name and number:
|
* Example with a combined street name and number:
|
||||||
* <code>
|
* <code>
|
||||||
* class Street extends DBField implements CompositeDBField {
|
* class Street extends CompositeDBField {
|
||||||
* protected $streetNumber;
|
* private static $composite_db = return array(
|
||||||
* protected $streetName;
|
|
||||||
* protected $isChanged = false;
|
|
||||||
* static $composite_db = return array(
|
|
||||||
* "Number" => "Int",
|
* "Number" => "Int",
|
||||||
* "Name" => "Text"
|
* "Name" => "Text"
|
||||||
* );
|
* );
|
||||||
*
|
|
||||||
* function requireField() {
|
|
||||||
* DB::requireField($this->tableName, "{$this->name}Number", 'Int');
|
|
||||||
* DB::requireField($this->tableName, "{$this->name}Name", 'Text');
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function writeToManipulation(&$manipulation) {
|
|
||||||
* if($this->getStreetName()) {
|
|
||||||
* $manipulation['fields']["{$this->name}Name"] = $this->prepValueForDB($this->getStreetName());
|
|
||||||
* } else {
|
|
||||||
* $manipulation['fields']["{$this->name}Name"] = DBField::create_field('Varchar', $this->getStreetName())
|
|
||||||
* ->nullValue();
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* if($this->getStreetNumber()) {
|
|
||||||
* $manipulation['fields']["{$this->name}Number"] = $this->prepValueForDB($this->getStreetNumber());
|
|
||||||
* } else {
|
|
||||||
* $manipulation['fields']["{$this->name}Number"] = DBField::create_field('Int', $this->getStreetNumber())
|
|
||||||
* ->nullValue();
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function addToQuery(&$query) {
|
|
||||||
* parent::addToQuery($query);
|
|
||||||
* $query->setSelect("{$this->name}Number");
|
|
||||||
* $query->setSelect("{$this->name}Name");
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function setValue($value, $record = null, $markChanged=true) {
|
|
||||||
* if ($value instanceof Street && $value->exists()) {
|
|
||||||
* $this->setStreetName($value->getStreetName(), $markChanged);
|
|
||||||
* $this->setStreetNumber($value->getStreetNumber(), $markChanged);
|
|
||||||
* if($markChanged) $this->isChanged = true;
|
|
||||||
* } else if($record && isset($record[$this->name . 'Name']) && isset($record[$this->name . 'Number'])) {
|
|
||||||
* if($record[$this->name . 'Name'] && $record[$this->name . 'Number']) {
|
|
||||||
* $this->setStreetName($record[$this->name . 'Name'], $markChanged);
|
|
||||||
* $this->setStreetNumber($record[$this->name . 'Number'], $markChanged);
|
|
||||||
* }
|
|
||||||
* if($markChanged) $this->isChanged = true;
|
|
||||||
* } else if (is_array($value)) {
|
|
||||||
* if (array_key_exists('Name', $value)) {
|
|
||||||
* $this->setStreetName($value['Name'], $markChanged);
|
|
||||||
* }
|
|
||||||
* if (array_key_exists('Number', $value)) {
|
|
||||||
* $this->setStreetNumber($value['Number'], $markChanged);
|
|
||||||
* }
|
|
||||||
* if($markChanged) $this->isChanged = true;
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function setStreetNumber($val, $markChanged=true) {
|
|
||||||
* $this->streetNumber = $val;
|
|
||||||
* if($markChanged) $this->isChanged = true;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function setStreetName($val, $markChanged=true) {
|
|
||||||
* $this->streetName = $val;
|
|
||||||
* if($markChanged) $this->isChanged = true;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function getStreetNumber() {
|
|
||||||
* return $this->streetNumber;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function getStreetName() {
|
|
||||||
* return $this->streetName;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function isChanged() {
|
|
||||||
* return $this->isChanged;
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* function exists() {
|
|
||||||
* return ($this->getStreetName() || $this->getStreetNumber());
|
|
||||||
* }
|
|
||||||
* }
|
* }
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
* @package framework
|
* @package framework
|
||||||
* @subpackage model
|
* @subpackage model
|
||||||
*/
|
*/
|
||||||
interface CompositeDBField {
|
abstract class CompositeDBField extends DBField {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Similiar to {@link DataObject::$db},
|
* Similiar to {@link DataObject::$db},
|
||||||
@ -104,48 +26,30 @@ interface CompositeDBField {
|
|||||||
* Don't include the fields "main name",
|
* Don't include the fields "main name",
|
||||||
* it will be prefixed in {@link requireField()}.
|
* it will be prefixed in {@link requireField()}.
|
||||||
*
|
*
|
||||||
* @var array $composite_db
|
* @config
|
||||||
|
* @var array
|
||||||
*/
|
*/
|
||||||
//static $composite_db;
|
private static $composite_db = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the value of this field in various formats.
|
* Either the parent dataobject link, or a record of saved values for each field
|
||||||
* Used by {@link DataObject->getField()}, {@link DataObject->setCastedField()}
|
|
||||||
* {@link DataObject->dbObject()} and {@link DataObject->write()}.
|
|
||||||
*
|
*
|
||||||
* As this method is used both for initializing the field after construction,
|
* @var array|DataObject
|
||||||
* and actually changing its values, it needs a {@link $markChanged}
|
|
||||||
* parameter.
|
|
||||||
*
|
|
||||||
* @param DBField|array $value
|
|
||||||
* @param DataObject|array $record An array or object that this field is part of
|
|
||||||
* @param boolean $markChanged Indicate wether this field should be marked changed.
|
|
||||||
* Set to FALSE if you are initializing this field after construction, rather
|
|
||||||
* than setting a new value.
|
|
||||||
*/
|
*/
|
||||||
public function setValue($value, $record = null, $markChanged = true);
|
protected $record = array();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used in constructing the database schema.
|
* Write all nested fields into a manipulation
|
||||||
* Add any custom properties defined in {@link $composite_db}.
|
|
||||||
* Should make one or more calls to {@link DB::requireField()}.
|
|
||||||
*/
|
|
||||||
//abstract public function requireField();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the custom internal values to an INSERT or UPDATE
|
|
||||||
* request passed through the ORM with {@link DataObject->write()}.
|
|
||||||
* Fields are added in $manipulation['fields']. Please ensure
|
|
||||||
* these fields are escaped for database insertion, as no
|
|
||||||
* further processing happens before running the query.
|
|
||||||
* Use {@link DBField->prepValueForDB()}.
|
|
||||||
* Ensure to write NULL or empty values as well to allow
|
|
||||||
* unsetting a previously set field. Use {@link DBField->nullValue()}
|
|
||||||
* for the appropriate type.
|
|
||||||
*
|
*
|
||||||
* @param array $manipulation
|
* @param array $manipulation
|
||||||
*/
|
*/
|
||||||
public function writeToManipulation(&$manipulation);
|
public function writeToManipulation(&$manipulation) {
|
||||||
|
foreach($this->compositeDatabaseFields() as $field => $spec) {
|
||||||
|
// Write sub-manipulation
|
||||||
|
$fieldObject = $this->dbObject($field);
|
||||||
|
$fieldObject->writeToManipulation($manipulation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add all columns which are defined through {@link requireField()}
|
* Add all columns which are defined through {@link requireField()}
|
||||||
@ -155,30 +59,202 @@ interface CompositeDBField {
|
|||||||
*
|
*
|
||||||
* @param SQLSelect $query
|
* @param SQLSelect $query
|
||||||
*/
|
*/
|
||||||
public function addToQuery(&$query);
|
public function addToQuery(&$query) {
|
||||||
|
parent::addToQuery($query);
|
||||||
|
|
||||||
|
foreach($this->compositeDatabaseFields() as $field => $spec) {
|
||||||
|
$table = $this->getTable();
|
||||||
|
$key = $this->getName() . $field;
|
||||||
|
if($table) {
|
||||||
|
$query->selectField("\"{$table}\".\"{$key}\"");
|
||||||
|
} else {
|
||||||
|
$query->selectField("\"{$key}\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return array in the format of {@link $composite_db}.
|
* Return array in the format of {@link $composite_db}.
|
||||||
* Used by {@link DataObject->hasOwnDatabaseField()}.
|
* Used by {@link DataObject->hasOwnDatabaseField()}.
|
||||||
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
public function compositeDatabaseFields();
|
public function compositeDatabaseFields() {
|
||||||
|
return $this->config()->composite_db;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function isChanged() {
|
||||||
|
// When unbound, use the local changed flag
|
||||||
|
if(! ($this->record instanceof DataObject) ) {
|
||||||
|
return $this->isChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defer to parent record
|
||||||
|
foreach($this->compositeDatabaseFields() as $field => $spec) {
|
||||||
|
$key = $this->getName() . $field;
|
||||||
|
if($this->record->isChanged($key)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if the field has been changed since its initialization.
|
* Composite field defaults to exists only if all fields have values
|
||||||
* Most likely relies on an internal flag thats changed when calling
|
|
||||||
* {@link setValue()} or any other custom setters on the object.
|
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function isChanged();
|
public function exists() {
|
||||||
|
// By default all fields
|
||||||
|
foreach($this->compositeDatabaseFields() as $field => $spec) {
|
||||||
|
$fieldObject = $this->dbObject($field);
|
||||||
|
if(!$fieldObject->exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requireField() {
|
||||||
|
foreach($this->compositeDatabaseFields() as $field => $spec){
|
||||||
|
$key = $this->getName() . $field;
|
||||||
|
DB::requireField($this->tableName, $key, $spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines if any of the properties in this field have a value,
|
* Assign the given value.
|
||||||
* meaning at least one of them is not NULL.
|
* If $record is assigned to a dataobject, this field becomes a loose wrapper over
|
||||||
|
* the records on that object instead.
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @param type $value
|
||||||
|
* @param DataObject $record
|
||||||
|
* @param type $markChanged
|
||||||
|
* @return type
|
||||||
*/
|
*/
|
||||||
public function exists();
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
|
$this->isChanged = $markChanged;
|
||||||
|
|
||||||
|
// When given a dataobject, bind this field to that
|
||||||
|
if($record instanceof DataObject) {
|
||||||
|
$this->bindTo($record);
|
||||||
|
$record = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($this->compositeDatabaseFields() as $field => $spec) {
|
||||||
|
// Check value
|
||||||
|
if($value instanceof CompositeDBField) {
|
||||||
|
// Check if saving from another composite field
|
||||||
|
$this->setField($field, $value->getField($field));
|
||||||
|
|
||||||
|
} elseif(isset($value[$field])) {
|
||||||
|
// Check if saving from an array
|
||||||
|
$this->setField($field, $value[$field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load from $record
|
||||||
|
$key = $this->getName() . $field;
|
||||||
|
if(isset($record[$key])) {
|
||||||
|
$this->setField($field, $record[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind this field to the dataobject, and set the underlying table to that of the owner
|
||||||
|
*
|
||||||
|
* @param DataObject $dataObject
|
||||||
|
*/
|
||||||
|
public function bindTo($dataObject) {
|
||||||
|
$this->record = $dataObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function saveInto($dataObject) {
|
||||||
|
foreach($this->compositeDatabaseFields() as $field => $spec) {
|
||||||
|
// Save into record
|
||||||
|
$key = $this->getName() . $field;
|
||||||
|
$dataObject->setField($key, $this->getField($field));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get value of a single composite field
|
||||||
|
*
|
||||||
|
* @param string $field
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getField($field) {
|
||||||
|
// Skip invalid fields
|
||||||
|
$fields = $this->compositeDatabaseFields();
|
||||||
|
if(!isset($fields[$field])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check bound object
|
||||||
|
if($this->record instanceof DataObject) {
|
||||||
|
$key = $this->getName().$field;
|
||||||
|
return $this->record->getField($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check local record
|
||||||
|
if(isset($this->record[$field])) {
|
||||||
|
return $this->record[$field];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasField($field) {
|
||||||
|
$fields = $this->compositeDatabaseFields();
|
||||||
|
return isset($fields[$field]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set value of a single composite field
|
||||||
|
*
|
||||||
|
* @param string $field
|
||||||
|
* @param mixed $value
|
||||||
|
* @param bool $markChanged
|
||||||
|
*/
|
||||||
|
public function setField($field, $value, $markChanged = true) {
|
||||||
|
// Skip non-db fields
|
||||||
|
if(!$this->hasField($field)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set changed
|
||||||
|
if($markChanged) {
|
||||||
|
$this->isChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set bound object
|
||||||
|
if($this->record instanceof DataObject) {
|
||||||
|
$key = $this->getName() . $field;
|
||||||
|
return $this->record->setField($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set local record
|
||||||
|
$this->record[$field] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a db object for the named field
|
||||||
|
*
|
||||||
|
* @param string $field Field name
|
||||||
|
* @return DBField|null
|
||||||
|
*/
|
||||||
|
public function dbObject($field) {
|
||||||
|
$fields = $this->compositeDatabaseFields();
|
||||||
|
if(!isset($fields[$field])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build nested field
|
||||||
|
$key = $this->getName() . $field;
|
||||||
|
$spec = $fields[$field];
|
||||||
|
$fieldObject = Object::create_from_string($spec, $key);
|
||||||
|
$fieldObject->setValue($this->getField($field), null, false);
|
||||||
|
return $fieldObject;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ class Currency extends Decimal {
|
|||||||
else return $val;
|
else return $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setValue($value, $record = null) {
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
$matches = null;
|
$matches = null;
|
||||||
if(is_numeric($value)) {
|
if(is_numeric($value)) {
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
|
202
model/fieldtypes/DBClassName.php
Normal file
202
model/fieldtypes/DBClassName.php
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a classname selector, which respects obsolete clasess.
|
||||||
|
*
|
||||||
|
* @package framework
|
||||||
|
* @subpackage model
|
||||||
|
*/
|
||||||
|
class DBClassName extends Enum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base classname of class to enumerate.
|
||||||
|
* If 'DataObject' then all classes are included.
|
||||||
|
* If empty, then the baseClass of the parent object will be used
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $baseClass = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parent object
|
||||||
|
*
|
||||||
|
* @var DataObject|null
|
||||||
|
*/
|
||||||
|
protected $record = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Classname spec cache for obsolete classes. The top level keys are the table, each of which contains
|
||||||
|
* nested arrays with keys mapped to field names. The values of the lowest level array are the classnames
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static $classname_cache = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all cached classname specs. It's necessary to clear all cached subclassed names
|
||||||
|
* for any classes if a new class manifest is generated.
|
||||||
|
*/
|
||||||
|
public static function clear_classname_cache() {
|
||||||
|
self::$classname_cache = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new DBClassName field
|
||||||
|
*
|
||||||
|
* @param string $name Name of field
|
||||||
|
* @param string|null $baseClass Optional base class to limit selections
|
||||||
|
*/
|
||||||
|
public function __construct($name = null, $baseClass = null) {
|
||||||
|
$this->setBaseClass($baseClass);
|
||||||
|
parent::__construct($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function requireField() {
|
||||||
|
$parts = array(
|
||||||
|
'datatype' => 'enum',
|
||||||
|
'enums' => $this->getEnumObsolete(),
|
||||||
|
'character set' => 'utf8',
|
||||||
|
'collate' => 'utf8_general_ci',
|
||||||
|
'default' => $this->getDefault(),
|
||||||
|
'table' => $this->getTable(),
|
||||||
|
'arrayValue' => $this->arrayValue
|
||||||
|
);
|
||||||
|
|
||||||
|
$values = array(
|
||||||
|
'type' => 'enum',
|
||||||
|
'parts' => $parts
|
||||||
|
);
|
||||||
|
|
||||||
|
DB::require_field($this->getTable(), $this->getName(), $values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the base dataclass for the list of subclasses
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getBaseClass() {
|
||||||
|
// Use explicit base class
|
||||||
|
if($this->baseClass) {
|
||||||
|
return $this->baseClass;
|
||||||
|
}
|
||||||
|
// Default to the basename of the record
|
||||||
|
if($this->record) {
|
||||||
|
return ClassInfo::baseDataClass($this->record);
|
||||||
|
}
|
||||||
|
// During dev/build only the table is assigned
|
||||||
|
$tableClass = $this->getClassNameFromTable($this->getTable());
|
||||||
|
if($tableClass) {
|
||||||
|
return $tableClass;
|
||||||
|
}
|
||||||
|
// Fallback to global default
|
||||||
|
return 'DataObject';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign the base class
|
||||||
|
*
|
||||||
|
* @param string $baseClass
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setBaseClass($baseClass) {
|
||||||
|
$this->baseClass = $baseClass;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a table name, find the base data class
|
||||||
|
*
|
||||||
|
* @param string $table
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
protected function getClassNameFromTable($table) {
|
||||||
|
if(empty($table)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$class = ClassInfo::baseDataClass($table);
|
||||||
|
if($class) {
|
||||||
|
return $class;
|
||||||
|
}
|
||||||
|
// If there is no class for this table, strip table modifiers (_Live / _versions) off the end
|
||||||
|
if(preg_match('/^(?<class>.+)(_[^_]+)$/i', $this->getTable(), $matches)) {
|
||||||
|
return $this->getClassNameFromTable($matches['class']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of classnames that should be selectable
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getEnum() {
|
||||||
|
$classNames = ClassInfo::subclassesFor($this->getBaseClass());
|
||||||
|
unset($classNames['DataObject']);
|
||||||
|
return $classNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of classnames, including obsolete classes.
|
||||||
|
*
|
||||||
|
* If table or name are not set, or if it is not a valid field on the given table,
|
||||||
|
* then only known classnames are returned.
|
||||||
|
*
|
||||||
|
* Values cached in this method can be cleared via `DBClassName::clear_classname_cache();`
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getEnumObsolete() {
|
||||||
|
// Without a table or field specified, we can only retrieve known classes
|
||||||
|
$table = $this->getTable();
|
||||||
|
$name = $this->getName();
|
||||||
|
if(empty($table) || empty($name)) {
|
||||||
|
return $this->getEnum();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the table level cache exists
|
||||||
|
if(empty(self::$classname_cache[$table])) {
|
||||||
|
self::$classname_cache[$table] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check existing cache
|
||||||
|
if(!empty(self::$classname_cache[$table][$name])) {
|
||||||
|
return self::$classname_cache[$table][$name];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all class names
|
||||||
|
$classNames = $this->getEnum();
|
||||||
|
if(DB::get_schema()->hasField($table, $name)) {
|
||||||
|
$existing = DB::query("SELECT DISTINCT \"{$name}\" FROM \"{$table}\"")->column();
|
||||||
|
$classNames = array_unique(array_merge($classNames, $existing));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache and return
|
||||||
|
self::$classname_cache[$table][$name] = $classNames;
|
||||||
|
return $classNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
|
parent::setValue($value, $record, $markChanged);
|
||||||
|
|
||||||
|
if($record instanceof DataObject) {
|
||||||
|
$this->record = $record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDefault() {
|
||||||
|
// Check for assigned default
|
||||||
|
$default = parent::getDefault();
|
||||||
|
if($default) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to first option
|
||||||
|
$enum = $this->getEnum();
|
||||||
|
return reset($enum);
|
||||||
|
}
|
||||||
|
}
|
@ -131,14 +131,21 @@ abstract class DBField extends ViewableData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the value on the field.
|
* Set the value of this field in various formats.
|
||||||
|
* Used by {@link DataObject->getField()}, {@link DataObject->setCastedField()}
|
||||||
|
* {@link DataObject->dbObject()} and {@link DataObject->write()}.
|
||||||
*
|
*
|
||||||
* Optionally takes the whole record as an argument, to pick other values.
|
* As this method is used both for initializing the field after construction,
|
||||||
|
* and actually changing its values, it needs a {@link $markChanged}
|
||||||
|
* parameter.
|
||||||
*
|
*
|
||||||
* @param mixed $value
|
* @param mixed $value
|
||||||
* @param array $record
|
* @param DataObject|array $record An array or object that this field is part of
|
||||||
|
* @param boolean $markChanged Indicate wether this field should be marked changed.
|
||||||
|
* Set to FALSE if you are initializing this field after construction, rather
|
||||||
|
* than setting a new value.
|
||||||
*/
|
*/
|
||||||
public function setValue($value, $record = null) {
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
$this->value = $value;
|
$this->value = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,8 +214,24 @@ abstract class DBField extends ViewableData {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign this DBField to a table
|
||||||
|
*
|
||||||
|
* @param string $tableName
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
public function setTable($tableName) {
|
public function setTable($tableName) {
|
||||||
$this->tableName = $tableName;
|
$this->tableName = $tableName;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the table this field belongs to, if assigned
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getTable() {
|
||||||
|
return $this->tableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
class Date extends DBField {
|
class Date extends DBField {
|
||||||
|
|
||||||
public function setValue($value, $record = null) {
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
if($value === false || $value === null || (is_string($value) && !strlen($value))) {
|
if($value === false || $value === null || (is_string($value) && !strlen($value))) {
|
||||||
// don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be
|
// don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be
|
||||||
// saved as NULL in database
|
// saved as NULL in database
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
*/
|
*/
|
||||||
class SS_Datetime extends Date implements TemplateGlobalProvider {
|
class SS_Datetime extends Date implements TemplateGlobalProvider {
|
||||||
|
|
||||||
public function setValue($value, $record = null) {
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
if($value === false || $value === null || (is_string($value) && !strlen($value))) {
|
if($value === false || $value === null || (is_string($value) && !strlen($value))) {
|
||||||
// don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be
|
// don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be
|
||||||
// saved as NULL in database
|
// saved as NULL in database
|
||||||
|
@ -9,7 +9,19 @@
|
|||||||
*/
|
*/
|
||||||
class Enum extends StringField {
|
class Enum extends StringField {
|
||||||
|
|
||||||
protected $enum, $default;
|
/**
|
||||||
|
* List of enum values
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $enum = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default value
|
||||||
|
*
|
||||||
|
* @var string|null
|
||||||
|
*/
|
||||||
|
protected $default = null;
|
||||||
|
|
||||||
private static $default_search_filter_class = 'ExactMatchFilter';
|
private static $default_search_filter_class = 'ExactMatchFilter';
|
||||||
|
|
||||||
@ -37,16 +49,12 @@ class Enum extends StringField {
|
|||||||
*/
|
*/
|
||||||
public function __construct($name = null, $enum = NULL, $default = NULL) {
|
public function __construct($name = null, $enum = NULL, $default = NULL) {
|
||||||
if($enum) {
|
if($enum) {
|
||||||
if(!is_array($enum)) {
|
$this->setEnum($enum);
|
||||||
$enum = preg_split("/ *, */", trim($enum));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->enum = $enum;
|
|
||||||
|
|
||||||
// If there's a default, then
|
// If there's a default, then
|
||||||
if($default) {
|
if($default) {
|
||||||
if(in_array($default, $enum)) {
|
if(in_array($default, $this->getEnum())) {
|
||||||
$this->default = $default;
|
$this->setDefault($default);
|
||||||
} else {
|
} else {
|
||||||
user_error("Enum::__construct() The default value '$default' does not match any item in the"
|
user_error("Enum::__construct() The default value '$default' does not match any item in the"
|
||||||
. " enumeration", E_USER_ERROR);
|
. " enumeration", E_USER_ERROR);
|
||||||
@ -54,7 +62,8 @@ class Enum extends StringField {
|
|||||||
|
|
||||||
// By default, set the default value to the first item
|
// By default, set the default value to the first item
|
||||||
} else {
|
} else {
|
||||||
$this->default = reset($enum);
|
$enum = $this->getEnum();
|
||||||
|
$this->setDefault(reset($enum));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,11 +76,11 @@ class Enum extends StringField {
|
|||||||
public function requireField() {
|
public function requireField() {
|
||||||
$parts = array(
|
$parts = array(
|
||||||
'datatype' => 'enum',
|
'datatype' => 'enum',
|
||||||
'enums' => $this->enum,
|
'enums' => $this->getEnum(),
|
||||||
'character set' => 'utf8',
|
'character set' => 'utf8',
|
||||||
'collate' => 'utf8_general_ci',
|
'collate' => 'utf8_general_ci',
|
||||||
'default' => $this->default,
|
'default' => $this->getDefault(),
|
||||||
'table' => $this->tableName,
|
'table' => $this->getTable(),
|
||||||
'arrayValue' => $this->arrayValue
|
'arrayValue' => $this->arrayValue
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -80,7 +89,7 @@ class Enum extends StringField {
|
|||||||
'parts' => $parts
|
'parts' => $parts
|
||||||
);
|
);
|
||||||
|
|
||||||
DB::require_field($this->tableName, $this->name, $values);
|
DB::require_field($this->getTable(), $this->getName(), $values);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,8 +100,12 @@ class Enum extends StringField {
|
|||||||
public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $form = null,
|
public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $form = null,
|
||||||
$emptyString = null) {
|
$emptyString = null) {
|
||||||
|
|
||||||
if(!$title) $title = $this->name;
|
if(!$title) {
|
||||||
if(!$name) $name = $this->name;
|
$title = $this->getName();
|
||||||
|
}
|
||||||
|
if(!$name) {
|
||||||
|
$name = $this->getName();
|
||||||
|
}
|
||||||
|
|
||||||
$field = new DropdownField($name, $title, $this->enumValues(false), $value, $form);
|
$field = new DropdownField($name, $title, $this->enumValues(false), $value, $form);
|
||||||
if($hasEmpty) {
|
if($hasEmpty) {
|
||||||
@ -129,7 +142,50 @@ class Enum extends StringField {
|
|||||||
*/
|
*/
|
||||||
public function enumValues($hasEmpty = false) {
|
public function enumValues($hasEmpty = false) {
|
||||||
return ($hasEmpty)
|
return ($hasEmpty)
|
||||||
? array_merge(array('' => ''), ArrayLib::valuekey($this->enum))
|
? array_merge(array('' => ''), ArrayLib::valuekey($this->getEnum()))
|
||||||
: ArrayLib::valuekey($this->enum);
|
: ArrayLib::valuekey($this->getEnum());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get list of enum values
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getEnum() {
|
||||||
|
return $this->enum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set enum options
|
||||||
|
*
|
||||||
|
* @param string|array $enum
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setEnum($enum) {
|
||||||
|
if(!is_array($enum)) {
|
||||||
|
$enum = preg_split("/ *, */", trim($enum));
|
||||||
|
}
|
||||||
|
$this->enum = $enum;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get default vwalue
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function getDefault() {
|
||||||
|
return $this->default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set default value
|
||||||
|
*
|
||||||
|
* @param string $default
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function setDefault($default) {
|
||||||
|
$this->default = $default;
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,9 @@ class ForeignKey extends Int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function scaffoldFormField($title = null, $params = null) {
|
public function scaffoldFormField($title = null, $params = null) {
|
||||||
|
if(empty($this->object)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$relationName = substr($this->name,0,-2);
|
$relationName = substr($this->name,0,-2);
|
||||||
$hasOneClass = $this->object->hasOneComponent($relationName);
|
$hasOneClass = $this->object->hasOneComponent($relationName);
|
||||||
|
|
||||||
@ -51,6 +54,13 @@ class ForeignKey extends Int {
|
|||||||
|
|
||||||
return $field;
|
return $field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
|
if($record instanceof DataObject) {
|
||||||
|
$this->object = $record;
|
||||||
|
}
|
||||||
|
parent::setValue($value, $record, $markChanged);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,22 +22,7 @@ require_once 'Zend/Currency.php';
|
|||||||
* @package framework
|
* @package framework
|
||||||
* @subpackage model
|
* @subpackage model
|
||||||
*/
|
*/
|
||||||
class Money extends DBField implements CompositeDBField {
|
class Money extends CompositeDBField {
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string $getCurrency()
|
|
||||||
*/
|
|
||||||
protected $currency;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var float $currencyAmount
|
|
||||||
*/
|
|
||||||
protected $amount;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var boolean $isChanged
|
|
||||||
*/
|
|
||||||
protected $isChanged = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string $locale
|
* @var string $locale
|
||||||
@ -69,77 +54,6 @@ class Money extends DBField implements CompositeDBField {
|
|||||||
parent::__construct($name);
|
parent::__construct($name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function compositeDatabaseFields() {
|
|
||||||
return self::$composite_db;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function requireField() {
|
|
||||||
$fields = $this->compositeDatabaseFields();
|
|
||||||
if($fields) foreach($fields as $name => $type){
|
|
||||||
DB::require_field($this->tableName, $this->name.$name, $type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function writeToManipulation(&$manipulation) {
|
|
||||||
if($this->getCurrency()) {
|
|
||||||
$manipulation['fields'][$this->name.'Currency'] = $this->prepValueForDB($this->getCurrency());
|
|
||||||
} else {
|
|
||||||
$manipulation['fields'][$this->name.'Currency']
|
|
||||||
= DBField::create_field('Varchar', $this->getCurrency())->nullValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
if($this->getAmount()) {
|
|
||||||
$manipulation['fields'][$this->name.'Amount'] = $this->getAmount();
|
|
||||||
} else {
|
|
||||||
$manipulation['fields'][$this->name.'Amount']
|
|
||||||
= DBField::create_field('Decimal', $this->getAmount())->nullValue();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addToQuery(&$query) {
|
|
||||||
parent::addToQuery($query);
|
|
||||||
$query->selectField(sprintf('"%sAmount"', $this->name));
|
|
||||||
$query->selectField(sprintf('"%sCurrency"', $this->name));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setValue($value, $record = null, $markChanged = true) {
|
|
||||||
// Convert an object to an array
|
|
||||||
if($record && $record instanceof DataObject) {
|
|
||||||
$record = $record->getQueriedDatabaseFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
// @todo Allow resetting value to NULL through Money $value field
|
|
||||||
if ($value instanceof Money && $value->exists()) {
|
|
||||||
$this->setCurrency($value->getCurrency(), $markChanged);
|
|
||||||
$this->setAmount($value->getAmount(), $markChanged);
|
|
||||||
if($markChanged) $this->isChanged = true;
|
|
||||||
} else if($record && isset($record[$this->name . 'Amount'])) {
|
|
||||||
if($record[$this->name . 'Amount']) {
|
|
||||||
if(!empty($record[$this->name . 'Currency'])) {
|
|
||||||
$this->setCurrency($record[$this->name . 'Currency'], $markChanged);
|
|
||||||
} else if($currency = (string)$this->config()->default_currency) {
|
|
||||||
$this->setCurrency($currency, $markChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setAmount($record[$this->name . 'Amount'], $markChanged);
|
|
||||||
} else {
|
|
||||||
$this->value = $this->nullValue();
|
|
||||||
}
|
|
||||||
if($markChanged) $this->isChanged = true;
|
|
||||||
} else if (is_array($value)) {
|
|
||||||
if (array_key_exists('Currency', $value)) {
|
|
||||||
$this->setCurrency($value['Currency'], $markChanged);
|
|
||||||
}
|
|
||||||
if (array_key_exists('Amount', $value)) {
|
|
||||||
$this->setAmount($value['Amount'], $markChanged);
|
|
||||||
}
|
|
||||||
if($markChanged) $this->isChanged = true;
|
|
||||||
} else {
|
|
||||||
// @todo Allow to reset a money value by passing in NULL
|
|
||||||
//user_error('Invalid value in Money->setValue()', E_USER_ERROR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@ -173,32 +87,28 @@ class Money extends DBField implements CompositeDBField {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getCurrency() {
|
public function getCurrency() {
|
||||||
return $this->currency;
|
return $this->getField('Currency');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string
|
* @param string
|
||||||
*/
|
*/
|
||||||
public function setCurrency($currency, $markChanged = true) {
|
public function setCurrency($currency, $markChanged = true) {
|
||||||
$this->currency = $currency;
|
$this->setField('Currency', $currency, $markChanged);
|
||||||
if($markChanged) $this->isChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @todo Return casted Float DBField?
|
|
||||||
*
|
|
||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
public function getAmount() {
|
public function getAmount() {
|
||||||
return $this->amount;
|
return $this->getField('Amount');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param float $amount
|
* @param float $amount
|
||||||
*/
|
*/
|
||||||
public function setAmount($amount, $markChanged = true) {
|
public function setAmount($amount, $markChanged = true) {
|
||||||
$this->amount = (float)$amount;
|
$this->setField('Amount', (float)$amount, $markChanged);
|
||||||
if($markChanged) $this->isChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -216,10 +126,6 @@ class Money extends DBField implements CompositeDBField {
|
|||||||
return (!empty($a) && is_numeric($a));
|
return (!empty($a) && is_numeric($a));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isChanged() {
|
|
||||||
return $this->isChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $locale
|
* @param string $locale
|
||||||
*/
|
*/
|
||||||
@ -259,7 +165,7 @@ class Money extends DBField implements CompositeDBField {
|
|||||||
/**
|
/**
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getName($currency = null, $locale = null) {
|
public function getCurrencyName($currency = null, $locale = null) {
|
||||||
if($locale === null) $locale = $this->getLocale();
|
if($locale === null) $locale = $this->getLocale();
|
||||||
if($currency === null) $currency = $this->getCurrency();
|
if($currency === null) $currency = $this->getCurrency();
|
||||||
|
|
||||||
@ -290,7 +196,7 @@ class Money extends DBField implements CompositeDBField {
|
|||||||
* @return FormField
|
* @return FormField
|
||||||
*/
|
*/
|
||||||
public function scaffoldFormField($title = null) {
|
public function scaffoldFormField($title = null) {
|
||||||
$field = new MoneyField($this->name);
|
$field = new MoneyField($this->getName());
|
||||||
$field->setAllowedCurrencies($this->getAllowedCurrencies());
|
$field->setAllowedCurrencies($this->getAllowedCurrencies());
|
||||||
$field->setLocale($this->getLocale());
|
$field->setLocale($this->getLocale());
|
||||||
|
|
||||||
|
@ -6,34 +6,12 @@
|
|||||||
* @package framework
|
* @package framework
|
||||||
* @subpackage model
|
* @subpackage model
|
||||||
*/
|
*/
|
||||||
class PolymorphicForeignKey extends ForeignKey implements CompositeDBField {
|
class PolymorphicForeignKey extends CompositeDBField {
|
||||||
|
|
||||||
/**
|
private static $composite_db = array(
|
||||||
* @var boolean $isChanged
|
'ID' => 'Int',
|
||||||
*/
|
'Class' => 'DBClassName("DataObject")'
|
||||||
protected $isChanged = false;
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Value of relation class
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected $classValue = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Field definition cache for compositeDatabaseFields
|
|
||||||
*
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected static $classname_spec_cache = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all cached classname specs. It's necessary to clear all cached subclassed names
|
|
||||||
* for any classes if a new class manifest is generated.
|
|
||||||
*/
|
|
||||||
public static function clear_classname_spec_cache() {
|
|
||||||
self::$classname_spec_cache = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function scaffoldFormField($title = null, $params = null) {
|
public function scaffoldFormField($title = null, $params = null) {
|
||||||
// Opt-out of form field generation - Scaffolding should be performed on
|
// Opt-out of form field generation - Scaffolding should be performed on
|
||||||
@ -42,55 +20,23 @@ class PolymorphicForeignKey extends ForeignKey implements CompositeDBField {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function requireField() {
|
|
||||||
$fields = $this->compositeDatabaseFields();
|
|
||||||
if($fields) foreach($fields as $name => $type){
|
|
||||||
DB::requireField($this->tableName, $this->name.$name, $type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function writeToManipulation(&$manipulation) {
|
|
||||||
|
|
||||||
// Write ID, checking that the value is valid
|
|
||||||
$manipulation['fields'][$this->name . 'ID'] = $this->exists()
|
|
||||||
? $this->prepValueForDB($this->getIDValue())
|
|
||||||
: $this->nullValue();
|
|
||||||
|
|
||||||
// Write class
|
|
||||||
$classObject = DBField::create_field('Enum', $this->getClassValue(), $this->name . 'Class');
|
|
||||||
$classObject->writeToManipulation($manipulation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function addToQuery(&$query) {
|
|
||||||
parent::addToQuery($query);
|
|
||||||
$query->selectField(
|
|
||||||
"\"{$this->tableName}\".\"{$this->name}ID\"",
|
|
||||||
"{$this->name}ID"
|
|
||||||
);
|
|
||||||
$query->selectField(
|
|
||||||
"\"{$this->tableName}\".\"{$this->name}Class\"",
|
|
||||||
"{$this->name}Class"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of the "Class" this key points to
|
* Get the value of the "Class" this key points to
|
||||||
*
|
*
|
||||||
* @return string Name of a subclass of DataObject
|
* @return string Name of a subclass of DataObject
|
||||||
*/
|
*/
|
||||||
public function getClassValue() {
|
public function getClassValue() {
|
||||||
return $this->classValue;
|
return $this->getField('Class');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the value of the "Class" this key points to
|
* Set the value of the "Class" this key points to
|
||||||
*
|
*
|
||||||
* @param string $class Name of a subclass of DataObject
|
* @param string $value Name of a subclass of DataObject
|
||||||
* @param boolean $markChanged Mark this field as changed?
|
* @param boolean $markChanged Mark this field as changed?
|
||||||
*/
|
*/
|
||||||
public function setClassValue($class, $markChanged = true) {
|
public function setClassValue($value, $markChanged = true) {
|
||||||
$this->classValue = $class;
|
$this->setField('Class', $value, $markChanged);
|
||||||
if($markChanged) $this->isChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,95 +45,36 @@ class PolymorphicForeignKey extends ForeignKey implements CompositeDBField {
|
|||||||
* @return integer
|
* @return integer
|
||||||
*/
|
*/
|
||||||
public function getIDValue() {
|
public function getIDValue() {
|
||||||
return parent::getValue();
|
return $this->getField('ID');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of the "ID" this key points to
|
* Sets the value of the "ID" this key points to
|
||||||
*
|
*
|
||||||
* @param integer $id
|
* @param integer $value
|
||||||
* @param boolean $markChanged Mark this field as changed?
|
* @param boolean $markChanged Mark this field as changed?
|
||||||
*/
|
*/
|
||||||
public function setIDValue($id, $markChanged = true) {
|
public function setIDValue($value, $markChanged = true) {
|
||||||
parent::setValue($id);
|
$this->setField('ID', $value, $markChanged);
|
||||||
if($markChanged) $this->isChanged = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setValue($value, $record = null, $markChanged = true) {
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
$idField = "{$this->name}ID";
|
// Map dataobject value to array
|
||||||
$classField = "{$this->name}Class";
|
|
||||||
|
|
||||||
// Check if an object is assigned directly
|
|
||||||
if($value instanceof DataObject) {
|
if($value instanceof DataObject) {
|
||||||
$record = array(
|
$value = array(
|
||||||
$idField => $value->ID,
|
'ID' => $value->ID,
|
||||||
$classField => $value->class
|
'Class' => $value->class
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert an object to an array
|
parent::setValue($value, $record, $markChanged);
|
||||||
if($record instanceof DataObject) {
|
|
||||||
$record = $record->getQueriedDatabaseFields();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use $value array if record is missing
|
|
||||||
if(empty($record) && is_array($value)) {
|
|
||||||
$record = $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inspect presented values
|
|
||||||
if(isset($record[$idField]) && isset($record[$classField])) {
|
|
||||||
if(empty($record[$idField]) || empty($record[$classField])) {
|
|
||||||
$this->setIDValue($this->nullValue(), $markChanged);
|
|
||||||
$this->setClassValue('', $markChanged);
|
|
||||||
} else {
|
|
||||||
$this->setClassValue($record[$classField], $markChanged);
|
|
||||||
$this->setIDValue($record[$idField], $markChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getValue() {
|
public function getValue() {
|
||||||
if($this->exists()) {
|
$id = $this->getIDValue();
|
||||||
return DataObject::get_by_id($this->getClassValue(), $this->getIDValue());
|
$class = $this->getClassValue();
|
||||||
|
if($id && $class && is_subclass_of($class, 'DataObject')) {
|
||||||
|
return DataObject::get_by_id($class, $id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function compositeDatabaseFields() {
|
|
||||||
|
|
||||||
// Ensure the table level cache exists
|
|
||||||
if(empty(self::$classname_spec_cache[$this->tableName])) {
|
|
||||||
self::$classname_spec_cache[$this->tableName] = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the field level cache exists
|
|
||||||
if(empty(self::$classname_spec_cache[$this->tableName][$this->name])) {
|
|
||||||
|
|
||||||
// Get all class names
|
|
||||||
$classNames = ClassInfo::subclassesFor('DataObject');
|
|
||||||
unset($classNames['DataObject']);
|
|
||||||
|
|
||||||
$schema = DB::get_schema();
|
|
||||||
if($schema->hasField($this->tableName, "{$this->name}Class")) {
|
|
||||||
$existing = DB::query("SELECT DISTINCT \"{$this->name}Class\" FROM \"{$this->tableName}\"")->column();
|
|
||||||
$classNames = array_unique(array_merge($classNames, $existing));
|
|
||||||
}
|
|
||||||
|
|
||||||
self::$classname_spec_cache[$this->tableName][$this->name]
|
|
||||||
= "Enum(array('" . implode("', '", array_filter($classNames)) . "'))";
|
|
||||||
}
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'ID' => 'Int',
|
|
||||||
'Class' => self::$classname_spec_cache[$this->tableName][$this->name]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isChanged() {
|
|
||||||
return $this->isChanged;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function exists() {
|
|
||||||
return $this->getClassValue() && $this->getIDValue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ class PrimaryKey extends Int {
|
|||||||
* @param string $name
|
* @param string $name
|
||||||
* @param DataOject $object The object that this is primary key for (should have a relation with $name)
|
* @param DataOject $object The object that this is primary key for (should have a relation with $name)
|
||||||
*/
|
*/
|
||||||
public function __construct($name = null, $object) {
|
public function __construct($name, $object = null) {
|
||||||
$this->object = $object;
|
$this->object = $object;
|
||||||
parent::__construct($name);
|
parent::__construct($name);
|
||||||
}
|
}
|
||||||
@ -31,5 +31,12 @@ class PrimaryKey extends Int {
|
|||||||
$field->setEmptyString(' ');
|
$field->setEmptyString(' ');
|
||||||
return $field;
|
return $field;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
|
parent::setValue($value, $record, $markChanged);
|
||||||
|
|
||||||
|
if($record instanceof DataObject) {
|
||||||
|
$this->object = $record;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
class Time extends DBField {
|
class Time extends DBField {
|
||||||
|
|
||||||
public function setValue($value, $record = null) {
|
public function setValue($value, $record = null, $markChanged = true) {
|
||||||
if($value) {
|
if($value) {
|
||||||
if(preg_match( '/(\d{1,2})[:.](\d{2})([a|A|p|P|][m|M])/', $value, $match )) $this->TwelveHour( $match );
|
if(preg_match( '/(\d{1,2})[:.](\d{2})([a|A|p|P|][m|M])/', $value, $match )) $this->TwelveHour( $match );
|
||||||
else $this->value = date('H:i:s', strtotime($value));
|
else $this->value = date('H:i:s', strtotime($value));
|
||||||
|
@ -16,6 +16,15 @@ class CompositeDBFieldTest extends SapphireTest {
|
|||||||
$this->assertTrue($obj->hasDatabaseField('MyMoneyAmount'));
|
$this->assertTrue($obj->hasDatabaseField('MyMoneyAmount'));
|
||||||
$this->assertTrue($obj->hasDatabaseField('MyMoneyCurrency'));
|
$this->assertTrue($obj->hasDatabaseField('MyMoneyCurrency'));
|
||||||
$this->assertFalse($obj->hasDatabaseField('MyMoney'));
|
$this->assertFalse($obj->hasDatabaseField('MyMoney'));
|
||||||
|
|
||||||
|
// Check that nested fields are exposed properly
|
||||||
|
$this->assertTrue($obj->dbObject('MyMoney')->hasField('Amount'));
|
||||||
|
$this->assertTrue($obj->dbObject('MyMoney')->hasField('Currency'));
|
||||||
|
|
||||||
|
// Not strictly correct
|
||||||
|
$this->assertFalse($obj->dbObject('MyMoney')->hasField('MyMoneyAmount'));
|
||||||
|
$this->assertFalse($obj->dbObject('MyMoney')->hasField('MyMoneyCurrency'));
|
||||||
|
$this->assertFalse($obj->dbObject('MyMoney')->hasField('MyMoney'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -24,16 +33,64 @@ class CompositeDBFieldTest extends SapphireTest {
|
|||||||
public function testCompositeFieldMetaDataFunctions() {
|
public function testCompositeFieldMetaDataFunctions() {
|
||||||
$this->assertEquals('Money', DataObject::is_composite_field('CompositeDBFieldTest_DataObject', 'MyMoney'));
|
$this->assertEquals('Money', DataObject::is_composite_field('CompositeDBFieldTest_DataObject', 'MyMoney'));
|
||||||
$this->assertNull(DataObject::is_composite_field('CompositeDBFieldTest_DataObject', 'Title'));
|
$this->assertNull(DataObject::is_composite_field('CompositeDBFieldTest_DataObject', 'Title'));
|
||||||
$this->assertEquals(array('MyMoney' => 'Money'),
|
$this->assertEquals(
|
||||||
DataObject::composite_fields('CompositeDBFieldTest_DataObject'));
|
array(
|
||||||
|
'MyMoney' => 'Money',
|
||||||
|
'OverriddenMoney' => 'Money'
|
||||||
|
),
|
||||||
|
DataObject::composite_fields('CompositeDBFieldTest_DataObject')
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
$this->assertEquals('Money', DataObject::is_composite_field('SubclassedDBFieldObject', 'MyMoney'));
|
$this->assertEquals('Money', DataObject::is_composite_field('SubclassedDBFieldObject', 'MyMoney'));
|
||||||
$this->assertEquals('Money', DataObject::is_composite_field('SubclassedDBFieldObject', 'OtherMoney'));
|
$this->assertEquals('Money', DataObject::is_composite_field('SubclassedDBFieldObject', 'OtherMoney'));
|
||||||
$this->assertNull(DataObject::is_composite_field('SubclassedDBFieldObject', 'Title'));
|
$this->assertNull(DataObject::is_composite_field('SubclassedDBFieldObject', 'Title'));
|
||||||
$this->assertNull(DataObject::is_composite_field('SubclassedDBFieldObject', 'OtherField'));
|
$this->assertNull(DataObject::is_composite_field('SubclassedDBFieldObject', 'OtherField'));
|
||||||
$this->assertEquals(array('MyMoney' => 'Money', 'OtherMoney' => 'Money'),
|
$this->assertEquals(
|
||||||
DataObject::composite_fields('SubclassedDBFieldObject'));
|
array(
|
||||||
|
'MyMoney' => 'Money',
|
||||||
|
'OtherMoney' => 'Money',
|
||||||
|
'OverriddenMoney' => 'Money',
|
||||||
|
),
|
||||||
|
DataObject::composite_fields('SubclassedDBFieldObject')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that changes to the fields affect the underlying dataobject, and vice versa
|
||||||
|
*/
|
||||||
|
public function testFieldBinding() {
|
||||||
|
$object = new CompositeDBFieldTest_DataObject();
|
||||||
|
$object->MyMoney->Currency = 'NZD';
|
||||||
|
$object->MyMoney->Amount = 100.0;
|
||||||
|
$this->assertEquals('NZD', $object->MyMoneyCurrency);
|
||||||
|
$this->assertEquals(100.0, $object->MyMoneyAmount);
|
||||||
|
$object->write();
|
||||||
|
|
||||||
|
$object2 = CompositeDBFieldTest_DataObject::get()->byID($object->ID);
|
||||||
|
$this->assertEquals('NZD', $object2->MyMoney->Currency);
|
||||||
|
$this->assertEquals(100.0, $object2->MyMoney->Amount);
|
||||||
|
|
||||||
|
$object2->MyMoneyCurrency = 'USD';
|
||||||
|
$this->assertEquals('USD', $object2->MyMoney->Currency);
|
||||||
|
|
||||||
|
$object2->MyMoney->setValue(array('Currency' => 'EUR', 'Amount' => 200.0));
|
||||||
|
$this->assertEquals('EUR', $object2->MyMoneyCurrency);
|
||||||
|
$this->assertEquals(200.0, $object2->MyMoneyAmount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensures that composite fields are assigned to the correct tables
|
||||||
|
*/
|
||||||
|
public function testInheritedTables() {
|
||||||
|
$object1 = new CompositeDBFieldTest_DataObject();
|
||||||
|
$object2 = new SubclassedDBFieldObject();
|
||||||
|
|
||||||
|
$this->assertEquals('CompositeDBFieldTest_DataObject', $object1->dbObject('MyMoney')->getTable());
|
||||||
|
$this->assertEquals('CompositeDBFieldTest_DataObject', $object1->dbObject('OverriddenMoney')->getTable());
|
||||||
|
$this->assertEquals('CompositeDBFieldTest_DataObject', $object2->dbObject('MyMoney')->getTable());
|
||||||
|
$this->assertEquals('SubclassedDBFieldObject', $object2->dbObject('OtherMoney')->getTable());
|
||||||
|
$this->assertEquals('SubclassedDBFieldObject', $object2->dbObject('OverriddenMoney')->getTable());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +98,7 @@ class CompositeDBFieldTest_DataObject extends DataObject implements TestOnly {
|
|||||||
private static $db = array(
|
private static $db = array(
|
||||||
'Title' => 'Text',
|
'Title' => 'Text',
|
||||||
'MyMoney' => 'Money',
|
'MyMoney' => 'Money',
|
||||||
|
'OverriddenMoney' => 'Money'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,5 +106,6 @@ class SubclassedDBFieldObject extends CompositeDBFieldTest_DataObject {
|
|||||||
private static $db = array(
|
private static $db = array(
|
||||||
'OtherField' => 'Text',
|
'OtherField' => 'Text',
|
||||||
'OtherMoney' => 'Money',
|
'OtherMoney' => 'Money',
|
||||||
|
'OverriddenMoney' => 'Money'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
121
tests/model/DBClassNameTest.php
Normal file
121
tests/model/DBClassNameTest.php
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class DBClassNameTest extends SapphireTest {
|
||||||
|
|
||||||
|
protected $extraDataObjects = array(
|
||||||
|
'DBClassNameTest_Object',
|
||||||
|
'DBClassNameTest_ObjectSubClass',
|
||||||
|
'DBClassNameTest_ObjectSubSubClass',
|
||||||
|
'DBClassNameTest_OtherClass'
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that custom subclasses generate the right hierarchy
|
||||||
|
*/
|
||||||
|
public function testEnumList() {
|
||||||
|
// Object 1 fields
|
||||||
|
$object = new DBClassNameTest_Object();
|
||||||
|
$defaultClass = $object->dbObject('DefaultClass');
|
||||||
|
$anyClass = $object->dbObject('AnyClass');
|
||||||
|
$childClass = $object->dbObject('ChildClass');
|
||||||
|
$leafClass = $object->dbObject('LeafClass');
|
||||||
|
|
||||||
|
// Object 2 fields
|
||||||
|
$object2 = new DBClassNameTest_ObjectSubClass();
|
||||||
|
$midDefault = $object2->dbObject('MidClassDefault');
|
||||||
|
$midClass = $object2->dbObject('MidClass');
|
||||||
|
|
||||||
|
// Default fields always default to children of base class (even if put in a subclass)
|
||||||
|
$mainSubclasses = array (
|
||||||
|
'DBClassNameTest_Object' => 'DBClassNameTest_Object',
|
||||||
|
'DBClassNameTest_ObjectSubClass' => 'DBClassNameTest_ObjectSubClass',
|
||||||
|
'DBClassNameTest_ObjectSubSubClass' => 'DBClassNameTest_ObjectSubSubClass',
|
||||||
|
);
|
||||||
|
$this->assertEquals($mainSubclasses, $defaultClass->getEnumObsolete());
|
||||||
|
$this->assertEquals($mainSubclasses, $midDefault->getEnumObsolete());
|
||||||
|
|
||||||
|
// Unbound classes detect any
|
||||||
|
$anyClasses = $anyClass->getEnumObsolete();
|
||||||
|
$this->assertContains('DBClassNameTest_OtherClass', $anyClasses);
|
||||||
|
$this->assertContains('DBClassNameTest_Object', $anyClasses);
|
||||||
|
$this->assertContains('DBClassNameTest_ObjectSubClass', $anyClasses);
|
||||||
|
$this->assertContains('DBClassNameTest_ObjectSubSubClass', $anyClasses);
|
||||||
|
|
||||||
|
// Classes bound to the middle of a tree
|
||||||
|
$midSubClasses = $mainSubclasses = array (
|
||||||
|
'DBClassNameTest_ObjectSubClass' => 'DBClassNameTest_ObjectSubClass',
|
||||||
|
'DBClassNameTest_ObjectSubSubClass' => 'DBClassNameTest_ObjectSubSubClass',
|
||||||
|
);
|
||||||
|
$this->assertEquals($midSubClasses, $childClass->getEnumObsolete());
|
||||||
|
$this->assertEquals($midSubClasses, $midClass->getEnumObsolete());
|
||||||
|
|
||||||
|
// Leaf clasess contain only exactly one node
|
||||||
|
$this->assertEquals(
|
||||||
|
array('DBClassNameTest_ObjectSubSubClass' => 'DBClassNameTest_ObjectSubSubClass',),
|
||||||
|
$leafClass->getEnumObsolete()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the base class can be detected under various circumstances
|
||||||
|
*/
|
||||||
|
public function testBaseClassDetection() {
|
||||||
|
// Explicit DataObject
|
||||||
|
$field1 = new DBClassName('MyClass', 'DataObject');
|
||||||
|
$this->assertEquals('DataObject', $field1->getBaseClass());
|
||||||
|
|
||||||
|
// Explicit base class
|
||||||
|
$field2 = new DBClassName('MyClass', 'DBClassNameTest_Object');
|
||||||
|
$this->assertEquals('DBClassNameTest_Object', $field2->getBaseClass());
|
||||||
|
|
||||||
|
// Explicit subclass
|
||||||
|
$field3 = new DBClassName('MyClass');
|
||||||
|
$field3->setValue(null, new DBClassNameTest_ObjectSubClass());
|
||||||
|
$this->assertEquals('DBClassNameTest_Object', $field3->getBaseClass());
|
||||||
|
|
||||||
|
// Implicit table
|
||||||
|
$field4 = new DBClassName('MyClass');
|
||||||
|
$field4->setTable('DBClassNameTest_ObjectSubClass_versions');
|
||||||
|
$this->assertEquals('DBClassNameTest_Object', $field4->getBaseClass());
|
||||||
|
|
||||||
|
// Missing
|
||||||
|
$field5 = new DBClassName('MyClass');
|
||||||
|
$this->assertEquals('DataObject', $field5->getBaseClass());
|
||||||
|
|
||||||
|
// Invalid class
|
||||||
|
$field6 = new DBClassName('MyClass');
|
||||||
|
$field6->setTable('InvalidTable');
|
||||||
|
$this->assertEquals('DataObject', $field6->getBaseClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DBClassNameTest_Object extends DataObject implements TestOnly {
|
||||||
|
|
||||||
|
private static $extensions = array(
|
||||||
|
'Versioned'
|
||||||
|
);
|
||||||
|
|
||||||
|
private static $db = array(
|
||||||
|
'DefaultClass' => 'DBClassName',
|
||||||
|
'AnyClass' => 'DBClassName("DataObject")',
|
||||||
|
'ChildClass' => 'DBClassName("DBClassNameTest_ObjectSubClass")',
|
||||||
|
'LeafClass' => 'DBClassName("DBClassNameTest_ObjectSubSubClass")'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DBClassNameTest_ObjectSubClass extends DBClassNameTest_Object {
|
||||||
|
private static $db = array(
|
||||||
|
'MidClassDefault' => 'DBClassName',
|
||||||
|
'MidClass' => 'DBClassName("DBClassNameTest_ObjectSubclass")'
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class DBClassNameTest_ObjectSubSubClass extends DBClassNameTest_ObjectSubclass {
|
||||||
|
}
|
||||||
|
|
||||||
|
class DBClassNameTest_OtherClass extends DataObject implements TestOnly {
|
||||||
|
private static $db = array(
|
||||||
|
'Title' => 'Varchar'
|
||||||
|
);
|
||||||
|
}
|
@ -130,20 +130,30 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
|
|
||||||
// Test with blank entries
|
// Test with blank entries
|
||||||
DataObject::clear_classname_spec_cache();
|
DataObject::clear_classname_spec_cache();
|
||||||
|
$do1 = new DataObjectSchemaGenerationTest_DO();
|
||||||
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
||||||
|
$this->assertEquals("DBClassName", $fields['ClassName']);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
"Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')",
|
array(
|
||||||
$fields['ClassName']
|
'DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO',
|
||||||
|
'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'
|
||||||
|
),
|
||||||
|
$do1->dbObject('ClassName')->getEnum()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
// Test with instance of subclass
|
// Test with instance of subclass
|
||||||
$item1 = new DataObjectSchemaGenerationTest_IndexDO();
|
$item1 = new DataObjectSchemaGenerationTest_IndexDO();
|
||||||
$item1->write();
|
$item1->write();
|
||||||
DataObject::clear_classname_spec_cache();
|
DataObject::clear_classname_spec_cache();
|
||||||
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
||||||
|
$this->assertEquals("DBClassName", $fields['ClassName']);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
"Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')",
|
array(
|
||||||
$fields['ClassName']
|
'DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO',
|
||||||
|
'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'
|
||||||
|
),
|
||||||
|
$item1->dbObject('ClassName')->getEnum()
|
||||||
);
|
);
|
||||||
$item1->delete();
|
$item1->delete();
|
||||||
|
|
||||||
@ -152,9 +162,13 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
$item2->write();
|
$item2->write();
|
||||||
DataObject::clear_classname_spec_cache();
|
DataObject::clear_classname_spec_cache();
|
||||||
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
||||||
|
$this->assertEquals("DBClassName", $fields['ClassName']);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
"Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')",
|
array(
|
||||||
$fields['ClassName']
|
'DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO',
|
||||||
|
'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'
|
||||||
|
),
|
||||||
|
$item2->dbObject('ClassName')->getEnum()
|
||||||
);
|
);
|
||||||
$item2->delete();
|
$item2->delete();
|
||||||
|
|
||||||
@ -165,9 +179,13 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
|||||||
$item2->write();
|
$item2->write();
|
||||||
DataObject::clear_classname_spec_cache();
|
DataObject::clear_classname_spec_cache();
|
||||||
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
||||||
|
$this->assertEquals("DBClassName", $fields['ClassName']);
|
||||||
$this->assertEquals(
|
$this->assertEquals(
|
||||||
"Enum('DataObjectSchemaGenerationTest_DO, DataObjectSchemaGenerationTest_IndexDO')",
|
array(
|
||||||
$fields['ClassName']
|
'DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO',
|
||||||
|
'DataObjectSchemaGenerationTest_IndexDO' => 'DataObjectSchemaGenerationTest_IndexDO'
|
||||||
|
),
|
||||||
|
$item1->dbObject('ClassName')->getEnum()
|
||||||
);
|
);
|
||||||
$item1->delete();
|
$item1->delete();
|
||||||
$item2->delete();
|
$item2->delete();
|
||||||
|
@ -10,8 +10,23 @@ class ManyManyListTest extends SapphireTest {
|
|||||||
|
|
||||||
protected $extraDataObjects = array(
|
protected $extraDataObjects = array(
|
||||||
'DataObjectTest_Team',
|
'DataObjectTest_Team',
|
||||||
|
'DataObjectTest_Fixture',
|
||||||
'DataObjectTest_SubTeam',
|
'DataObjectTest_SubTeam',
|
||||||
|
'OtherSubclassWithSameField',
|
||||||
|
'DataObjectTest_FieldlessTable',
|
||||||
|
'DataObjectTest_FieldlessSubTable',
|
||||||
|
'DataObjectTest_ValidatedObject',
|
||||||
'DataObjectTest_Player',
|
'DataObjectTest_Player',
|
||||||
|
'DataObjectTest_TeamComment',
|
||||||
|
'DataObjectTest_EquipmentCompany',
|
||||||
|
'DataObjectTest_SubEquipmentCompany',
|
||||||
|
'DataObjectTest\NamespacedClass',
|
||||||
|
'DataObjectTest\RelationClass',
|
||||||
|
'DataObjectTest_ExtendedTeamComment',
|
||||||
|
'DataObjectTest_Company',
|
||||||
|
'DataObjectTest_Staff',
|
||||||
|
'DataObjectTest_CEO',
|
||||||
|
'DataObjectTest_Fan',
|
||||||
'ManyManyListTest_ExtraFields'
|
'ManyManyListTest_ExtraFields'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -145,11 +145,11 @@ class MoneyTest extends SapphireTest {
|
|||||||
));
|
));
|
||||||
$m->setLocale('ar_EG');
|
$m->setLocale('ar_EG');
|
||||||
|
|
||||||
$this->assertSame('Estnische Krone', $m->getName('EEK','de_AT'));
|
$this->assertSame('Estnische Krone', $m->getCurrencyName('EEK','de_AT'));
|
||||||
$this->assertSame('يورو', $m->getName());
|
$this->assertSame('يورو', $m->getCurrencyName());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$m->getName('EGP', 'xy_XY');
|
$m->getCurrencyName('EGP', 'xy_XY');
|
||||||
$this->setExpectedException("Exception");
|
$this->setExpectedException("Exception");
|
||||||
} catch(Exception $e) {
|
} catch(Exception $e) {
|
||||||
}
|
}
|
||||||
|
@ -263,17 +263,15 @@ class ViewableData extends Object implements IteratorAggregate {
|
|||||||
* on this object.
|
* on this object.
|
||||||
*
|
*
|
||||||
* @param string $field
|
* @param string $field
|
||||||
* @return string
|
* @return string Casting helper
|
||||||
*/
|
*/
|
||||||
public function castingHelper($field) {
|
public function castingHelper($field) {
|
||||||
if($this->hasMethod('db') && $fieldSpec = $this->db($field)) {
|
$specs = $this->config()->casting;
|
||||||
return $fieldSpec;
|
if(isset($specs[$field])) {
|
||||||
|
return $specs[$field];
|
||||||
|
} elseif($this->failover) {
|
||||||
|
return $this->failover->castingHelper($field);
|
||||||
}
|
}
|
||||||
|
|
||||||
$specs = Config::inst()->get(get_class($this), 'casting');
|
|
||||||
if(isset($specs[$field])) return $specs[$field];
|
|
||||||
|
|
||||||
if($this->failover) return $this->failover->castingHelper($field);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user