mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Move many methods from DataObject to DataObjectSchema
This commit is contained in:
parent
f0dd9af699
commit
11bbed4f76
@ -212,10 +212,11 @@ class AssetControlExtension extends DataExtension
|
||||
{
|
||||
// Search for dbfile instances
|
||||
$files = array();
|
||||
foreach ($record->db() as $field => $db) {
|
||||
$fields = DataObject::getSchema()->fieldSpecs($record);
|
||||
foreach ($fields as $field => $db) {
|
||||
$fieldObj = $record->$field;
|
||||
if(!is_object($fieldObj) || !($record->$field instanceof DBFile)) {
|
||||
continue;
|
||||
if (!($fieldObj instanceof DBFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Omit variant and merge with set
|
||||
|
@ -112,7 +112,7 @@ class ClassInfo {
|
||||
);
|
||||
|
||||
foreach ($classes as $class) {
|
||||
if (DataObject::has_own_table($class)) {
|
||||
if (DataObject::getSchema()->classHasTable($class)) {
|
||||
$result[$class] = $class;
|
||||
}
|
||||
}
|
||||
@ -201,7 +201,7 @@ class ClassInfo {
|
||||
if(!isset(self::$_cache_ancestry[$cacheKey])) {
|
||||
$ancestry = array();
|
||||
do {
|
||||
if (!$tablesOnly || DataObject::has_own_table($parent)) {
|
||||
if (!$tablesOnly || DataObject::getSchema()->classHasTable($parent)) {
|
||||
$ancestry[$parent] = $parent;
|
||||
}
|
||||
} while ($parent = get_parent_class($parent));
|
||||
|
@ -222,7 +222,9 @@ class CsvBulkLoader extends BulkLoader {
|
||||
|
||||
// find existing object, or create new one
|
||||
$existingObj = $this->findExistingObject($record, $columnMap);
|
||||
/** @var DataObject $obj */
|
||||
$obj = ($existingObj) ? $existingObj : new $class();
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
// first run: find/create any relations and store them on the object
|
||||
// we can't combine runs, as other columns might rely on the relation being present
|
||||
@ -243,7 +245,7 @@ class CsvBulkLoader extends BulkLoader {
|
||||
$relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record);
|
||||
}
|
||||
if(!$relationObj || !$relationObj->exists()) {
|
||||
$relationClass = $obj->hasOneComponent($relationName);
|
||||
$relationClass = $schema->hasOneComponent(get_class($obj), $relationName);
|
||||
$relationObj = new $relationClass();
|
||||
//write if we aren't previewing
|
||||
if (!$preview) $relationObj->write();
|
||||
@ -327,7 +329,7 @@ class CsvBulkLoader extends BulkLoader {
|
||||
*
|
||||
* @param array $record CSV data column
|
||||
* @param array $columnMap
|
||||
* @return mixed
|
||||
* @return DataObject
|
||||
*/
|
||||
public function findExistingObject($record, $columnMap = []) {
|
||||
$SNG_objectClass = singleton($this->objectClass);
|
||||
|
@ -87,6 +87,7 @@ class FixtureBlueprint {
|
||||
|
||||
try {
|
||||
$class = $this->class;
|
||||
$schema = DataObject::getSchema();
|
||||
$obj = DataModel::inst()->$class->newObject();
|
||||
|
||||
// If an ID is explicitly passed, then we'll sort out the initial write straight away
|
||||
@ -120,11 +121,10 @@ class FixtureBlueprint {
|
||||
|
||||
// Populate overrides
|
||||
if($data) foreach($data as $fieldName => $fieldVal) {
|
||||
// Defer relationship processing
|
||||
if(
|
||||
$obj->manyManyComponent($fieldName)
|
||||
|| $obj->hasManyComponent($fieldName)
|
||||
|| $obj->hasOneComponent($fieldName)
|
||||
$schema->manyManyComponent($class, $fieldName)
|
||||
|| $schema->hasManyComponent($class, $fieldName)
|
||||
|| $schema->hasOneComponent($class, $fieldName)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
@ -142,8 +142,8 @@ class FixtureBlueprint {
|
||||
|
||||
// Populate all relations
|
||||
if($data) foreach($data as $fieldName => $fieldVal) {
|
||||
$isManyMany = $obj->manyManyComponent($fieldName);
|
||||
$isHasMany = $obj->hasManyComponent($fieldName);
|
||||
$isManyMany = $schema->manyManyComponent($class, $fieldName);
|
||||
$isHasMany = $schema->hasManyComponent($class, $fieldName);
|
||||
if ($isManyMany && $isHasMany) {
|
||||
throw new InvalidArgumentException("$fieldName is both many_many and has_many");
|
||||
}
|
||||
@ -207,7 +207,7 @@ class FixtureBlueprint {
|
||||
}
|
||||
} else {
|
||||
$hasOneField = preg_replace('/ID$/', '', $fieldName);
|
||||
if($className = $obj->hasOneComponent($hasOneField)) {
|
||||
if($className = $schema->hasOneComponent($class, $hasOneField)) {
|
||||
$obj->{$hasOneField.'ID'} = $this->parseValue($fieldVal, $fixtures, $fieldClass);
|
||||
// Inject class for polymorphic relation
|
||||
if($className === 'SilverStripe\\ORM\\DataObject') {
|
||||
|
@ -123,8 +123,8 @@ class FileField extends FormField {
|
||||
/** @var File $file */
|
||||
if($this->relationAutoSetting) {
|
||||
// assume that the file is connected via a has-one
|
||||
$objectClass = $record->hasOneComponent($this->name);
|
||||
if($objectClass === 'SilverStripe\\Assets\\File' || empty($objectClass)) {
|
||||
$objectClass = DataObject::getSchema()->hasOneComponent(get_class($record), $this->name);
|
||||
if($objectClass === File::class || empty($objectClass)) {
|
||||
// Create object of the appropriate file class
|
||||
$file = Object::create($fileClass);
|
||||
} else {
|
||||
|
@ -114,6 +114,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
|
||||
$columns = $gridField->getColumns();
|
||||
$currentColumn = 0;
|
||||
|
||||
$schema = DataObject::getSchema();
|
||||
foreach($columns as $columnField) {
|
||||
$currentColumn++;
|
||||
$metadata = $gridField->getColumnMetadata($columnField);
|
||||
@ -139,7 +140,7 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM
|
||||
} elseif(method_exists($tmpItem, 'hasMethod') && $tmpItem->hasMethod($methodName)) {
|
||||
// The part is a relation name, so get the object/list from it
|
||||
$tmpItem = $tmpItem->$methodName();
|
||||
} elseif($tmpItem instanceof DataObject && $tmpItem->hasDatabaseField($methodName)) {
|
||||
} elseif ($tmpItem instanceof DataObject && $schema->fieldSpec($tmpItem, $methodName, ['dbOnly'])) {
|
||||
// Else, if we've found a database field at the end of the chain, we can sort on it.
|
||||
// If a method is applied further to this field (E.g. 'Cost.Currency') then don't try to sort.
|
||||
$allowSort = $idx === sizeof($parts) - 1;
|
||||
|
@ -543,7 +543,7 @@ class UploadField extends FileField {
|
||||
if($relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
|
||||
// has_many or many_many
|
||||
$relation->setByIDList($idList);
|
||||
} elseif($record->hasOneComponent($fieldname)) {
|
||||
} elseif(DataObject::getSchema()->hasOneComponent(get_class($record), $fieldname)) {
|
||||
// has_one
|
||||
$record->{"{$fieldname}ID"} = $idList ? reset($idList) : 0;
|
||||
}
|
||||
@ -631,7 +631,7 @@ class UploadField extends FileField {
|
||||
if(empty($allowedMaxFileNumber)) {
|
||||
$record = $this->getRecord();
|
||||
$name = $this->getName();
|
||||
if($record && $record->hasOneComponent($name)) {
|
||||
if($record && DataObject::getSchema()->hasOneComponent(get_class($record), $name)) {
|
||||
return 1; // Default for has_one
|
||||
} else {
|
||||
return null; // Default for has_many and many_many
|
||||
|
@ -219,10 +219,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
/**
|
||||
* Static caches used by relevant functions.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_cache_has_own_table = array();
|
||||
protected static $_cache_get_one;
|
||||
protected static $_cache_get_class_ancestry;
|
||||
|
||||
/**
|
||||
* Cache of field labels
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $_cache_field_labels = array();
|
||||
|
||||
/**
|
||||
@ -277,88 +283,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return DataObjectSchema
|
||||
*/
|
||||
public static function getSchema() {
|
||||
return Injector::inst()->get('SilverStripe\ORM\DataObjectSchema');
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the complete map of fields to specification on this object, including fixed_fields.
|
||||
* "ID" will be included on every table.
|
||||
*
|
||||
* Composite DB field specifications are returned by reference if necessary, but not in the return
|
||||
* array.
|
||||
*
|
||||
* Can be called directly on an object. E.g. Member::database_fields()
|
||||
*
|
||||
* @param string $class Class name to query from
|
||||
* @return array Map of fieldname to specification, similiar to {@link DataObject::$db}.
|
||||
*/
|
||||
public static function database_fields($class = null) {
|
||||
if(empty($class)) {
|
||||
$class = get_called_class();
|
||||
}
|
||||
return static::getSchema()->databaseFields($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all database columns explicitly defined on a class in {@link DataObject::$db}
|
||||
* and {@link DataObject::$has_one}. Resolves instances of {@link DBComposite}
|
||||
* into the actual database fields, rather than the name of the field which
|
||||
* might not equate a database column.
|
||||
*
|
||||
* Does not include "base fields" like "ID", "ClassName", "Created", "LastEdited",
|
||||
* see {@link database_fields()}.
|
||||
*
|
||||
* Can be called directly on an object. E.g. Member::custom_database_fields()
|
||||
*
|
||||
* @uses DBComposite->compositeDatabaseFields()
|
||||
*
|
||||
* @param string $class Class name to query from
|
||||
* @return array Map of fieldname to specification, similiar to {@link DataObject::$db}.
|
||||
*/
|
||||
public static function custom_database_fields($class = null) {
|
||||
if(empty($class)) {
|
||||
$class = get_called_class();
|
||||
}
|
||||
|
||||
// Remove fixed fields. This assumes that NO fixed_fields are composite
|
||||
$fields = static::getSchema()->databaseFields($class);
|
||||
$fields = array_diff_key($fields, self::config()->fixed_fields);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field class if the given db field on the class is a composite field.
|
||||
* Will check all applicable ancestor classes and aggregate results.
|
||||
*
|
||||
* @param string $class Class to check
|
||||
* @param string $name Field to check
|
||||
* @param boolean $aggregated True if parent classes should be checked, or false to limit to this class
|
||||
* @return string|false Class spec name of composite field if it exists, or false if not
|
||||
*/
|
||||
public static function is_composite_field($class, $name, $aggregated = true) {
|
||||
$fields = self::composite_fields($class, $aggregated);
|
||||
return isset($fields[$name]) ? $fields[$name] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* Can be called directly on an object. E.g. Member::composite_fields(), or Member::composite_fields(null, true)
|
||||
* to aggregate.
|
||||
*
|
||||
* 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 List of composite fields and their class spec
|
||||
*/
|
||||
public static function composite_fields($class = null, $aggregated = true) {
|
||||
// Check $class
|
||||
if(empty($class)) {
|
||||
$class = get_called_class();
|
||||
}
|
||||
return static::getSchema()->compositeFields($class, $aggregated);
|
||||
return Injector::inst()->get(DataObjectSchema::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -381,8 +306,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(!$record) {
|
||||
$record = array(
|
||||
'ID' => 0,
|
||||
'ClassName' => get_class($this),
|
||||
'RecordClassName' => get_class($this)
|
||||
'ClassName' => static::class,
|
||||
'RecordClassName' => static::class
|
||||
);
|
||||
}
|
||||
|
||||
@ -417,13 +342,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// Identify fields that should be lazy loaded, but only on existing records
|
||||
if(!empty($record['ID'])) {
|
||||
$currentObj = get_class($this);
|
||||
while($currentObj != 'SilverStripe\ORM\DataObject') {
|
||||
$fields = self::custom_database_fields($currentObj);
|
||||
foreach($fields as $field => $type) {
|
||||
if(!array_key_exists($field, $record)) $this->record[$field.'_Lazy'] = $currentObj;
|
||||
// Get all field specs scoped to class for later lazy loading
|
||||
$fields = static::getSchema()->fieldSpecs(static::class, ['includeClass', 'dbOnly']);
|
||||
foreach($fields as $field => $fieldSpec) {
|
||||
$fieldClass = strtok($fieldSpec, ".");
|
||||
if(!array_key_exists($field, $record)) {
|
||||
$this->record[$field.'_Lazy'] = $fieldClass;
|
||||
}
|
||||
$currentObj = get_parent_class($currentObj);
|
||||
}
|
||||
}
|
||||
|
||||
@ -478,7 +403,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function duplicate($doWrite = true) {
|
||||
/** @var static $clone */
|
||||
$clone = Injector::inst()->create(get_class($this), $this->toMap(), false, $this->model );
|
||||
$clone = Injector::inst()->create(static::class, $this->toMap(), false, $this->model );
|
||||
$clone->ID = 0;
|
||||
|
||||
$clone->invokeWithExtensions('onBeforeDuplicate', $this, $doWrite);
|
||||
@ -563,7 +488,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function getClassName() {
|
||||
$className = $this->getField("ClassName");
|
||||
if (!ClassInfo::exists($className)) {
|
||||
return get_class($this);
|
||||
return static::class;
|
||||
}
|
||||
return $className;
|
||||
}
|
||||
@ -580,7 +505,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function setClassName($className) {
|
||||
$className = trim($className);
|
||||
if(!$className || !is_subclass_of($className, __CLASS__)) {
|
||||
if(!$className || !is_subclass_of($className, self::class)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -607,7 +532,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return DataObject The new instance of the new class, The exact type will be of the class name provided.
|
||||
*/
|
||||
public function newClassInstance($newClassName) {
|
||||
if (!is_subclass_of($newClassName, __CLASS__)) {
|
||||
if (!is_subclass_of($newClassName, self::class)) {
|
||||
throw new InvalidArgumentException("$newClassName is not a valid subclass of DataObject");
|
||||
}
|
||||
|
||||
@ -645,7 +570,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
|
||||
if($this->class == 'SilverStripe\ORM\DataObject') return;
|
||||
if(static::class === self::class) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set up accessors for joined items
|
||||
if($manyMany = $this->manyMany()) {
|
||||
@ -813,8 +740,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return string
|
||||
*/
|
||||
public function getTitle() {
|
||||
if($this->hasDatabaseField('Title')) return $this->getField('Title');
|
||||
if($this->hasDatabaseField('Name')) return $this->getField('Name');
|
||||
$schema = static::getSchema();
|
||||
if($schema->fieldSpec($this, 'Title')) {
|
||||
return $this->getField('Title');
|
||||
}
|
||||
if($schema->fieldSpec($this, 'Name')) {
|
||||
return $this->getField('Name');
|
||||
}
|
||||
|
||||
return "#{$this->ID}";
|
||||
}
|
||||
@ -966,8 +898,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
// makes sure we don't merge data like ID or ClassName
|
||||
$rightData = $rightObj->db();
|
||||
|
||||
$rightData = DataObject::getSchema()->fieldSpecs(get_class($rightObj));
|
||||
foreach($rightData as $key=>$rightSpec) {
|
||||
// Don't merge ID
|
||||
if($key === 'ID') {
|
||||
@ -1032,11 +963,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function forceChange() {
|
||||
// Ensure lazy fields loaded
|
||||
$this->loadLazyFields();
|
||||
$fields = static::getSchema()->fieldSpecs(static::class);
|
||||
|
||||
// $this->record might not contain the blank values so we loop on $this->inheritedDatabaseFields() as well
|
||||
$fieldNames = array_unique(array_merge(
|
||||
array_keys($this->record),
|
||||
array_keys($this->db())
|
||||
array_keys($fields)
|
||||
));
|
||||
|
||||
foreach($fieldNames as $fieldName) {
|
||||
@ -1046,7 +978,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
// @todo Find better way to allow versioned to write a new version after forceChange
|
||||
if($this->isChanged('Version')) unset($this->changed['Version']);
|
||||
if($this->isChanged('Version')) {
|
||||
unset($this->changed['Version']);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -1154,13 +1088,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->$fieldName = $fieldValue;
|
||||
}
|
||||
// Set many-many defaults with an array of ids
|
||||
if(is_array($fieldValue) && $this->manyManyComponent($fieldName)) {
|
||||
if(is_array($fieldValue) && $this->getSchema()->manyManyComponent(static::class, $fieldName)) {
|
||||
/** @var ManyManyList $manyManyJoin */
|
||||
$manyManyJoin = $this->$fieldName();
|
||||
$manyManyJoin->setByIDList($fieldValue);
|
||||
}
|
||||
}
|
||||
if($class == 'SilverStripe\ORM\DataObject') {
|
||||
if($class == self::class) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1183,7 +1117,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
);
|
||||
}
|
||||
|
||||
if(Config::inst()->get('SilverStripe\ORM\DataObject', 'validation_enabled')) {
|
||||
if($this->config()->get('validation_enabled')) {
|
||||
$result = $this->validate();
|
||||
if (!$result->valid()) {
|
||||
return new ValidationException(
|
||||
@ -1246,22 +1180,24 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @param string $class Class of table to manipulate
|
||||
*/
|
||||
protected function prepareManipulationTable($baseTable, $now, $isNewRecord, &$manipulation, $class) {
|
||||
$table = $this->getSchema()->tableName($class);
|
||||
$schema = $this->getSchema();
|
||||
$table = $schema->tableName($class);
|
||||
$manipulation[$table] = array();
|
||||
|
||||
// Extract records for this table
|
||||
foreach($this->record as $fieldName => $fieldValue) {
|
||||
|
||||
// Check if this record pertains to this table, and
|
||||
// we're not attempting to reset the BaseTable->ID
|
||||
if( empty($this->changed[$fieldName])
|
||||
|| ($table === $baseTable && $fieldName === 'ID')
|
||||
|| (!self::has_own_table_database_field($class, $fieldName)
|
||||
&& !self::is_composite_field($class, $fieldName, false))
|
||||
) {
|
||||
// Ignore unchanged fields or attempts to reset the BaseTable->ID
|
||||
if (empty($this->changed[$fieldName]) || ($table === $baseTable && $fieldName === 'ID')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure this field pertains to this table
|
||||
$specification = $schema->fieldSpec($class, $fieldName, ['dbOnly', 'uninherited']);
|
||||
if (!$specification) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if database column doesn't correlate to a DBField instance...
|
||||
$fieldObj = $this->dbObject($fieldName);
|
||||
@ -1323,10 +1259,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
protected function writeManipulation($baseTable, $now, $isNewRecord) {
|
||||
// Generate database manipulations for each class
|
||||
$manipulation = array();
|
||||
foreach($this->getClassAncestry() as $class) {
|
||||
if(self::has_own_table($class)) {
|
||||
$this->prepareManipulationTable($baseTable, $now, $isNewRecord, $manipulation, $class);
|
||||
}
|
||||
foreach(ClassInfo::ancestry(static::class, true) as $class) {
|
||||
$this->prepareManipulationTable($baseTable, $now, $isNewRecord, $manipulation, $class);
|
||||
}
|
||||
|
||||
// Allow extensions to extend this manipulation
|
||||
@ -1501,7 +1435,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return array Class ancestry
|
||||
*/
|
||||
public function getClassAncestry() {
|
||||
return ClassInfo::ancestry(get_class($this));
|
||||
return ClassInfo::ancestry(static::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1518,12 +1452,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $this->components[$componentName];
|
||||
}
|
||||
|
||||
if($class = $this->hasOneComponent($componentName)) {
|
||||
$schema = static::getSchema();
|
||||
if($class = $schema->hasOneComponent(static::class, $componentName)) {
|
||||
$joinField = $componentName . 'ID';
|
||||
$joinID = $this->getField($joinField);
|
||||
|
||||
// Extract class name for polymorphic relations
|
||||
if($class === __CLASS__) {
|
||||
if($class === self::class) {
|
||||
$class = $this->getField($componentName . 'Class');
|
||||
if(empty($class)) return null;
|
||||
}
|
||||
@ -1539,8 +1474,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(empty($component)) {
|
||||
$component = $this->model->$class->newObject();
|
||||
}
|
||||
} elseif($class = $this->belongsToComponent($componentName)) {
|
||||
$joinField = $this->getRemoteJoinField($componentName, 'belongs_to', $polymorphic);
|
||||
} elseif($class = $schema->belongsToComponent(static::class, $componentName)) {
|
||||
$joinField = $schema->getRemoteJoinField(static::class, $componentName, 'belongs_to', $polymorphic);
|
||||
$joinID = $this->ID;
|
||||
|
||||
if($joinID) {
|
||||
@ -1591,7 +1526,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function getComponents($componentName) {
|
||||
$result = null;
|
||||
|
||||
$componentClass = $this->hasManyComponent($componentName);
|
||||
$schema = $this->getSchema();
|
||||
$componentClass = $schema->hasManyComponent(static::class, $componentName);
|
||||
if(!$componentClass) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
"DataObject::getComponents(): Unknown 1-to-many component '%s' on class '%s'",
|
||||
@ -1610,7 +1546,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
// Determine type and nature of foreign relation
|
||||
$joinField = $this->getRemoteJoinField($componentName, 'has_many', $polymorphic);
|
||||
$joinField = $schema->getRemoteJoinField(static::class, $componentName, 'has_many', $polymorphic);
|
||||
/** @var HasManyList $result */
|
||||
if($polymorphic) {
|
||||
$result = PolymorphicHasManyList::create($componentClass, $joinField, $this->class);
|
||||
@ -1635,7 +1571,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function getRelationClass($relationName) {
|
||||
// Parse many_many
|
||||
$manyManyComponent = $this->manyManyComponent($relationName);
|
||||
$manyManyComponent = $this->getSchema()->manyManyComponent(static::class, $relationName);
|
||||
if ($manyManyComponent) {
|
||||
list(
|
||||
$relationClass, $parentClass, $componentClass,
|
||||
@ -1702,6 +1638,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function inferReciprocalComponent($remoteClass, $remoteRelation) {
|
||||
$remote = DataObject::singleton($remoteClass);
|
||||
$class = $remote->getRelationClass($remoteRelation);
|
||||
$schema = static::getSchema();
|
||||
|
||||
// Validate arguments
|
||||
if(!$this->isInDB()) {
|
||||
@ -1715,7 +1652,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$remoteRelation
|
||||
));
|
||||
}
|
||||
if($class === 'SilverStripe\ORM\DataObject') {
|
||||
if($class === self::class) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
"%s cannot generate opposite component of relation %s.%s as it is polymorphic. " .
|
||||
"This method does not support polymorphic relationships",
|
||||
@ -1727,7 +1664,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(!is_a($this, $class, true)) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
"Relation %s on %s does not refer to objects of type %s",
|
||||
$remoteRelation, $remoteClass, get_class($this)
|
||||
$remoteRelation, $remoteClass, static::class
|
||||
));
|
||||
}
|
||||
|
||||
@ -1737,7 +1674,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
case 'has_one': {
|
||||
// Mock has_many
|
||||
$joinField = "{$remoteRelation}ID";
|
||||
$componentClass = static::getSchema()->classForField($remoteClass, $joinField);
|
||||
$componentClass = $schema->classForField($remoteClass, $joinField);
|
||||
$result = HasManyList::create($componentClass, $joinField);
|
||||
if ($this->model) {
|
||||
$result->setDataModel($this->model);
|
||||
@ -1749,7 +1686,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
case 'belongs_to':
|
||||
case 'has_many': {
|
||||
// These relations must have a has_one on the other end, so find it
|
||||
$joinField = $remote->getRemoteJoinField($remoteRelation, $relationType, $polymorphic);
|
||||
$joinField = $schema->getRemoteJoinField($remoteClass, $remoteRelation, $relationType, $polymorphic);
|
||||
if ($polymorphic) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
"%s cannot generate opposite component of relation %s.%s, as the other end appears" .
|
||||
@ -1773,8 +1710,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
case 'belongs_many_many': {
|
||||
// Get components and extra fields from parent
|
||||
list($relationClass, $componentClass, $parentClass, $componentField, $parentField, $table)
|
||||
= $remote->manyManyComponent($remoteRelation);
|
||||
$extraFields = $remote->manyManyExtraFieldsForComponent($remoteRelation) ?: array();
|
||||
= $remote->getSchema()->manyManyComponent($remoteClass, $remoteRelation);
|
||||
$extraFields = $schema->manyManyExtraFieldsForComponent($remoteClass, $remoteRelation) ?: array();
|
||||
|
||||
// Reverse parent and component fields and create an inverse ManyManyList
|
||||
/** @var RelationList $result */
|
||||
@ -1798,33 +1735,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find the database key on another object that is used to store a
|
||||
* relationship to this class. If no join field can be found it defaults to 'ParentID'.
|
||||
*
|
||||
* If the remote field is polymorphic then $polymorphic is set to true, and the return value
|
||||
* is in the form 'Relation' instead of 'RelationID', referencing the composite DBField.
|
||||
*
|
||||
* @param string $component Name of the relation on the current object pointing to the
|
||||
* remote object.
|
||||
* @param string $type the join type - either 'has_many' or 'belongs_to'
|
||||
* @param boolean $polymorphic Flag set to true if the remote join field is polymorphic.
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getRemoteJoinField($component, $type = 'has_many', &$polymorphic = false) {
|
||||
return $this
|
||||
->getSchema()
|
||||
->getRemoteJoinField(get_class($this), $component, $type, $polymorphic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a many-to-many component, as a ManyManyList.
|
||||
* @param string $componentName Name of the many-many component
|
||||
* @return RelationList|UnsavedRelationList The set of components
|
||||
*/
|
||||
public function getManyManyComponents($componentName) {
|
||||
$manyManyComponent = $this->manyManyComponent($componentName);
|
||||
$schema = static::getSchema();
|
||||
$manyManyComponent = $schema->manyManyComponent(static::class, $componentName);
|
||||
if(!$manyManyComponent) {
|
||||
throw new InvalidArgumentException(sprintf(
|
||||
"DataObject::getComponents(): Unknown many-to-many component '%s' on class '%s'",
|
||||
@ -1845,7 +1763,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $this->unsavedRelations[$componentName];
|
||||
}
|
||||
|
||||
$extraFields = $this->manyManyExtraFieldsForComponent($componentName) ?: array();
|
||||
$extraFields = $schema->manyManyExtraFieldsForComponent(static::class, $componentName) ?: array();
|
||||
/** @var RelationList $result */
|
||||
$result = Injector::inst()->create(
|
||||
$relationClass,
|
||||
@ -1887,15 +1805,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return (array)Config::inst()->get($this->class, 'has_one', Config::INHERITED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data for a specific has_one component.
|
||||
* @param string $component
|
||||
* @return string|null
|
||||
*/
|
||||
public function hasOneComponent($component) {
|
||||
return $this->getSchema()->hasOneComponent(get_class($this), $component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the class of a remote belongs_to relationship. If no component is specified a map of all components and
|
||||
* their class name will be returned.
|
||||
@ -1913,35 +1822,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data for a specific belongs_to component.
|
||||
* @param string $component
|
||||
* @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have
|
||||
* the field data stripped off. It defaults to TRUE.
|
||||
* @return string|null
|
||||
*/
|
||||
public function belongsToComponent($component, $classOnly = true) {
|
||||
return $this->getSchema()->belongsToComponent(get_class($this), $component, $classOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the database fields in this object
|
||||
*
|
||||
* @param string $fieldName Limit the output to a specific field name
|
||||
* @param bool $includeClass If returning a single column, prefix the column with the class name
|
||||
* in Table.Column(spec) format
|
||||
* @return array|string|null The database fields, or if searching a single field,
|
||||
* just this one field if found. Field will be a string in FieldClass(args)
|
||||
* format, or RecordClass.FieldClass(args) format if $includeClass is true
|
||||
*/
|
||||
public function db($fieldName = null, $includeClass = false) {
|
||||
if ($fieldName) {
|
||||
return static::getSchema()->fieldSpecification(static::class, $fieldName, $includeClass);
|
||||
} else {
|
||||
return static::getSchema()->fieldSpecifications(static::class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the class of a one-to-many relationship. If no $component is specified then an array of all the one-to-many
|
||||
* relationships and their classes will be returned.
|
||||
@ -1959,17 +1839,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data for a specific has_many component.
|
||||
* @param string $component
|
||||
* @param bool $classOnly If this is TRUE, than any has_many relationships in the form "ClassName.Field" will have
|
||||
* the field data stripped off. It defaults to TRUE.
|
||||
* @return string|null
|
||||
*/
|
||||
public function hasManyComponent($component, $classOnly = true) {
|
||||
return $this->getSchema()->hasManyComponent(get_class($this), $component, $classOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the many-to-many extra fields specification.
|
||||
*
|
||||
@ -1982,58 +1851,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return Config::inst()->get($this->class, 'many_many_extraFields', Config::INHERITED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the many-to-many extra fields specification for a specific component.
|
||||
* @param string $component
|
||||
* @return array|null
|
||||
*/
|
||||
public function manyManyExtraFieldsForComponent($component) {
|
||||
// Get all many_many_extraFields defined in this class or parent classes
|
||||
$extraFields = (array)Config::inst()->get($this->class, 'many_many_extraFields', Config::INHERITED);
|
||||
// Extra fields are immediately available
|
||||
if(isset($extraFields[$component])) {
|
||||
return $extraFields[$component];
|
||||
}
|
||||
|
||||
// Check this class' belongs_many_manys to see if any of their reverse associations contain extra fields
|
||||
$manyMany = (array)Config::inst()->get($this->class, 'belongs_many_many', Config::INHERITED);
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$relationName = null;
|
||||
// Extract class and relation name from dot-notation
|
||||
if(strpos($candidate, '.') !== false) {
|
||||
list($candidate, $relationName) = explode('.', $candidate, 2);
|
||||
}
|
||||
|
||||
// If we've not already found the relation name from dot notation, we need to find a relation that points
|
||||
// back to this class. As there's no dot-notation, there can only be one relation pointing to this class,
|
||||
// so it's safe to assume that it's the correct one
|
||||
if(!$relationName) {
|
||||
$candidateManyManys = (array)Config::inst()->get($candidate, 'many_many', Config::UNINHERITED);
|
||||
|
||||
foreach($candidateManyManys as $relation => $relatedClass) {
|
||||
if (is_a($this, $relatedClass)) {
|
||||
$relationName = $relation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we've found a matching relation on the target class, see if we can find extra fields for it
|
||||
$extraFields = (array)Config::inst()->get($candidate, 'many_many_extraFields', Config::UNINHERITED);
|
||||
if(isset($extraFields[$relationName])) {
|
||||
return $extraFields[$relationName];
|
||||
}
|
||||
}
|
||||
|
||||
return isset($items) ? $items : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about a many-to-many component.
|
||||
* The return value is an array of (parentclass, childclass). If $component is null, then all many-many
|
||||
* components are returned.
|
||||
*
|
||||
* @see DataObject::manyManyComponent()
|
||||
* @see DataObjectSchema::manyManyComponent()
|
||||
* @return array|null An array of (parentclass, childclass), or an array of all many-many components
|
||||
*/
|
||||
public function manyMany() {
|
||||
@ -2043,30 +1866,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about a specific many_many component. Returns a numeric array.
|
||||
* The first item in the array will be the class name of the relation: either
|
||||
* ManyManyList or ManyManyThroughList.
|
||||
*
|
||||
* Standard many_many return type is:
|
||||
*
|
||||
* array(
|
||||
* <manyManyClass>, Name of class for relation
|
||||
* <classname>, The class that relation is defined in e.g. "Product"
|
||||
* <candidateName>, The target class of the relation e.g. "Category"
|
||||
* <parentField>, The field name pointing to <classname>'s table e.g. "ProductID"
|
||||
* <childField>, The field name pointing to <candidatename>'s table e.g. "CategoryID"
|
||||
* <joinTableOrRelation> The join table between the two classes e.g. "Product_Categories".
|
||||
* If the class name is 'ManyManyThroughList' then this is the name of the
|
||||
* has_many relation.
|
||||
* )
|
||||
* @param string $component The component name
|
||||
* @return array|null
|
||||
*/
|
||||
public function manyManyComponent($component) {
|
||||
return $this->getSchema()->manyManyComponent(get_class($this), $component);
|
||||
}
|
||||
|
||||
/**
|
||||
* This returns an array (if it exists) describing the database extensions that are required, or false if none
|
||||
*
|
||||
@ -2284,7 +2083,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Called by {@link __get()} and any getFieldName() methods you might create.
|
||||
*
|
||||
* @param string $field The name of the field
|
||||
*
|
||||
* @return mixed The field value
|
||||
*/
|
||||
public function getField($field) {
|
||||
@ -2300,7 +2098,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
|
||||
// In case of complex fields, return the DBField object
|
||||
if(self::is_composite_field($this->class, $field)) {
|
||||
if (static::getSchema()->compositeField(static::class, $field)) {
|
||||
$this->record[$field] = $this->dbObject($field);
|
||||
}
|
||||
|
||||
@ -2343,7 +2141,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// Limit query to the current record, unless it has the Versioned extension,
|
||||
// in which case it requires special handling through augmentLoadLazyFields()
|
||||
$baseIDColumn = static::getSchema()->sqlColumnForField($this, 'ID');
|
||||
$schema = static::getSchema();
|
||||
$baseIDColumn = $schema->sqlColumnForField($this, 'ID');
|
||||
$dataQuery->where([
|
||||
$baseIDColumn => $this->record['ID']
|
||||
])->limit(1);
|
||||
@ -2352,8 +2151,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// Add SQL for fields, both simple & multi-value
|
||||
// TODO: This is copy & pasted from buildSQL(), it could be moved into a method
|
||||
$databaseFields = self::database_fields($class);
|
||||
if($databaseFields) foreach($databaseFields as $k => $v) {
|
||||
$databaseFields = $schema->databaseFields($class, false);
|
||||
foreach($databaseFields as $k => $v) {
|
||||
if(!isset($this->record[$k]) || $this->record[$k] === null) {
|
||||
$columns[] = $k;
|
||||
}
|
||||
@ -2426,7 +2225,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(is_array($databaseFieldsOnly)) {
|
||||
$fields = array_intersect_key((array)$this->changed, array_flip($databaseFieldsOnly));
|
||||
} elseif($databaseFieldsOnly) {
|
||||
$fields = array_intersect_key((array)$this->changed, $this->db());
|
||||
$fieldsSpecs = static::getSchema()->fieldSpecs(static::class);
|
||||
$fields = array_intersect_key((array)$this->changed, $fieldsSpecs);
|
||||
} else {
|
||||
$fields = $this->changed;
|
||||
}
|
||||
@ -2505,8 +2305,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// Situation 2: Passing a literal or non-DBField object
|
||||
} else {
|
||||
// If this is a proper database field, we shouldn't be getting non-DBField objects
|
||||
if(is_object($val) && $this->db($fieldName)) {
|
||||
user_error('DataObject::setField: passed an object that is not a DBField', E_USER_WARNING);
|
||||
if(is_object($val) && static::getSchema()->fieldSpec(static::class, $fieldName)) {
|
||||
throw new InvalidArgumentException('DataObject::setField: passed an object that is not a DBField');
|
||||
}
|
||||
|
||||
// if a field is not existing or has strictly changed
|
||||
@ -2516,9 +2316,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// At the very least, the type has changed
|
||||
$this->changed[$fieldName] = self::CHANGE_STRICT;
|
||||
|
||||
if((!isset($this->record[$fieldName]) && $val) || (isset($this->record[$fieldName])
|
||||
&& $this->record[$fieldName] != $val)) {
|
||||
|
||||
if ((!isset($this->record[$fieldName]) && $val)
|
||||
|| (isset($this->record[$fieldName]) && $this->record[$fieldName] != $val)
|
||||
) {
|
||||
// Value has changed as well, not just the type
|
||||
$this->changed[$fieldName] = self::CHANGE_VALUE;
|
||||
}
|
||||
@ -2558,7 +2358,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function castingHelper($field) {
|
||||
if ($fieldSpec = $this->db($field)) {
|
||||
$fieldSpec = static::getSchema()->fieldSpec(static::class, $field);
|
||||
if ($fieldSpec) {
|
||||
return $fieldSpec;
|
||||
}
|
||||
|
||||
@ -2585,10 +2386,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return boolean True if the given field exists
|
||||
*/
|
||||
public function hasField($field) {
|
||||
$schema = static::getSchema();
|
||||
return (
|
||||
array_key_exists($field, $this->record)
|
||||
|| $this->db($field)
|
||||
|| (substr($field,-2) == 'ID') && $this->hasOneComponent(substr($field,0, -2))
|
||||
|| $schema->fieldSpec(static::class, $field)
|
||||
|| (substr($field,-2) == 'ID') && $schema->hasOneComponent(static::class, substr($field,0, -2))
|
||||
|| $this->hasMethod("get{$field}")
|
||||
);
|
||||
}
|
||||
@ -2601,57 +2403,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasDatabaseField($field) {
|
||||
return $this->db($field)
|
||||
&& ! self::is_composite_field(get_class($this), $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field type of the given field, if it belongs to this class, and not a parent.
|
||||
* Note that the field type will not include constructor arguments in round brackets, only the classname.
|
||||
*
|
||||
* @param string $field Name of the field
|
||||
* @return string The field type of the given field
|
||||
*/
|
||||
public function hasOwnTableDatabaseField($field) {
|
||||
return self::has_own_table_database_field($this->class, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field type of the given field, if it belongs to this class, and not a parent.
|
||||
* Note that the field type will not include constructor arguments in round brackets, only the classname.
|
||||
*
|
||||
* @param string $class Class name to check
|
||||
* @param string $field Name of the field
|
||||
* @return string The field type of the given field
|
||||
*/
|
||||
public static function has_own_table_database_field($class, $field) {
|
||||
$fieldMap = self::database_fields($class);
|
||||
|
||||
// Remove string-based "constructor-arguments" from the DBField definition
|
||||
if(isset($fieldMap[$field])) {
|
||||
$spec = $fieldMap[$field];
|
||||
if(is_string($spec)) {
|
||||
return strtok($spec,'(');
|
||||
} else {
|
||||
return $spec['type'];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if given class has its own table. Uses the rules for whether the table should exist rather than
|
||||
* actually looking in the database.
|
||||
*
|
||||
* @param string $dataClass
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_own_table($dataClass) {
|
||||
if(!is_subclass_of($dataClass, 'SilverStripe\ORM\DataObject')) {
|
||||
return false;
|
||||
}
|
||||
$fields = static::database_fields($dataClass);
|
||||
return !empty($fields);
|
||||
$spec = static::getSchema()->fieldSpec(static::class, $field, ['dbOnly']);
|
||||
return !empty($spec);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2671,7 +2424,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
if(Permission::checkMember($member, "ADMIN")) return true;
|
||||
|
||||
if($this->manyManyComponent('Can' . $perm)) {
|
||||
if($this->getSchema()->manyManyComponent(static::class, 'Can' . $perm)) {
|
||||
if($this->ParentID && $this->SecurityType == 'Inherit') {
|
||||
if(!($p = $this->Parent)) {
|
||||
return false;
|
||||
@ -2834,8 +2587,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function dbObject($fieldName) {
|
||||
// Check for field in DB
|
||||
$helper = $this->db($fieldName, true);
|
||||
|
||||
$helper = static::getSchema()->fieldSpec(static::class, $fieldName, ['includeClass']);
|
||||
if(!$helper) {
|
||||
return null;
|
||||
}
|
||||
@ -2983,15 +2735,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return DataList The objects matching the filter, in the class specified by $containerClass
|
||||
*/
|
||||
public static function get($callerClass = null, $filter = "", $sort = "", $join = "", $limit = null,
|
||||
$containerClass = 'SilverStripe\ORM\DataList') {
|
||||
$containerClass = DataList::class) {
|
||||
|
||||
if($callerClass == null) {
|
||||
$callerClass = get_called_class();
|
||||
if($callerClass == 'SilverStripe\ORM\DataObject') {
|
||||
if ($callerClass == self::class) {
|
||||
throw new \InvalidArgumentException('Call <classname>::get() instead of DataObject::get()');
|
||||
}
|
||||
|
||||
if($filter || $sort || $join || $limit || ($containerClass != 'SilverStripe\ORM\DataList')) {
|
||||
if($filter || $sort || $join || $limit || ($containerClass != DataList::class)) {
|
||||
throw new \InvalidArgumentException('If calling <classname>::get() then you shouldn\'t pass any other'
|
||||
. ' arguments');
|
||||
}
|
||||
@ -3070,7 +2822,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return DataObject $this
|
||||
*/
|
||||
public function flushCache($persistent = true) {
|
||||
if($this->class == __CLASS__) {
|
||||
if($this->class == self::class) {
|
||||
self::$_cache_get_one = array();
|
||||
return $this;
|
||||
}
|
||||
@ -3108,7 +2860,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
DBClassName::clear_classname_cache();
|
||||
ClassInfo::reset_db_cache();
|
||||
static::getSchema()->reset();
|
||||
self::$_cache_has_own_table = array();
|
||||
self::$_cache_get_one = array();
|
||||
self::$_cache_field_labels = array();
|
||||
}
|
||||
@ -3231,7 +2982,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
|
||||
if(get_parent_class($this) == 'SilverStripe\ORM\DataObject') {
|
||||
if(get_parent_class($this) == self::class) {
|
||||
$indexes['ClassName'] = true;
|
||||
}
|
||||
|
||||
@ -3245,14 +2996,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function requireTable() {
|
||||
// Only build the table if we've actually got fields
|
||||
$fields = self::database_fields($this->class);
|
||||
$table = static::getSchema()->tableName($this->class);
|
||||
$extensions = self::database_extensions($this->class);
|
||||
$schema = static::getSchema();
|
||||
$fields = $schema->databaseFields(static::class, false);
|
||||
$table = $schema->tableName(static::class);
|
||||
$extensions = self::database_extensions(static::class);
|
||||
|
||||
$indexes = $this->databaseIndexes();
|
||||
|
||||
if($fields) {
|
||||
$hasAutoIncPK = get_parent_class($this) === __CLASS__;
|
||||
$hasAutoIncPK = get_parent_class($this) === self::class;
|
||||
DB::require_table(
|
||||
$table, $fields, $indexes, $hasAutoIncPK, $this->stat('create_table_options'), $extensions
|
||||
);
|
||||
@ -3265,7 +3017,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$extras = $this->uninherited('many_many_extraFields');
|
||||
foreach($manyMany as $component => $spec) {
|
||||
// Get many_many spec
|
||||
$manyManyComponent = $this->getSchema()->manyManyComponent(get_class($this), $component);
|
||||
$manyManyComponent = $schema->manyManyComponent(static::class, $component);
|
||||
list(
|
||||
$relationClass, $parentClass, $componentClass,
|
||||
$parentField, $childField, $tableOrClass
|
||||
@ -3343,6 +3095,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$fields = array();
|
||||
|
||||
// remove the custom getters as the search should not include them
|
||||
$schema = static::getSchema();
|
||||
if($summaryFields) {
|
||||
foreach($summaryFields as $key => $name) {
|
||||
$spec = $name;
|
||||
@ -3352,9 +3105,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$name = substr($name, 0, $fieldPos);
|
||||
}
|
||||
|
||||
if($this->hasDatabaseField($name)) {
|
||||
if ($schema->fieldSpec($this, $name)) {
|
||||
$fields[] = $name;
|
||||
} elseif($this->relObject($spec)) {
|
||||
} elseif ($this->relObject($spec)) {
|
||||
$fields[] = $spec;
|
||||
}
|
||||
}
|
||||
@ -3441,7 +3194,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$ancestry = ClassInfo::ancestry($this->class);
|
||||
$ancestry = array_reverse($ancestry);
|
||||
if($ancestry) foreach($ancestry as $ancestorClass) {
|
||||
if($ancestorClass == 'SilverStripe\\View\\ViewableData') break;
|
||||
if($ancestorClass === ViewableData::class) {
|
||||
break;
|
||||
}
|
||||
$types = array(
|
||||
'db' => (array)Config::inst()->get($ancestorClass, 'db', Config::UNINHERITED)
|
||||
);
|
||||
@ -3500,10 +3255,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if (!$fields) {
|
||||
$fields = array();
|
||||
// try to scaffold a couple of usual suspects
|
||||
if ($this->hasField('Name')) $fields['Name'] = 'Name';
|
||||
if ($this->hasDatabaseField('Title')) $fields['Title'] = 'Title';
|
||||
if ($this->hasField('Description')) $fields['Description'] = 'Description';
|
||||
if ($this->hasField('FirstName')) $fields['FirstName'] = 'First Name';
|
||||
if ($this->hasField('Name')) {
|
||||
$fields['Name'] = 'Name';
|
||||
}
|
||||
if (static::getSchema()->fieldSpec($this, 'Title')) {
|
||||
$fields['Title'] = 'Title';
|
||||
}
|
||||
if ($this->hasField('Description')) {
|
||||
$fields['Description'] = 'Description';
|
||||
}
|
||||
if ($this->hasField('FirstName')) {
|
||||
$fields['FirstName'] = 'First Name';
|
||||
}
|
||||
}
|
||||
$this->extend("updateSummaryFields", $fields);
|
||||
|
||||
@ -3825,7 +3588,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function hasValue($field, $arguments = null, $cache = true) {
|
||||
// has_one fields should not use dbObject to check if a value is given
|
||||
if(!$this->hasOneComponent($field) && ($obj = $this->dbObject($field))) {
|
||||
$hasOne = static::getSchema()->hasOneComponent(static::class, $field);
|
||||
if(!$hasOne && ($obj = $this->dbObject($field))) {
|
||||
return $obj->exists();
|
||||
} else {
|
||||
return parent::hasValue($field, $arguments, $cache);
|
||||
@ -3851,7 +3615,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
public function setJoin(DataObject $object, $alias = null) {
|
||||
$this->joinRecord = $object;
|
||||
if ($alias) {
|
||||
if ($this->db($alias)) {
|
||||
if (static::getSchema()->fieldSpec(static::class, $alias)) {
|
||||
throw new InvalidArgumentException(
|
||||
"Joined record $alias cannot also be a db field"
|
||||
);
|
||||
|
@ -140,17 +140,37 @@ class DataObjectSchema {
|
||||
/**
|
||||
* Get all DB field specifications for a class, including ancestors and composite fields.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string|DataObject $classOrInstance
|
||||
* @param array $options Array of options. Specify any number of the below:
|
||||
* - `uninherited`: Set to true to limit to only this table
|
||||
* - `dbOnly`: Exclude virtual fields (such as composite fields), and only include fields with a db column.
|
||||
* - `includeClass`: If true prefix the field specification with the class name in RecordClass.Column(spec) format.
|
||||
* @return array
|
||||
*/
|
||||
public function fieldSpecifications($class) {
|
||||
$classes = ClassInfo::ancestry($class, true);
|
||||
public function fieldSpecs($classOrInstance, $options = []) {
|
||||
$class = ClassInfo::class_name($classOrInstance);
|
||||
$uninherited = !empty($options['uninherited']) || in_array('uninherited', $options);
|
||||
$dbOnly = !empty($options['dbOnly']) || in_array('dbOnly', $options);
|
||||
$includeClass = !empty($options['includeClass']) || in_array('includeClass', $options);
|
||||
|
||||
// Walk class hierarchy
|
||||
$db = [];
|
||||
$classes = $uninherited ? [$class] : ClassInfo::ancestry($class);
|
||||
foreach($classes as $tableClass) {
|
||||
// Merge fields with new fields and composite fields
|
||||
$fields = $this->databaseFields($tableClass);
|
||||
$compositeFields = $this->compositeFields($tableClass, false);
|
||||
$db = array_merge($db, $fields, $compositeFields);
|
||||
// Find all fields on this class
|
||||
$fields = $this->databaseFields($tableClass, false);
|
||||
|
||||
// Merge with composite fields
|
||||
if (!$dbOnly) {
|
||||
$compositeFields = $this->compositeFields($tableClass, false);
|
||||
$fields = array_merge($fields, $compositeFields);
|
||||
}
|
||||
|
||||
// Record specification
|
||||
foreach ($fields as $name => $specification) {
|
||||
$prefix = $includeClass ? "{$tableClass}." : "";
|
||||
$db[$name] = $prefix . $specification;
|
||||
}
|
||||
}
|
||||
return $db;
|
||||
}
|
||||
@ -159,30 +179,18 @@ class DataObjectSchema {
|
||||
/**
|
||||
* Get specifications for a single class field
|
||||
*
|
||||
* @param string $class
|
||||
* @param string|DataObject $classOrInstance Name or instance of class
|
||||
* @param string $fieldName
|
||||
* @param bool $includeClass If returning a single column, prefix the column with the class name
|
||||
* in RecordClass.Column(spec) format
|
||||
* @param array $options Array of options. Specify any number of the below:
|
||||
* - `uninherited`: Set to true to limit to only this table
|
||||
* - `dbOnly`: Exclude virtual fields (such as composite fields), and only include fields with a db column.
|
||||
* - `includeClass`: If true prefix the field specification with the class name in RecordClass.Column(spec) format.
|
||||
* @return string|null Field will be a string in FieldClass(args) format, or
|
||||
* RecordClass.FieldClass(args) format if $includeClass is true. Will be null if no field is found.
|
||||
*/
|
||||
public function fieldSpecification($class, $fieldName, $includeClass = false) {
|
||||
$classes = array_reverse(ClassInfo::ancestry($class, true));
|
||||
foreach($classes as $tableClass) {
|
||||
// Merge fields with new fields and composite fields
|
||||
$fields = $this->databaseFields($tableClass);
|
||||
$compositeFields = $this->compositeFields($tableClass, false);
|
||||
$db = array_merge($fields, $compositeFields);
|
||||
|
||||
// Check for search field
|
||||
if(isset($db[$fieldName])) {
|
||||
$prefix = $includeClass ? "{$tableClass}." : "";
|
||||
return $prefix . $db[$fieldName];
|
||||
}
|
||||
}
|
||||
|
||||
// At end of search complete
|
||||
return null;
|
||||
public function fieldSpec($classOrInstance, $fieldName, $options = []) {
|
||||
$specs = $this->fieldSpecs($classOrInstance, $options);
|
||||
return isset($specs[$fieldName]) ? $specs[$fieldName] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -249,7 +257,7 @@ class DataObjectSchema {
|
||||
|
||||
// Generate default table name
|
||||
if(!$table) {
|
||||
$separator = $this->config()->table_namespace_separator;
|
||||
$separator = $this->config()->get('table_namespace_separator');
|
||||
$table = str_replace('\\', $separator, trim($class, '\\'));
|
||||
}
|
||||
|
||||
@ -261,15 +269,48 @@ class DataObjectSchema {
|
||||
* "ID" will be included on every table.
|
||||
*
|
||||
* @param string $class Class name to query from
|
||||
* @param bool $aggregated Include fields in entire hierarchy, rather than just on this table
|
||||
* @return array Map of fieldname to specification, similiar to {@link DataObject::$db}.
|
||||
*/
|
||||
public function databaseFields($class) {
|
||||
public function databaseFields($class, $aggregated = true) {
|
||||
$class = ClassInfo::class_name($class);
|
||||
if($class === DataObject::class) {
|
||||
return [];
|
||||
}
|
||||
$this->cacheDatabaseFields($class);
|
||||
return $this->databaseFields[$class];
|
||||
$fields = $this->databaseFields[$class];
|
||||
|
||||
if (!$aggregated) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
// Recursively merge
|
||||
$parentFields = $this->databaseFields(get_parent_class($class));
|
||||
return array_merge($fields, array_diff_key($parentFields, $fields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single database field.
|
||||
*
|
||||
* @param string $class Class name to query from
|
||||
* @param string $field Field name
|
||||
* @param bool $aggregated Include fields in entire hierarchy, rather than just on this table
|
||||
* @return string|null Field specification, or null if not a field
|
||||
*/
|
||||
public function databaseField($class, $field, $aggregated = true) {
|
||||
$fields = $this->databaseFields($class, $aggregated);
|
||||
return isset($fields[$field]) ? $fields[$field] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given class has a table
|
||||
*
|
||||
* @param string $class
|
||||
* @return bool
|
||||
*/
|
||||
public function classHasTable($class) {
|
||||
$fields = $this->databaseFields($class, false);
|
||||
return !empty($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -300,7 +341,20 @@ class DataObjectSchema {
|
||||
|
||||
// Recursively merge
|
||||
$parentFields = $this->compositeFields(get_parent_class($class));
|
||||
return array_merge($compositeFields, $parentFields);
|
||||
return array_merge($compositeFields, array_diff_key($parentFields, $compositeFields));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a composite field for a class
|
||||
*
|
||||
* @param string $class Class name to query from
|
||||
* @param string $field Field name
|
||||
* @param bool $aggregated Include fields in entire hierarchy, rather than just on this table
|
||||
* @return string|null Field specification, or null if not a field
|
||||
*/
|
||||
public function compositeField($class, $field, $aggregated = true) {
|
||||
$fields = $this->compositeFields($class, $aggregated);
|
||||
return isset($fields[$field]) ? $fields[$field] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -318,7 +372,7 @@ class DataObjectSchema {
|
||||
$dbFields = array();
|
||||
|
||||
// Ensure fixed fields appear at the start
|
||||
$fixedFields = DataObject::config()->fixed_fields;
|
||||
$fixedFields = DataObject::config()->get('fixed_fields');
|
||||
if(get_parent_class($class) === DataObject::class) {
|
||||
// Merge fixed with ClassName spec and custom db fields
|
||||
$dbFields = $fixedFields;
|
||||
@ -357,7 +411,7 @@ class DataObjectSchema {
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent field-less tables
|
||||
// Prevent field-less tables with only 'ID'
|
||||
if(count($dbFields) < 2) {
|
||||
$dbFields = [];
|
||||
}
|
||||
@ -401,14 +455,14 @@ class DataObjectSchema {
|
||||
}
|
||||
|
||||
// Short circuit for fixed fields
|
||||
$fixed = DataObject::config()->fixed_fields;
|
||||
$fixed = DataObject::config()->get('fixed_fields');
|
||||
if(isset($fixed[$fieldName])) {
|
||||
return $this->baseDataClass($candidateClass);
|
||||
}
|
||||
|
||||
// Find regular field
|
||||
while($candidateClass) {
|
||||
$fields = $this->databaseFields($candidateClass);
|
||||
$fields = $this->databaseFields($candidateClass, false);
|
||||
if(isset($fields[$fieldName])) {
|
||||
return $candidateClass;
|
||||
}
|
||||
@ -425,7 +479,7 @@ class DataObjectSchema {
|
||||
* Standard many_many return type is:
|
||||
*
|
||||
* array(
|
||||
* <manyManyClass>, Name of class for relation
|
||||
* <manyManyClass>, Name of class for relation. E.g. "Categories"
|
||||
* <classname>, The class that relation is defined in e.g. "Product"
|
||||
* <candidateName>, The target class of the relation e.g. "Category"
|
||||
* <parentField>, The field name pointing to <classname>'s table e.g. "ProductID".
|
||||
@ -449,35 +503,85 @@ class DataObjectSchema {
|
||||
|
||||
// Check if the component is defined in belongs_many_many on this class
|
||||
$belongsManyMany = Config::inst()->get($parentClass, 'belongs_many_many', Config::UNINHERITED);
|
||||
if(!isset($belongsManyMany[$component])) {
|
||||
if (!isset($belongsManyMany[$component])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Extract class and relation name from dot-notation
|
||||
$childClass = $belongsManyMany[$component];
|
||||
$relationName = null;
|
||||
if(strpos($childClass, '.') !== false) {
|
||||
list($childClass, $relationName) = explode('.', $childClass, 2);
|
||||
}
|
||||
|
||||
// We need to find the inverse component name, if not explicitly given
|
||||
if (!$relationName) {
|
||||
$relationName = $this->getManyManyInverseRelationship($childClass, $parentClass);
|
||||
}
|
||||
|
||||
// Check valid relation found
|
||||
if (!$relationName) {
|
||||
throw new LogicException("Inverse component of $childClass not found ({$class})");
|
||||
}
|
||||
list($childClass, $relationName)
|
||||
= $this->parseBelongsManyManyComponent($parentClass, $component, $belongsManyMany[$component]);
|
||||
|
||||
// Build inverse relationship from other many_many, and swap parent/child
|
||||
list($relationClass, $childClass, $parentClass, $childField, $parentField, $joinTable)
|
||||
= $this->parseManyManyComponent($childClass, $relationName, $parentClass);
|
||||
= $this->manyManyComponent($childClass, $relationName);
|
||||
return [$relationClass, $parentClass, $childClass, $parentField, $childField, $joinTable];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Parse a belongs_many_many component to extract class and relationship name
|
||||
*
|
||||
* @param string $parentClass Name of class
|
||||
* @param string $component Name of relation on class
|
||||
* @param string $specification specification for this belongs_many_many
|
||||
* @return array Array with child class and relation name
|
||||
*/
|
||||
protected function parseBelongsManyManyComponent($parentClass, $component, $specification)
|
||||
{
|
||||
$childClass = $specification;
|
||||
$relationName = null;
|
||||
if (strpos($specification, '.') !== false) {
|
||||
list($childClass, $relationName) = explode('.', $specification, 2);
|
||||
}
|
||||
|
||||
// We need to find the inverse component name, if not explicitly given
|
||||
if (!$relationName) {
|
||||
$relationName = $this->getManyManyInverseRelationship($childClass, $parentClass);
|
||||
}
|
||||
|
||||
// Check valid relation found
|
||||
if (!$relationName) {
|
||||
throw new LogicException(
|
||||
"belongs_many_many relation {$parentClass}.{$component} points to "
|
||||
. "{$specification} without matching many_many"
|
||||
);
|
||||
}
|
||||
|
||||
// Return relatios
|
||||
return array($childClass, $relationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the many-to-many extra fields specification for a specific component.
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $component
|
||||
* @return array|null
|
||||
*/
|
||||
public function manyManyExtraFieldsForComponent($class, $component) {
|
||||
// Get directly declared many_many_extraFields
|
||||
$extraFields = Config::inst()->get($class, 'many_many_extraFields');
|
||||
if (isset($extraFields[$component])) {
|
||||
return $extraFields[$component];
|
||||
}
|
||||
|
||||
// If not belongs_many_many then there are no components
|
||||
while ($class && ($class !== DataObject::class)) {
|
||||
$belongsManyMany = Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
|
||||
if (isset($belongsManyMany[$component])) {
|
||||
// Reverse relationship and find extrafields from child class
|
||||
list($childClass, $relationName) = $this->parseBelongsManyManyComponent($class, $component,
|
||||
$belongsManyMany[$component]);
|
||||
return $this->manyManyExtraFieldsForComponent($childClass, $relationName);
|
||||
}
|
||||
$class = get_parent_class($class);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return data for a specific has_many component.
|
||||
*
|
||||
@ -739,7 +843,7 @@ class DataObjectSchema {
|
||||
|
||||
// Validate the join class isn't also the name of a field or relation on either side
|
||||
// of the relation
|
||||
$field = $this->fieldSpecification($relationClass, $joinClass);
|
||||
$field = $this->fieldSpec($relationClass, $joinClass);
|
||||
if ($field) {
|
||||
throw new InvalidArgumentException(
|
||||
"many_many through relation {$parentClass}.{$component} {$key} class {$relationClass} "
|
||||
|
@ -240,7 +240,7 @@ class DataQuery {
|
||||
$selectColumns = null;
|
||||
if ($queriedColumns) {
|
||||
// Restrict queried columns to that on the selected table
|
||||
$tableFields = DataObject::database_fields($tableClass);
|
||||
$tableFields = $schema->databaseFields($tableClass, false);
|
||||
unset($tableFields['ID']);
|
||||
$selectColumns = array_intersect($queriedColumns, array_keys($tableFields));
|
||||
}
|
||||
@ -508,14 +508,15 @@ class DataQuery {
|
||||
*/
|
||||
protected function selectColumnsFromTable(SQLSelect &$query, $tableClass, $columns = null) {
|
||||
// Add SQL for multi-value fields
|
||||
$databaseFields = DataObject::database_fields($tableClass);
|
||||
$compositeFields = DataObject::composite_fields($tableClass, false);
|
||||
$schema = DataObject::getSchema();
|
||||
$databaseFields = $schema->databaseFields($tableClass, false);
|
||||
$compositeFields = $schema->compositeFields($tableClass, false);
|
||||
unset($databaseFields['ID']);
|
||||
foreach($databaseFields as $k => $v) {
|
||||
if((is_null($columns) || in_array($k, $columns)) && !isset($compositeFields[$k])) {
|
||||
// Update $collidingFields if necessary
|
||||
$expressionForField = $query->expressionForField($k);
|
||||
$quotedField = DataObject::getSchema()->sqlColumnForField($tableClass, $k);
|
||||
$quotedField = $schema->sqlColumnForField($tableClass, $k);
|
||||
if($expressionForField) {
|
||||
if(!isset($this->collidingFields[$k])) {
|
||||
$this->collidingFields[$k] = array($expressionForField);
|
||||
@ -528,7 +529,7 @@ class DataQuery {
|
||||
}
|
||||
foreach($compositeFields as $k => $v) {
|
||||
if((is_null($columns) || in_array($k, $columns)) && $v) {
|
||||
$tableName = DataObject::getSchema()->tableName($tableClass);
|
||||
$tableName = $schema->tableName($tableClass);
|
||||
$dbO = Object::create_from_string($v, $k);
|
||||
$dbO->setTable($tableName);
|
||||
$dbO->addToQuery($query);
|
||||
@ -727,14 +728,14 @@ class DataQuery {
|
||||
|
||||
$modelClass = $this->dataClass;
|
||||
|
||||
$schema = DataObject::getSchema();
|
||||
foreach($relation as $rel) {
|
||||
$model = singleton($modelClass);
|
||||
if ($component = $model->hasOneComponent($rel)) {
|
||||
if ($component = $schema->hasOneComponent($modelClass, $rel)) {
|
||||
// Join via has_one
|
||||
$this->joinHasOneRelation($modelClass, $rel, $component);
|
||||
$modelClass = $component;
|
||||
|
||||
} elseif ($component = $model->hasManyComponent($rel)) {
|
||||
} elseif ($component = $schema->hasManyComponent($modelClass, $rel)) {
|
||||
// Fail on non-linear relations
|
||||
if($linearOnly) {
|
||||
throw new InvalidArgumentException("$rel is not a linear relation on model $modelClass");
|
||||
@ -743,7 +744,7 @@ class DataQuery {
|
||||
$this->joinHasManyRelation($modelClass, $rel, $component);
|
||||
$modelClass = $component;
|
||||
|
||||
} elseif ($component = $model->manyManyComponent($rel)) {
|
||||
} elseif ($component = $schema->manyManyComponent($modelClass, $rel)) {
|
||||
// Fail on non-linear relations
|
||||
if($linearOnly) {
|
||||
throw new InvalidArgumentException("$rel is not a linear relation on model $modelClass");
|
||||
@ -833,8 +834,7 @@ class DataQuery {
|
||||
|
||||
// Join table with associated has_one
|
||||
/** @var DataObject $model */
|
||||
$model = singleton($localClass);
|
||||
$foreignKey = $model->getRemoteJoinField($localField, 'has_many', $polymorphic);
|
||||
$foreignKey = $schema->getRemoteJoinField($localClass, $localField, 'has_many', $polymorphic);
|
||||
$localIDColumn = $schema->sqlColumnForField($localClass, 'ID');
|
||||
if($polymorphic) {
|
||||
$foreignKeyIDColumn = $schema->sqlColumnForField($foreignClass, "{$foreignKey}ID");
|
||||
|
@ -336,42 +336,46 @@ class DatabaseAdmin extends Controller {
|
||||
* corresponding records in their parent class tables.
|
||||
*/
|
||||
public function cleanup() {
|
||||
$allClasses = get_declared_classes();
|
||||
$baseClasses = [];
|
||||
foreach($allClasses as $class) {
|
||||
if(get_parent_class($class) == 'SilverStripe\ORM\DataObject') {
|
||||
foreach(ClassInfo::subclassesFor(DataObject::class) as $class) {
|
||||
if(get_parent_class($class) == DataObject::class) {
|
||||
$baseClasses[] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
$schema = DataObject::getSchema();
|
||||
foreach($baseClasses as $baseClass) {
|
||||
// Get data classes
|
||||
$baseTable = $schema->baseDataTable($baseClass);
|
||||
$subclasses = ClassInfo::subclassesFor($baseClass);
|
||||
unset($subclasses[0]);
|
||||
foreach($subclasses as $k => $subclass) {
|
||||
if(DataObject::has_own_table($subclass)) {
|
||||
if(!DataObject::getSchema()->classHasTable($subclass)) {
|
||||
unset($subclasses[$k]);
|
||||
}
|
||||
}
|
||||
|
||||
if($subclasses) {
|
||||
$records = DB::query("SELECT * FROM \"$baseClass\"");
|
||||
$records = DB::query("SELECT * FROM \"$baseTable\"");
|
||||
|
||||
|
||||
foreach($subclasses as $subclass) {
|
||||
$subclassTable = $schema->tableName($subclass);
|
||||
$recordExists[$subclass] =
|
||||
DB::query("SELECT \"ID\" FROM \"$subclass\"")->keyedColumn();
|
||||
DB::query("SELECT \"ID\" FROM \"$subclassTable\"")->keyedColumn();
|
||||
}
|
||||
|
||||
foreach($records as $record) {
|
||||
foreach($subclasses as $subclass) {
|
||||
$subclassTable = $schema->tableName($subclass);
|
||||
$id = $record['ID'];
|
||||
if(($record['ClassName'] != $subclass) &&
|
||||
(!is_subclass_of($record['ClassName'], $subclass)) &&
|
||||
(isset($recordExists[$subclass][$id]))) {
|
||||
$sql = "DELETE FROM \"$subclass\" WHERE \"ID\" = $record[ID]";
|
||||
echo "<li>$sql";
|
||||
DB::query($sql);
|
||||
if (($record['ClassName'] != $subclass)
|
||||
&& (!is_subclass_of($record['ClassName'], $subclass))
|
||||
&& isset($recordExists[$subclass][$id])
|
||||
) {
|
||||
$sql = "DELETE FROM \"$subclassTable\" WHERE \"ID\" = ?";
|
||||
echo "<li>$sql [{$id}]</li>";
|
||||
DB::prepared_query($sql, [$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ class DBForeignKey extends DBInt {
|
||||
return null;
|
||||
}
|
||||
$relationName = substr($this->name,0,-2);
|
||||
$hasOneClass = $this->object->hasOneComponent($relationName);
|
||||
$hasOneClass = DataObject::getSchema()->hasOneComponent(get_class($this->object), $relationName);
|
||||
if(empty($hasOneClass)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -736,7 +736,7 @@ class Hierarchy extends DataExtension {
|
||||
if ($hide_from_cms_tree && $this->showingCMSTree()) {
|
||||
$staged = $staged->exclude('ClassName', $hide_from_cms_tree);
|
||||
}
|
||||
if (!$showAll && $this->owner->db('ShowInMenus')) {
|
||||
if (!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
|
||||
$staged = $staged->filter('ShowInMenus', 1);
|
||||
}
|
||||
$this->owner->extend("augmentStageChildren", $staged, $showAll);
|
||||
@ -753,7 +753,7 @@ class Hierarchy extends DataExtension {
|
||||
* @throws Exception
|
||||
*/
|
||||
public function liveChildren($showAll = false, $onlyDeletedFromStage = false) {
|
||||
if(!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
|
||||
if(!$this->owner->hasExtension(Versioned::class)) {
|
||||
throw new Exception('Hierarchy->liveChildren() only works with Versioned extension applied');
|
||||
}
|
||||
|
||||
@ -773,7 +773,9 @@ class Hierarchy extends DataExtension {
|
||||
if ($hide_from_cms_tree && $this->showingCMSTree()) {
|
||||
$children = $children->exclude('ClassName', $hide_from_cms_tree);
|
||||
}
|
||||
if(!$showAll && $this->owner->db('ShowInMenus')) $children = $children->filter('ShowInMenus', 1);
|
||||
if(!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
|
||||
$children = $children->filter('ShowInMenus', 1);
|
||||
}
|
||||
|
||||
return $children;
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ class ManyManyThroughQueryManipulator implements DataQueryManipulator
|
||||
* @return string
|
||||
*/
|
||||
public function getJoinAlias() {
|
||||
return $this->getJoinClass();
|
||||
return DataObject::getSchema()->tableName($this->getJoinClass());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -575,8 +575,9 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
||||
public function augmentDatabase() {
|
||||
$owner = $this->owner;
|
||||
$class = get_class($owner);
|
||||
$schema = $owner->getSchema();
|
||||
$baseTable = $this->baseTable();
|
||||
$classTable = $owner->getSchema()->tableName($owner);
|
||||
$classTable = $schema->tableName($owner);
|
||||
|
||||
$isRootClass = $class === $owner->baseClass();
|
||||
|
||||
@ -606,10 +607,10 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
||||
$suffixTable = $classTable;
|
||||
}
|
||||
|
||||
$fields = DataObject::database_fields($owner->class);
|
||||
$fields = $schema->databaseFields($class, false);
|
||||
unset($fields['ID']);
|
||||
if($fields) {
|
||||
$options = Config::inst()->get($owner->class, 'create_table_options', Config::FIRST_SET);
|
||||
$options = Config::inst()->get($class, 'create_table_options', Config::FIRST_SET);
|
||||
$indexes = $owner->databaseIndexes();
|
||||
$extensionClass = $allSuffixes[$suffix];
|
||||
if ($suffix && ($extension = $owner->getExtensionInstance($extensionClass))) {
|
||||
@ -760,8 +761,9 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
||||
* @param int $recordID ID of record to version
|
||||
*/
|
||||
protected function augmentWriteVersioned(&$manipulation, $class, $table, $recordID) {
|
||||
$baseDataClass = DataObject::getSchema()->baseDataClass($class);
|
||||
$baseDataTable = DataObject::getSchema()->tableName($baseDataClass);
|
||||
$schema = DataObject::getSchema();
|
||||
$baseDataClass = $schema->baseDataClass($class);
|
||||
$baseDataTable = $schema->tableName($baseDataClass);
|
||||
|
||||
// Set up a new entry in (table)_versions
|
||||
$newManipulation = array(
|
||||
@ -774,8 +776,7 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
||||
$data = DB::prepared_query("SELECT * FROM \"{$table}\" WHERE \"ID\" = ?", array($recordID))->record();
|
||||
|
||||
if ($data) {
|
||||
$fields = DataObject::database_fields($class);
|
||||
|
||||
$fields = $schema->databaseFields($class, false);
|
||||
if (is_array($fields)) {
|
||||
$data = array_intersect_key($data, $fields);
|
||||
|
||||
@ -1383,8 +1384,8 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
||||
*/
|
||||
public function canBeVersioned($class) {
|
||||
return ClassInfo::exists($class)
|
||||
&& is_subclass_of($class, 'SilverStripe\ORM\DataObject')
|
||||
&& DataObject::has_own_table($class);
|
||||
&& is_subclass_of($class, DataObject::class)
|
||||
&& DataObject::getSchema()->classHasTable($class);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1514,11 +1515,12 @@ class Versioned extends DataExtension implements TemplateGlobalProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
$schema = DataObject::getSchema();
|
||||
$ownedHasMany = array_intersect($owns, array_keys($hasMany));
|
||||
foreach($ownedHasMany as $relationship) {
|
||||
// Find metadata on relationship
|
||||
$joinClass = $owner->hasManyComponent($relationship);
|
||||
$joinField = $owner->getRemoteJoinField($relationship, 'has_many', $polymorphic);
|
||||
$joinClass = $schema->hasManyComponent(get_class($owner), $relationship);
|
||||
$joinField = $schema->getRemoteJoinField(get_class($owner), $relationship, 'has_many', $polymorphic);
|
||||
$idField = $polymorphic ? "{$joinField}ID" : $joinField;
|
||||
$joinTable = DataObject::getSchema()->tableForField($joinClass, $idField);
|
||||
|
||||
|
@ -4,6 +4,7 @@ namespace SilverStripe\Security;
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Forms\FormField;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
use SilverStripe\ORM\SS_List;
|
||||
use SilverStripe\ORM\ArrayList;
|
||||
use SilverStripe\ORM\DataObjectInterface;
|
||||
@ -281,7 +282,11 @@ class PermissionCheckboxSetField extends FormField {
|
||||
$permission->delete();
|
||||
}
|
||||
|
||||
if($fieldname && $record && ($record->hasManyComponent($fieldname) || $record->manyManyComponent($fieldname))) {
|
||||
$schema = DataObject::getSchema();
|
||||
if($fieldname && $record && (
|
||||
$schema->hasManyComponent(get_class($record), $fieldname)
|
||||
|| $schema->manyManyComponent(get_class($record), $fieldname)
|
||||
)) {
|
||||
|
||||
if(!$record->ID) $record->write(); // We need a record ID to write permissions
|
||||
|
||||
|
@ -12,6 +12,7 @@ use SilverStripe\Core\Convert;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\Dev\Deprecation;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\Forms\EmailField;
|
||||
use SilverStripe\Forms\FieldList;
|
||||
use SilverStripe\Forms\Form;
|
||||
@ -1009,18 +1010,18 @@ class Security extends Controller implements TemplateGlobalProvider {
|
||||
return self::$database_is_ready;
|
||||
}
|
||||
|
||||
$requiredClasses = ClassInfo::dataClassesFor('SilverStripe\\Security\\Member');
|
||||
$requiredClasses[] = 'SilverStripe\\Security\\Group';
|
||||
$requiredClasses[] = 'SilverStripe\\Security\\Permission';
|
||||
|
||||
$requiredClasses = ClassInfo::dataClassesFor(Member::class);
|
||||
$requiredClasses[] = Group::class;
|
||||
$requiredClasses[] = Permission::class;
|
||||
$schema = DataObject::getSchema();
|
||||
foreach($requiredClasses as $class) {
|
||||
// Skip test classes, as not all test classes are scaffolded at once
|
||||
if(is_subclass_of($class, 'SilverStripe\\Dev\\TestOnly')) {
|
||||
if(is_a($class, TestOnly::class, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if any of the tables aren't created in the database
|
||||
$table = DataObject::getSchema()->tableName($class);
|
||||
$table = $schema->tableName($class);
|
||||
if(!ClassInfo::hasTable($table)) {
|
||||
return false;
|
||||
}
|
||||
@ -1035,7 +1036,7 @@ class Security extends Controller implements TemplateGlobalProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
$objFields = DataObject::database_fields($class);
|
||||
$objFields = $schema->databaseFields($class, false);
|
||||
$missingFields = array_diff_key($objFields, $dbFields);
|
||||
|
||||
if($missingFields) {
|
||||
|
@ -859,7 +859,6 @@ A very small number of methods were chosen for deprecation, and will be removed
|
||||
appropriately on queries.
|
||||
* `DataList::createDataObject` is now public.
|
||||
* `DataObject` constructor now has an additional parameter, which must be included in subclasses.
|
||||
* `DataObject::database_fields` now returns all fields on that table.
|
||||
* `DataObject::db` now returns composite fields.
|
||||
* `DataObject::ClassName` field has been refactored into a `DBClassName` type field.
|
||||
* `DataObject::can` has new method signature with `$context` parameter.
|
||||
@ -910,7 +909,24 @@ A very small number of methods were chosen for deprecation, and will be removed
|
||||
|
||||
#### <a name="overview-orm-removed"></a>ORM Removed API
|
||||
|
||||
* `DataObject::db` removed and replaced with `DataObjectSchema::fieldSpec` and `DataObjectSchema::fieldSpecs`
|
||||
* `DataObject::manyManyComponent` moved to `DataObjectSchema`
|
||||
* `DataObject::belongsToComponent` moved to `DataObjectSchema`
|
||||
* `DataObject::hasOneComponent` moved to `DataObjectSchema`
|
||||
* `DataObject::hasManyComponent` moved to `DataObjectSchema`
|
||||
* `DataObject::getRemoteJoinField` moved to `DataObjectSchema`
|
||||
* `DataObject::database_fields` renamed and moved to `DataObjectSchema::databaseFields`
|
||||
* `DataObject::has_own_table` renamed and moved to `DataObjectSchema::classHasTable`
|
||||
* `DataObject::composite_fields` renamed and moved to `DataObjectSchema::compositeFields``
|
||||
* `DataObject::manyManyExtraFieldsForComponent` moved to `DataObjectSchema`
|
||||
* Removed `DataObject::validateModelDefinitions`. Relations are now validated within `DataObjectSchema`
|
||||
* Removed `DataObject` methods `hasOwnTableDatabaseField`, `has_own_table_database_field` and
|
||||
`hasDatabaseFields` are superceded by `DataObjectSchema::fieldSpec`.
|
||||
Use `$schema->fieldSpec($class, $field, ['dbOnly', 'uninherited'])`.
|
||||
Exclude `uninherited` option to search all tables in the class hierarchy.
|
||||
* Removed `DataObject::is_composite_field`. Use `DataObjectSchema::compositeField` instead.
|
||||
* Removed `DataObject::custom_database_fields`. Use `DataObjectSchema::databaseFields`
|
||||
or `DataObjectSchema::fieldSpecs` instead.
|
||||
* Removed `DataList::getRelation`, as it was mutable. Use `DataList::applyRelation` instead, which is immutable.
|
||||
* Removed `DataList::applyFilterContext` private method
|
||||
* `Member` Field 'RememberLoginToken' removed, replaced with 'RememberLoginHashes' has_many relationship
|
||||
|
@ -26,12 +26,15 @@ use SilverStripe\Forms\GridField\GridField_ActionProvider;
|
||||
use SilverStripe\Forms\GridField\GridField_DataManipulator;
|
||||
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class GridFieldTest extends SapphireTest {
|
||||
|
||||
protected $extraDataObjects = [
|
||||
GridFieldTest_Permissions::class,
|
||||
GridFieldTest_Cheerleader::class,
|
||||
GridFieldTest_Player::class,
|
||||
GridFieldTest_Team::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* @covers SilverStripe\Forms\GridField\GridField::__construct
|
||||
*/
|
||||
|
@ -48,28 +48,29 @@ class DBCompositeTest extends SapphireTest {
|
||||
* Test DataObject::composite_fields() and DataObject::is_composite_field()
|
||||
*/
|
||||
public function testCompositeFieldMetaDataFunctions() {
|
||||
$this->assertEquals('Money', DataObject::is_composite_field('DBCompositeTest_DataObject', 'MyMoney'));
|
||||
$this->assertFalse(DataObject::is_composite_field('DBCompositeTest_DataObject', 'Title'));
|
||||
$schema = DataObject::getSchema();
|
||||
$this->assertEquals('Money', $schema->compositeField(DBCompositeTest_DataObject::class, 'MyMoney'));
|
||||
$this->assertNull($schema->compositeField(DBCompositeTest_DataObject::class, 'Title'));
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'MyMoney' => 'Money',
|
||||
'OverriddenMoney' => 'Money'
|
||||
),
|
||||
DataObject::composite_fields('DBCompositeTest_DataObject')
|
||||
$schema->compositeFields(DBCompositeTest_DataObject::class)
|
||||
);
|
||||
|
||||
|
||||
$this->assertEquals('Money', DataObject::is_composite_field('SubclassedDBFieldObject', 'MyMoney'));
|
||||
$this->assertEquals('Money', DataObject::is_composite_field('SubclassedDBFieldObject', 'OtherMoney'));
|
||||
$this->assertFalse(DataObject::is_composite_field('SubclassedDBFieldObject', 'Title'));
|
||||
$this->assertFalse(DataObject::is_composite_field('SubclassedDBFieldObject', 'OtherField'));
|
||||
$this->assertEquals('Money', $schema->compositeField(SubclassedDBFieldObject::class, 'MyMoney'));
|
||||
$this->assertEquals('Money', $schema->compositeField(SubclassedDBFieldObject::class, 'OtherMoney'));
|
||||
$this->assertNull($schema->compositeField(SubclassedDBFieldObject::class, 'Title'));
|
||||
$this->assertNull($schema->compositeField(SubclassedDBFieldObject::class, 'OtherField'));
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'MyMoney' => 'Money',
|
||||
'OtherMoney' => 'Money',
|
||||
'OverriddenMoney' => 'Money',
|
||||
),
|
||||
DataObject::composite_fields('SubclassedDBFieldObject')
|
||||
$schema->compositeFields(SubclassedDBFieldObject::class)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -139,11 +139,12 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
||||
* by the order of classnames of existing records
|
||||
*/
|
||||
public function testClassNameSpecGeneration() {
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
// Test with blank entries
|
||||
DBClassName::clear_classname_cache();
|
||||
$do1 = new DataObjectSchemaGenerationTest_DO();
|
||||
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
||||
$fields = $schema->databaseFields(DataObjectSchemaGenerationTest_DO::class, false);
|
||||
/** @skipUpgrade */
|
||||
$this->assertEquals("DBClassName", $fields['ClassName']);
|
||||
$this->assertEquals(
|
||||
@ -159,9 +160,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
||||
$item1 = new DataObjectSchemaGenerationTest_IndexDO();
|
||||
$item1->write();
|
||||
DBClassName::clear_classname_cache();
|
||||
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
||||
/** @skipUpgrade */
|
||||
$this->assertEquals("DBClassName", $fields['ClassName']);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO',
|
||||
@ -175,9 +173,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
||||
$item2 = new DataObjectSchemaGenerationTest_DO();
|
||||
$item2->write();
|
||||
DBClassName::clear_classname_cache();
|
||||
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
||||
/** @skipUpgrade */
|
||||
$this->assertEquals("DBClassName", $fields['ClassName']);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO',
|
||||
@ -193,9 +188,6 @@ class DataObjectSchemaGenerationTest extends SapphireTest {
|
||||
$item2 = new DataObjectSchemaGenerationTest_DO();
|
||||
$item2->write();
|
||||
DBClassName::clear_classname_cache();
|
||||
$fields = DataObject::database_fields('DataObjectSchemaGenerationTest_DO');
|
||||
/** @skipUpgrade */
|
||||
$this->assertEquals("DBClassName", $fields['ClassName']);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'DataObjectSchemaGenerationTest_DO' => 'DataObjectSchemaGenerationTest_DO',
|
||||
|
@ -12,6 +12,7 @@ use SilverStripe\ORM\DataExtension;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
use SilverStripe\ORM\ValidationResult;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\View\ViewableData;
|
||||
|
||||
/**
|
||||
* @package framework
|
||||
@ -60,8 +61,8 @@ class DataObjectTest extends SapphireTest {
|
||||
}
|
||||
|
||||
public function testDb() {
|
||||
$obj = new DataObjectTest_TeamComment();
|
||||
$dbFields = $obj->db();
|
||||
$schema = DataObject::getSchema();
|
||||
$dbFields = $schema->fieldSpecs(DataObjectTest_TeamComment::class);
|
||||
|
||||
// Assert fields are included
|
||||
$this->assertArrayHasKey('Name', $dbFields);
|
||||
@ -73,15 +74,20 @@ class DataObjectTest extends SapphireTest {
|
||||
$this->assertArrayHasKey('ID', $dbFields);
|
||||
|
||||
// Assert that the correct field type is returned when passing a field
|
||||
$this->assertEquals('Varchar', $obj->db('Name'));
|
||||
$this->assertEquals('Text', $obj->db('Comment'));
|
||||
$this->assertEquals('Varchar', $schema->fieldSpec(DataObjectTest_TeamComment::class, 'Name'));
|
||||
$this->assertEquals('Text', $schema->fieldSpec(DataObjectTest_TeamComment::class, 'Comment'));
|
||||
|
||||
// Test with table required
|
||||
$this->assertEquals('DataObjectTest_TeamComment.Varchar', $obj->db('Name', true));
|
||||
$this->assertEquals('DataObjectTest_TeamComment.Text', $obj->db('Comment', true));
|
||||
|
||||
$this->assertEquals(
|
||||
'DataObjectTest_TeamComment.Varchar',
|
||||
$schema->fieldSpec(DataObjectTest_TeamComment::class, 'Name', ['includeClass'])
|
||||
);
|
||||
$this->assertEquals(
|
||||
'DataObjectTest_TeamComment.Text',
|
||||
$schema->fieldSpec(DataObjectTest_TeamComment::class, 'Comment', ['includeClass'])
|
||||
);
|
||||
$obj = new DataObjectTest_ExtendedTeamComment();
|
||||
$dbFields = $obj->db();
|
||||
$dbFields = $schema->fieldSpecs(DataObjectTest_ExtendedTeamComment::class);
|
||||
|
||||
// fixed fields are still included in extended classes
|
||||
$this->assertArrayHasKey('Created', $dbFields);
|
||||
@ -90,7 +96,7 @@ class DataObjectTest extends SapphireTest {
|
||||
$this->assertArrayHasKey('ID', $dbFields);
|
||||
|
||||
// Assert overloaded fields have correct data type
|
||||
$this->assertEquals('HTMLText', $obj->db('Comment'));
|
||||
$this->assertEquals('HTMLText', $schema->fieldSpec(DataObjectTest_ExtendedTeamComment::class, 'Comment'));
|
||||
$this->assertEquals('HTMLText', $dbFields['Comment'],
|
||||
'Calls to DataObject::db without a field specified return correct data types');
|
||||
|
||||
@ -786,7 +792,7 @@ class DataObjectTest extends SapphireTest {
|
||||
$teamSingleton = singleton('DataObjectTest_Team');
|
||||
|
||||
$subteamInstance = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$subteamSingleton = singleton('DataObjectTest_SubTeam');
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
/* hasField() singleton checks */
|
||||
$this->assertTrue($teamSingleton->hasField('ID'),
|
||||
@ -837,35 +843,35 @@ class DataObjectTest extends SapphireTest {
|
||||
/* hasDatabaseField() singleton checks */
|
||||
//$this->assertTrue($teamSingleton->hasDatabaseField('ID'),
|
||||
//'hasDatabaseField() finds built-in fields in singletons');
|
||||
$this->assertTrue($teamSingleton->hasDatabaseField('Title'),
|
||||
$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'Title'),
|
||||
'hasDatabaseField() finds custom fields in singletons');
|
||||
|
||||
/* hasDatabaseField() instance checks */
|
||||
$this->assertFalse($teamInstance->hasDatabaseField('NonExistingField'),
|
||||
$this->assertNull($schema->fieldSpec(DataObjectTest_Team::class, 'NonExistingField'),
|
||||
'hasDatabaseField() doesnt find non-existing fields in instances');
|
||||
//$this->assertTrue($teamInstance->hasDatabaseField('ID'),
|
||||
//$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'ID'),
|
||||
//'hasDatabaseField() finds built-in fields in instances');
|
||||
$this->assertTrue($teamInstance->hasDatabaseField('Created'),
|
||||
$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'Created'),
|
||||
'hasDatabaseField() finds built-in fields in instances');
|
||||
$this->assertTrue($teamInstance->hasDatabaseField('DatabaseField'),
|
||||
$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'DatabaseField'),
|
||||
'hasDatabaseField() finds custom fields in instances');
|
||||
$this->assertFalse($teamInstance->hasDatabaseField('SubclassDatabaseField'),
|
||||
$this->assertNull($schema->fieldSpec(DataObjectTest_Team::class, 'SubclassDatabaseField'),
|
||||
'hasDatabaseField() doesnt find subclass fields in parentclass instances');
|
||||
//$this->assertFalse($teamInstance->hasDatabaseField('DynamicField'),
|
||||
//$this->assertNull($schema->fieldSpec(DataObjectTest_Team::class, 'DynamicField'),
|
||||
//'hasDatabaseField() doesnt dynamic getters in instances');
|
||||
$this->assertTrue($teamInstance->hasDatabaseField('HasOneRelationshipID'),
|
||||
$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'HasOneRelationshipID'),
|
||||
'hasDatabaseField() finds foreign keys in instances');
|
||||
$this->assertTrue($teamInstance->hasDatabaseField('ExtendedDatabaseField'),
|
||||
$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'ExtendedDatabaseField'),
|
||||
'hasDatabaseField() finds extended fields in instances');
|
||||
$this->assertTrue($teamInstance->hasDatabaseField('ExtendedHasOneRelationshipID'),
|
||||
$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_Team::class, 'ExtendedHasOneRelationshipID'),
|
||||
'hasDatabaseField() finds extended foreign keys in instances');
|
||||
$this->assertFalse($teamInstance->hasDatabaseField('ExtendedDynamicField'),
|
||||
$this->assertNull($schema->fieldSpec(DataObjectTest_Team::class, 'ExtendedDynamicField'),
|
||||
'hasDatabaseField() doesnt include extended dynamic getters in instances');
|
||||
|
||||
/* hasDatabaseField() subclass checks */
|
||||
$this->assertTrue($subteamInstance->hasDatabaseField('DatabaseField'),
|
||||
$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_SubTeam::class, 'DatabaseField'),
|
||||
'hasField() finds custom fields in subclass instances');
|
||||
$this->assertTrue($subteamInstance->hasDatabaseField('SubclassDatabaseField'),
|
||||
$this->assertNotEmpty($schema->fieldSpec(DataObjectTest_SubTeam::class, 'SubclassDatabaseField'),
|
||||
'hasField() finds custom fields in subclass instances');
|
||||
|
||||
}
|
||||
@ -874,9 +880,10 @@ class DataObjectTest extends SapphireTest {
|
||||
* @todo Re-enable all test cases for field inheritance aggregation after behaviour has been fixed
|
||||
*/
|
||||
public function testFieldInheritance() {
|
||||
$teamInstance = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$subteamInstance = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
// Test logical fields (including composite)
|
||||
$teamSpecifications = $schema->fieldSpecs(DataObjectTest_Team::class);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'ID',
|
||||
@ -891,10 +898,11 @@ class DataObjectTest extends SapphireTest {
|
||||
'HasOneRelationshipID',
|
||||
'ExtendedHasOneRelationshipID'
|
||||
),
|
||||
array_keys($teamInstance->db()),
|
||||
'inheritedDatabaseFields() contains all fields defined on instance: base, extended and foreign keys'
|
||||
array_keys($teamSpecifications),
|
||||
'fieldSpecifications() contains all fields defined on instance: base, extended and foreign keys'
|
||||
);
|
||||
|
||||
$teamFields = $schema->databaseFields(DataObjectTest_Team::class, false);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'ID',
|
||||
@ -909,10 +917,11 @@ class DataObjectTest extends SapphireTest {
|
||||
'HasOneRelationshipID',
|
||||
'ExtendedHasOneRelationshipID'
|
||||
),
|
||||
array_keys(DataObject::database_fields('DataObjectTest_Team', false)),
|
||||
array_keys($teamFields),
|
||||
'databaseFields() contains only fields defined on instance, including base, extended and foreign keys'
|
||||
);
|
||||
|
||||
$subteamSpecifications = $schema->fieldSpecs(DataObjectTest_SubTeam::class);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'ID',
|
||||
@ -929,17 +938,18 @@ class DataObjectTest extends SapphireTest {
|
||||
'SubclassDatabaseField',
|
||||
'ParentTeamID',
|
||||
),
|
||||
array_keys($subteamInstance->db()),
|
||||
'inheritedDatabaseFields() on subclass contains all fields, including base, extended and foreign keys'
|
||||
array_keys($subteamSpecifications),
|
||||
'fieldSpecifications() on subclass contains all fields, including base, extended and foreign keys'
|
||||
);
|
||||
|
||||
$subteamFields = $schema->databaseFields(DataObjectTest_SubTeam::class, false);
|
||||
$this->assertEquals(
|
||||
array(
|
||||
'ID',
|
||||
'SubclassDatabaseField',
|
||||
'ParentTeamID',
|
||||
),
|
||||
array_keys(DataObject::database_fields('DataObjectTest_SubTeam')),
|
||||
array_keys($subteamFields),
|
||||
'databaseFields() on subclass contains only fields defined on instance'
|
||||
);
|
||||
}
|
||||
@ -1103,22 +1113,26 @@ class DataObjectTest extends SapphireTest {
|
||||
DB::query("SELECT \"ID\" FROM \"DataObjectTest_Team\" WHERE \"Title\" = 'asdfasdf'")->value());
|
||||
}
|
||||
|
||||
public function TestHasOwnTable() {
|
||||
public function testHasOwnTable() {
|
||||
$schema = DataObject::getSchema();
|
||||
/* Test DataObject::has_own_table() returns true if the object has $has_one or $db values */
|
||||
$this->assertTrue(DataObject::has_own_table("DataObjectTest_Player"));
|
||||
$this->assertTrue(DataObject::has_own_table("DataObjectTest_Team"));
|
||||
$this->assertTrue(DataObject::has_own_table("DataObjectTest_Fixture"));
|
||||
$this->assertTrue($schema->classHasTable(DataObjectTest_Player::class));
|
||||
$this->assertTrue($schema->classHasTable(DataObjectTest_Team::class));
|
||||
$this->assertTrue($schema->classHasTable(DataObjectTest_Fixture::class));
|
||||
|
||||
/* Root DataObject that always have a table, even if they lack both $db and $has_one */
|
||||
$this->assertTrue(DataObject::has_own_table("DataObjectTest_FieldlessTable"));
|
||||
$this->assertTrue($schema->classHasTable(DataObjectTest_FieldlessTable::class));
|
||||
|
||||
/* Subclasses without $db or $has_one don't have a table */
|
||||
$this->assertFalse(DataObject::has_own_table("DataObjectTest_FieldlessSubTable"));
|
||||
$this->assertFalse($schema->classHasTable(DataObjectTest_FieldlessSubTable::class));
|
||||
|
||||
/* Return false if you don't pass it a subclass of DataObject */
|
||||
$this->assertFalse(DataObject::has_own_table("SilverStripe\\ORM\\DataObject"));
|
||||
$this->assertFalse(DataObject::has_own_table("SilverStripe\\View\\ViewableData"));
|
||||
$this->assertFalse(DataObject::has_own_table("ThisIsntADataObject"));
|
||||
$this->assertFalse($schema->classHasTable(DataObject::class));
|
||||
$this->assertFalse($schema->classHasTable(ViewableData::class));
|
||||
|
||||
// Invalid class
|
||||
$this->setExpectedException(ReflectionException::class, 'Class ThisIsntADataObject does not exist');
|
||||
$this->assertFalse($schema->classHasTable("ThisIsntADataObject"));
|
||||
}
|
||||
|
||||
public function testMerge() {
|
||||
@ -1190,22 +1204,19 @@ class DataObjectTest extends SapphireTest {
|
||||
public function testValidateModelDefinitionsFailsWithArray() {
|
||||
Config::inst()->update('DataObjectTest_Team', 'has_one', array('NotValid' => array('NoArraysAllowed')));
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$object = new DataObjectTest_Team();
|
||||
$object->hasOneComponent('NotValid');
|
||||
DataObject::getSchema()->hasOneComponent(DataObjectTest_Team::class, 'NotValid');
|
||||
}
|
||||
|
||||
public function testValidateModelDefinitionsFailsWithIntKey() {
|
||||
Config::inst()->update('DataObjectTest_Team', 'has_many', array(12 => 'DataObjectTest_Player'));
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$object = new DataObjectTest_Team();
|
||||
$object->hasManyComponent(12);
|
||||
DataObject::getSchema()->hasManyComponent(DataObjectTest_Team::class, 12);
|
||||
}
|
||||
|
||||
public function testValidateModelDefinitionsFailsWithIntValue() {
|
||||
Config::inst()->update('DataObjectTest_Team', 'many_many', array('Players' => 12));
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$object = new DataObjectTest_Team();
|
||||
$object->manyManyComponent('Players');
|
||||
DataObject::getSchema()->manyManyComponent(DataObjectTest_Team::class, 'Players');
|
||||
}
|
||||
|
||||
public function testNewClassInstance() {
|
||||
@ -1241,7 +1252,8 @@ class DataObjectTest extends SapphireTest {
|
||||
$equipmentSuppliers = $team->EquipmentSuppliers();
|
||||
|
||||
// Check that DataObject::many_many() works as expected
|
||||
list($relationClass, $class, $targetClass, $parentField, $childField, $joinTable) = $team->manyManyComponent('Sponsors');
|
||||
list($relationClass, $class, $targetClass, $parentField, $childField, $joinTable)
|
||||
= DataObject::getSchema()->manyManyComponent(DataObjectTest_Team::class, 'Sponsors');
|
||||
$this->assertEquals(ManyManyList::class, $relationClass);
|
||||
$this->assertEquals('DataObjectTest_Team', $class,
|
||||
'DataObject::many_many() didn\'t find the correct base class');
|
||||
@ -1312,8 +1324,8 @@ class DataObjectTest extends SapphireTest {
|
||||
}
|
||||
|
||||
public function testManyManyExtraFields() {
|
||||
$player = $this->objFromFixture('DataObjectTest_Player', 'player1');
|
||||
$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
// Get all extra fields
|
||||
$teamExtraFields = $team->manyManyExtraFields();
|
||||
@ -1330,13 +1342,13 @@ class DataObjectTest extends SapphireTest {
|
||||
), $teamExtraFields);
|
||||
|
||||
// Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
|
||||
$teamExtraFields = $team->manyManyExtraFieldsForComponent('Players');
|
||||
$teamExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest_Team::class, 'Players');
|
||||
$this->assertEquals($teamExtraFields, array(
|
||||
'Position' => 'Varchar(100)'
|
||||
));
|
||||
|
||||
// We'll have to go through the relation to get the extra fields on Player
|
||||
$playerExtraFields = $player->manyManyExtraFieldsForComponent('Teams');
|
||||
$playerExtraFields = $schema->manyManyExtraFieldsForComponent(DataObjectTest_Player::class, 'Teams');
|
||||
$this->assertEquals($playerExtraFields, array(
|
||||
'Position' => 'Varchar(100)'
|
||||
));
|
||||
@ -1522,7 +1534,7 @@ class DataObjectTest extends SapphireTest {
|
||||
|
||||
$this->assertEquals (
|
||||
'DataObjectTest_Staff',
|
||||
$company->hasManyComponent('CurrentStaff'),
|
||||
DataObject::getSchema()->hasManyComponent(DataObjectTest_Company::class, 'CurrentStaff'),
|
||||
'has_many strips field name data by default on single relationships.'
|
||||
);
|
||||
|
||||
@ -1537,33 +1549,44 @@ class DataObjectTest extends SapphireTest {
|
||||
|
||||
$this->assertEquals (
|
||||
'DataObjectTest_Staff.CurrentCompany',
|
||||
$company->hasManyComponent('CurrentStaff', false),
|
||||
DataObject::getSchema()->hasManyComponent(DataObjectTest_Company::class, 'CurrentStaff', false),
|
||||
'has_many returns field name data on single records when $classOnly is false.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetRemoteJoinField() {
|
||||
$company = new DataObjectTest_Company();
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
$staffJoinField = $company->getRemoteJoinField('CurrentStaff', 'has_many', $polymorphic);
|
||||
// Company schema
|
||||
$staffJoinField = $schema->getRemoteJoinField(
|
||||
DataObjectTest_Company::class, 'CurrentStaff', 'has_many', $polymorphic
|
||||
);
|
||||
$this->assertEquals('CurrentCompanyID', $staffJoinField);
|
||||
$this->assertFalse($polymorphic, 'DataObjectTest_Company->CurrentStaff is not polymorphic');
|
||||
$previousStaffJoinField = $company->getRemoteJoinField('PreviousStaff', 'has_many', $polymorphic);
|
||||
$previousStaffJoinField = $schema->getRemoteJoinField(
|
||||
DataObjectTest_Company::class, 'PreviousStaff', 'has_many', $polymorphic
|
||||
);
|
||||
$this->assertEquals('PreviousCompanyID', $previousStaffJoinField);
|
||||
$this->assertFalse($polymorphic, 'DataObjectTest_Company->PreviousStaff is not polymorphic');
|
||||
|
||||
$ceo = new DataObjectTest_CEO();
|
||||
|
||||
$this->assertEquals('CEOID', $ceo->getRemoteJoinField('Company', 'belongs_to', $polymorphic));
|
||||
// CEO Schema
|
||||
$this->assertEquals('CEOID', $schema->getRemoteJoinField(
|
||||
DataObjectTest_CEO::class, 'Company', 'belongs_to', $polymorphic
|
||||
));
|
||||
$this->assertFalse($polymorphic, 'DataObjectTest_CEO->Company is not polymorphic');
|
||||
$this->assertEquals('PreviousCEOID', $ceo->getRemoteJoinField('PreviousCompany', 'belongs_to', $polymorphic));
|
||||
$this->assertEquals('PreviousCEOID', $schema->getRemoteJoinField(
|
||||
DataObjectTest_CEO::class, 'PreviousCompany', 'belongs_to', $polymorphic
|
||||
));
|
||||
$this->assertFalse($polymorphic, 'DataObjectTest_CEO->PreviousCompany is not polymorphic');
|
||||
|
||||
$team = new DataObjectTest_Team();
|
||||
|
||||
$this->assertEquals('Favourite', $team->getRemoteJoinField('Fans', 'has_many', $polymorphic));
|
||||
// Team schema
|
||||
$this->assertEquals('Favourite', $schema->getRemoteJoinField(
|
||||
DataObjectTest_Team::class, 'Fans', 'has_many', $polymorphic
|
||||
));
|
||||
$this->assertTrue($polymorphic, 'DataObjectTest_Team->Fans is polymorphic');
|
||||
$this->assertEquals('TeamID', $team->getRemoteJoinField('Comments', 'has_many', $polymorphic));
|
||||
$this->assertEquals('TeamID', $schema->getRemoteJoinField(
|
||||
DataObjectTest_Team::class, 'Comments', 'has_many', $polymorphic
|
||||
));
|
||||
$this->assertFalse($polymorphic, 'DataObjectTest_Team->Comments is not polymorphic');
|
||||
}
|
||||
|
||||
|
@ -206,8 +206,37 @@ class ManyManyThroughListTest extends SapphireTest
|
||||
'ManyManyThroughListTest_JoinObject' => 'Text'
|
||||
]);
|
||||
$this->setExpectedException(InvalidArgumentException::class);
|
||||
$object = new ManyManyThroughListTest_Object();
|
||||
$object->manyManyComponent('Items');
|
||||
DataObject::getSchema()->manyManyComponent(ManyManyThroughListTest_Object::class, 'Items');
|
||||
}
|
||||
|
||||
public function testRelationParsing() {
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
// Parent components
|
||||
$this->assertEquals(
|
||||
[
|
||||
ManyManyThroughList::class,
|
||||
ManyManyThroughListTest_Object::class,
|
||||
ManyManyThroughListTest_Item::class,
|
||||
'ParentID',
|
||||
'ChildID',
|
||||
ManyManyThroughListTest_JoinObject::class
|
||||
],
|
||||
$schema->manyManyComponent(ManyManyThroughListTest_Object::class, 'Items')
|
||||
);
|
||||
|
||||
// Belongs_many_many is the same, but with parent/child substituted
|
||||
$this->assertEquals(
|
||||
[
|
||||
ManyManyThroughList::class,
|
||||
ManyManyThroughListTest_Item::class,
|
||||
ManyManyThroughListTest_Object::class,
|
||||
'ChildID',
|
||||
'ParentID',
|
||||
ManyManyThroughListTest_JoinObject::class
|
||||
],
|
||||
$schema->manyManyComponent(ManyManyThroughListTest_Item::class, 'Objects')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use SilverStripe\Dev\Debug;
|
||||
use SilverStripe\ORM\DB;
|
||||
use SilverStripe\ORM\HasManyList;
|
||||
use SilverStripe\ORM\ManyManyList;
|
||||
@ -405,32 +406,28 @@ class VersionedTest extends SapphireTest {
|
||||
* Tests DataObject::hasOwnTableDatabaseField
|
||||
*/
|
||||
public function testHasOwnTableDatabaseFieldWithVersioned() {
|
||||
$noversion = new DataObject();
|
||||
$versioned = new VersionedTest_DataObject();
|
||||
$versionedSub = new VersionedTest_Subclass();
|
||||
$versionedAno = new VersionedTest_AnotherSubclass();
|
||||
$versionField = new VersionedTest_UnversionedWithField();
|
||||
$schema = DataObject::getSchema();
|
||||
|
||||
$this->assertFalse(
|
||||
(bool) $noversion->hasOwnTableDatabaseField('Version'),
|
||||
$this->assertNull(
|
||||
$schema->fieldSpec(DataObject::class, 'Version', ['uninherited']),
|
||||
'Plain models have no version field.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'Int', $versioned->hasOwnTableDatabaseField('Version'),
|
||||
'Int',
|
||||
$schema->fieldSpec(VersionedTest_DataObject::class, 'Version', ['uninherited']),
|
||||
'The versioned ext adds an Int version field.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$versionedSub->hasOwnTableDatabaseField('Version'),
|
||||
$this->assertNull(
|
||||
$schema->fieldSpec(VersionedTest_Subclass::class, 'Version', ['uninherited']),
|
||||
'Sub-classes of a versioned model don\'t have a Version field.'
|
||||
);
|
||||
$this->assertNull(
|
||||
$schema->fieldSpec(VersionedTest_AnotherSubclass::class, 'Version', ['uninherited']),
|
||||
'Sub-classes of a versioned model don\'t have a Version field.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
null,
|
||||
$versionedAno->hasOwnTableDatabaseField('Version'),
|
||||
'Sub-classes of a versioned model don\'t have a Version field.'
|
||||
);
|
||||
$this->assertEquals(
|
||||
'Varchar', $versionField->hasOwnTableDatabaseField('Version'),
|
||||
'Varchar(255)',
|
||||
$schema->fieldSpec(VersionedTest_UnversionedWithField::class, 'Version', ['uninherited']),
|
||||
'Models w/o Versioned can have their own Version field.'
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user