mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merge pull request #3799 from kinglozzer/1377-multiple-many-many
NEW: Support multiple many_manys between the same classes (closes #1377)
This commit is contained in:
commit
b9283fbc06
@ -62,7 +62,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
}
|
||||
|
||||
if($this->relationDepth > 0) {
|
||||
foreach($obj->has_one() as $relName => $relClass) {
|
||||
foreach($obj->hasOne() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
@ -82,7 +82,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
));
|
||||
}
|
||||
|
||||
foreach($obj->has_many() as $relName => $relClass) {
|
||||
foreach($obj->hasMany() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
@ -103,7 +103,7 @@ class JSONDataFormatter extends DataFormatter {
|
||||
$serobj->$relName = $innerParts;
|
||||
}
|
||||
|
||||
foreach($obj->many_many() as $relName => $relClass) {
|
||||
foreach($obj->manyMany() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
|
@ -68,7 +68,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
}
|
||||
|
||||
if($this->relationDepth > 0) {
|
||||
foreach($obj->has_one() as $relName => $relClass) {
|
||||
foreach($obj->hasOne() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
@ -85,7 +85,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
. "\"></$relName>\n";
|
||||
}
|
||||
|
||||
foreach($obj->has_many() as $relName => $relClass) {
|
||||
foreach($obj->hasMany() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
@ -103,7 +103,7 @@ class XMLDataFormatter extends DataFormatter {
|
||||
$xml .= "</$relName>\n";
|
||||
}
|
||||
|
||||
foreach($obj->many_many() as $relName => $relClass) {
|
||||
foreach($obj->manyMany() as $relName => $relClass) {
|
||||
if(!singleton($relClass)->stat('api_access')) continue;
|
||||
|
||||
// Field filtering
|
||||
|
@ -220,9 +220,9 @@ abstract class BulkLoader extends ViewableData {
|
||||
// using $$includerelations flag as false, so that it only contain $db fields
|
||||
$spec['fields'] = (array)singleton($this->objectClass)->fieldLabels(false);
|
||||
|
||||
$has_ones = singleton($this->objectClass)->has_one();
|
||||
$has_manys = singleton($this->objectClass)->has_many();
|
||||
$many_manys = singleton($this->objectClass)->many_many();
|
||||
$has_ones = singleton($this->objectClass)->hasOne();
|
||||
$has_manys = singleton($this->objectClass)->hasMany();
|
||||
$many_manys = singleton($this->objectClass)->manyMany();
|
||||
|
||||
$spec['relations'] = (array)$has_ones + (array)$has_manys + (array)$many_manys;
|
||||
|
||||
|
@ -120,7 +120,7 @@ class CsvBulkLoader extends BulkLoader {
|
||||
$relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record);
|
||||
}
|
||||
if(!$relationObj || !$relationObj->exists()) {
|
||||
$relationClass = $obj->has_one($relationName);
|
||||
$relationClass = $obj->hasOneComponent($relationName);
|
||||
$relationObj = new $relationClass();
|
||||
//write if we aren't previewing
|
||||
if (!$preview) $relationObj->write();
|
||||
|
@ -110,7 +110,11 @@ class FixtureBlueprint {
|
||||
// Populate overrides
|
||||
if($data) foreach($data as $fieldName => $fieldVal) {
|
||||
// Defer relationship processing
|
||||
if($obj->many_many($fieldName) || $obj->has_many($fieldName) || $obj->has_one($fieldName)) {
|
||||
if(
|
||||
$obj->manyManyComponent($fieldName)
|
||||
|| $obj->hasManyComponent($fieldName)
|
||||
|| $obj->hasOneComponent($fieldName)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -127,7 +131,7 @@ class FixtureBlueprint {
|
||||
|
||||
// Populate all relations
|
||||
if($data) foreach($data as $fieldName => $fieldVal) {
|
||||
if($obj->many_many($fieldName) || $obj->has_many($fieldName)) {
|
||||
if($obj->manyManyComponent($fieldName) || $obj->hasManyComponent($fieldName)) {
|
||||
$obj->write();
|
||||
|
||||
$parsedItems = array();
|
||||
@ -165,15 +169,15 @@ class FixtureBlueprint {
|
||||
$parsedItems[] = $this->parseValue($item, $fixtures);
|
||||
}
|
||||
|
||||
if($obj->has_many($fieldName)) {
|
||||
if($obj->hasManyComponent($fieldName)) {
|
||||
$obj->getComponents($fieldName)->setByIDList($parsedItems);
|
||||
} elseif($obj->many_many($fieldName)) {
|
||||
} elseif($obj->manyManyComponent($fieldName)) {
|
||||
$obj->getManyManyComponents($fieldName)->setByIDList($parsedItems);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$hasOneField = preg_replace('/ID$/', '', $fieldName);
|
||||
if($className = $obj->has_one($hasOneField)) {
|
||||
if($className = $obj->hasOneComponent($hasOneField)) {
|
||||
$obj->{$hasOneField.'ID'} = $this->parseValue($fieldVal, $fixtures, $fieldClass);
|
||||
// Inject class for polymorphic relation
|
||||
if($className === 'DataObject') {
|
||||
|
@ -204,6 +204,27 @@ The relationship can also be navigated in [templates](../templates).
|
||||
<% end_if %>
|
||||
<% end_with %>
|
||||
|
||||
To specify multiple $many_manys between the same classes, use the dot notation to distinguish them like below:
|
||||
|
||||
:::php
|
||||
<?php
|
||||
|
||||
class Category extends DataObject {
|
||||
|
||||
private static $many_many = array(
|
||||
'Products' => 'Product',
|
||||
'FeaturedProducts' => 'Product'
|
||||
);
|
||||
}
|
||||
|
||||
class Product extends DataObject {
|
||||
|
||||
private static $belongs_many_many = array(
|
||||
'Categories' => 'Category.Products',
|
||||
'FeaturedInCategories' => 'Category.FeaturedProducts'
|
||||
);
|
||||
}
|
||||
|
||||
## many_many or belongs_many_many?
|
||||
|
||||
If you're unsure about whether an object should take on `many_many` or `belongs_many_many`, the best way to think about it is that the object where the relationship will be edited (i.e. via checkboxes) should contain the `many_many`. For instance, in a `many_many` of Product => Categories, the `Product` should contain the `many_many`, because it is much more likely that the user will select Categories for a Product than vice-versa.
|
||||
|
@ -109,7 +109,7 @@ class FileField extends FormField {
|
||||
|
||||
if($this->relationAutoSetting) {
|
||||
// assume that the file is connected via a has-one
|
||||
$hasOnes = $record->has_one($this->name);
|
||||
$hasOnes = $record->hasOne($this->name);
|
||||
// try to create a file matching the relation
|
||||
$file = (is_string($hasOnes)) ? Object::create($hasOnes) : new $fileClass();
|
||||
} else if($record instanceof File) {
|
||||
|
@ -93,8 +93,8 @@ class FormScaffolder extends Object {
|
||||
}
|
||||
|
||||
// add has_one relation fields
|
||||
if($this->obj->has_one()) {
|
||||
foreach($this->obj->has_one() as $relationship => $component) {
|
||||
if($this->obj->hasOne()) {
|
||||
foreach($this->obj->hasOne() as $relationship => $component) {
|
||||
if($this->restrictFields && !in_array($relationship, $this->restrictFields)) continue;
|
||||
$fieldName = $component === 'DataObject'
|
||||
? $relationship // Polymorphic has_one field is composite, so don't refer to ID subfield
|
||||
@ -118,10 +118,10 @@ class FormScaffolder extends Object {
|
||||
// only add relational fields if an ID is present
|
||||
if($this->obj->ID) {
|
||||
// add has_many relation fields
|
||||
if($this->obj->has_many()
|
||||
if($this->obj->hasMany()
|
||||
&& ($this->includeRelations === true || isset($this->includeRelations['has_many']))) {
|
||||
|
||||
foreach($this->obj->has_many() as $relationship => $component) {
|
||||
foreach($this->obj->hasMany() as $relationship => $component) {
|
||||
if($this->tabbed) {
|
||||
$relationTab = $fields->findOrMakeTab(
|
||||
"Root.$relationship",
|
||||
@ -145,10 +145,10 @@ class FormScaffolder extends Object {
|
||||
}
|
||||
}
|
||||
|
||||
if($this->obj->many_many()
|
||||
if($this->obj->manyMany()
|
||||
&& ($this->includeRelations === true || isset($this->includeRelations['many_many']))) {
|
||||
|
||||
foreach($this->obj->many_many() as $relationship => $component) {
|
||||
foreach($this->obj->manyMany() as $relationship => $component) {
|
||||
if($this->tabbed) {
|
||||
$relationTab = $fields->findOrMakeTab(
|
||||
"Root.$relationship",
|
||||
|
@ -502,7 +502,7 @@ class UploadField extends FileField {
|
||||
if($relation && ($relation instanceof RelationList || $relation instanceof UnsavedRelationList)) {
|
||||
// has_many or many_many
|
||||
$relation->setByIDList($idList);
|
||||
} elseif($record->has_one($fieldname)) {
|
||||
} elseif($record->hasOneComponent($fieldname)) {
|
||||
// has_one
|
||||
$record->{"{$fieldname}ID"} = $idList ? reset($idList) : 0;
|
||||
}
|
||||
@ -590,7 +590,7 @@ class UploadField extends FileField {
|
||||
if(empty($allowedMaxFileNumber)) {
|
||||
$record = $this->getRecord();
|
||||
$name = $this->getName();
|
||||
if($record && $record->has_one($name)) {
|
||||
if($record && $record->hasOneComponent($name)) {
|
||||
return 1; // Default for has_one
|
||||
} else {
|
||||
return null; // Default for has_many and many_many
|
||||
|
@ -78,7 +78,7 @@ class DataDifferencer extends ViewableData {
|
||||
$fields = array_keys($this->toRecord->toMap());
|
||||
}
|
||||
|
||||
$hasOnes = array_merge($this->fromRecord->has_one(), $this->toRecord->has_one());
|
||||
$hasOnes = array_merge($this->fromRecord->hasOne(), $this->toRecord->hasOne());
|
||||
|
||||
// Loop through properties
|
||||
foreach($fields as $field) {
|
||||
|
@ -547,10 +547,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
// DO NOT copy has_many relations, because copying the relation would result in us changing the has_one
|
||||
// relation on the other side of this relation to point at the copy and no longer the original (being a
|
||||
// has_one, it can only point at one thing at a time). So, all relations except has_many can and are copied
|
||||
if ($sourceObject->has_one()) foreach($sourceObject->has_one() as $name => $type) {
|
||||
if ($sourceObject->hasOne()) foreach($sourceObject->hasOne() as $name => $type) {
|
||||
$this->duplicateRelations($sourceObject, $destinationObject, $name);
|
||||
}
|
||||
if ($sourceObject->many_many()) foreach($sourceObject->many_many() as $name => $type) {
|
||||
if ($sourceObject->manyMany()) foreach($sourceObject->manyMany() as $name => $type) {
|
||||
//many_many include belongs_many_many
|
||||
$this->duplicateRelations($sourceObject, $destinationObject, $name);
|
||||
}
|
||||
@ -666,24 +666,24 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if($this->class == 'DataObject') return;
|
||||
|
||||
// Set up accessors for joined items
|
||||
if($manyMany = $this->many_many()) {
|
||||
if($manyMany = $this->manyMany()) {
|
||||
foreach($manyMany as $relationship => $class) {
|
||||
$this->addWrapperMethod($relationship, 'getManyManyComponents');
|
||||
}
|
||||
}
|
||||
if($hasMany = $this->has_many()) {
|
||||
if($hasMany = $this->hasMany()) {
|
||||
|
||||
foreach($hasMany as $relationship => $class) {
|
||||
$this->addWrapperMethod($relationship, 'getComponents');
|
||||
}
|
||||
|
||||
}
|
||||
if($hasOne = $this->has_one()) {
|
||||
if($hasOne = $this->hasOne()) {
|
||||
foreach($hasOne as $relationship => $class) {
|
||||
$this->addWrapperMethod($relationship, 'getComponent');
|
||||
}
|
||||
}
|
||||
if($belongsTo = $this->belongs_to()) foreach(array_keys($belongsTo) as $relationship) {
|
||||
if($belongsTo = $this->belongsTo()) foreach(array_keys($belongsTo) as $relationship) {
|
||||
$this->addWrapperMethod($relationship, 'getComponent');
|
||||
}
|
||||
}
|
||||
@ -972,7 +972,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
// merge relations
|
||||
if($includeRelations) {
|
||||
if($manyMany = $this->many_many()) {
|
||||
if($manyMany = $this->manyMany()) {
|
||||
foreach($manyMany as $relationship => $class) {
|
||||
$leftComponents = $leftObj->getManyManyComponents($relationship);
|
||||
$rightComponents = $rightObj->getManyManyComponents($relationship);
|
||||
@ -983,7 +983,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
|
||||
if($hasMany = $this->has_many()) {
|
||||
if($hasMany = $this->hasMany()) {
|
||||
foreach($hasMany as $relationship => $class) {
|
||||
$leftComponents = $leftObj->getComponents($relationship);
|
||||
$rightComponents = $rightObj->getComponents($relationship);
|
||||
@ -995,7 +995,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
}
|
||||
|
||||
if($hasOne = $this->has_one()) {
|
||||
if($hasOne = $this->hasOne()) {
|
||||
foreach($hasOne as $relationship => $class) {
|
||||
$leftComponent = $leftObj->getComponent($relationship);
|
||||
$rightComponent = $rightObj->getComponent($relationship);
|
||||
@ -1130,7 +1130,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->$fieldName = $fieldValue;
|
||||
}
|
||||
// Set many-many defaults with an array of ids
|
||||
if(is_array($fieldValue) && $this->many_many($fieldName)) {
|
||||
if(is_array($fieldValue) && $this->manyManyComponent($fieldName)) {
|
||||
$manyManyJoin = $this->$fieldName();
|
||||
$manyManyJoin->setByIdList($fieldValue);
|
||||
}
|
||||
@ -1497,7 +1497,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $this->components[$componentName];
|
||||
}
|
||||
|
||||
if($class = $this->has_one($componentName)) {
|
||||
if($class = $this->hasOneComponent($componentName)) {
|
||||
$joinField = $componentName . 'ID';
|
||||
$joinID = $this->getField($joinField);
|
||||
|
||||
@ -1514,7 +1514,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
if(empty($component)) {
|
||||
$component = $this->model->$class->newObject();
|
||||
}
|
||||
} elseif($class = $this->belongs_to($componentName)) {
|
||||
} elseif($class = $this->belongsToComponent($componentName)) {
|
||||
|
||||
$joinField = $this->getRemoteJoinField($componentName, 'belongs_to', $polymorphic);
|
||||
$joinID = $this->ID;
|
||||
@ -1553,18 +1553,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Returns a one-to-many relation as a HasManyList
|
||||
*
|
||||
* @param string $componentName Name of the component
|
||||
* @param string $filter A filter to be inserted into the WHERE clause
|
||||
* @param string|array $sort A sort expression to be inserted into the ORDER BY clause. If omitted, the static
|
||||
* field $default_sort on the component class will be used.
|
||||
* @param string|null $filter Deprecated. A filter to be inserted into the WHERE clause
|
||||
* @param string|null|array $sort Deprecated. A sort expression to be inserted into the ORDER BY clause. If omitted,
|
||||
* the static field $default_sort on the component class will be used.
|
||||
* @param string $join Deprecated, use leftJoin($table, $joinClause) instead
|
||||
* @param string|array $limit A limit expression to be inserted into the LIMIT clause
|
||||
* @param string|null|array $limit Deprecated. A limit expression to be inserted into the LIMIT clause
|
||||
*
|
||||
* @return HasManyList The components of the one-to-many relationship.
|
||||
*/
|
||||
public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null) {
|
||||
public function getComponents($componentName, $filter = null, $sort = null, $join = null, $limit = null) {
|
||||
$result = null;
|
||||
|
||||
if(!$componentClass = $this->has_many($componentName)) {
|
||||
if(!$componentClass = $this->hasManyComponent($componentName)) {
|
||||
user_error("DataObject::getComponents(): Unknown 1-to-many component '$componentName'"
|
||||
. " on class '$this->class'", E_USER_ERROR);
|
||||
}
|
||||
@ -1575,6 +1575,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
);
|
||||
}
|
||||
|
||||
if($filter !== null || $sort !== null || $limit !== null) {
|
||||
Deprecation::notice('3.2', 'The $filter, $sort and $limit parameters for DataObject::getComponents()
|
||||
have been deprecated. Please manipluate the returned list directly.', Deprecation::SCOPE_GLOBAL);
|
||||
}
|
||||
|
||||
// If we haven't been written yet, we can't save these relations, so use a list that handles this case
|
||||
if(!$this->ID) {
|
||||
if(!isset($this->unsavedRelations[$componentName])) {
|
||||
@ -1647,7 +1652,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*/
|
||||
public function getRemoteJoinField($component, $type = 'has_many', &$polymorphic = false) {
|
||||
// Extract relation from current object
|
||||
$remoteClass = $this->$type($component, false);
|
||||
if($type === 'has_many') {
|
||||
$remoteClass = $this->hasManyComponent($component, false);
|
||||
} else {
|
||||
$remoteClass = $this->belongsToComponent($component, false);
|
||||
}
|
||||
|
||||
if(empty($remoteClass)) {
|
||||
throw new Exception("Unknown $type component '$component' on class '$this->class'");
|
||||
}
|
||||
@ -1716,8 +1726,15 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
*
|
||||
* @todo Implement query-params
|
||||
*/
|
||||
public function getManyManyComponents($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $table) = $this->many_many($componentName);
|
||||
public function getManyManyComponents($componentName, $filter = null, $sort = null, $join = null, $limit = null) {
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $table)
|
||||
= $this->manyManyComponent($componentName);
|
||||
|
||||
if($filter !== null || $sort !== null || $join !== null || $limit !== null) {
|
||||
Deprecation::notice('3.2', 'The $filter, $sort, $join and $limit parameters for
|
||||
DataObject::getManyManyComponents() have been deprecated.
|
||||
Please manipluate the returned list directly.', Deprecation::SCOPE_GLOBAL);
|
||||
}
|
||||
|
||||
// If we haven't been written yet, we can't save these relations, so use a list that handles this case
|
||||
if(!$this->ID) {
|
||||
@ -1730,7 +1747,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
$result = ManyManyList::create(
|
||||
$componentClass, $table, $componentField, $parentField,
|
||||
$this->many_many_extraFields($componentName)
|
||||
$this->manyManyExtraFieldsForComponent($componentName)
|
||||
);
|
||||
if($this->model) $result->setDataModel($this->model);
|
||||
|
||||
@ -1743,64 +1760,91 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
->limit($limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.0 Method has been replaced by hasOne() and hasOneComponent()
|
||||
* @param string $component
|
||||
* @return array|null
|
||||
*/
|
||||
public function has_one($component = null) {
|
||||
if($component) {
|
||||
Deprecation::notice('3.2', 'Please use hasOneComponent() instead');
|
||||
return $this->hasOneComponent($component);
|
||||
}
|
||||
|
||||
Deprecation::notice('3.2', 'Please use hasOne() instead');
|
||||
return $this->hasOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the class of a one-to-one component. If $component is null, return all of the one-to-one components and
|
||||
* their classes. If the selected has_one is a polymorphic field then 'DataObject' will be returned for the type.
|
||||
*
|
||||
* @param string $component Name of component
|
||||
*
|
||||
* @return string|array The class of the one-to-one component, or an array of all one-to-one components and their
|
||||
* classes.
|
||||
* @param string $component Deprecated - Name of component
|
||||
* @return string|array The class of the one-to-one component, or an array of all one-to-one components and
|
||||
* their classes.
|
||||
*/
|
||||
public function has_one($component = null) {
|
||||
$classes = ClassInfo::ancestry($this);
|
||||
|
||||
foreach($classes as $class) {
|
||||
// Wait until after we reach DataObject
|
||||
if(in_array($class, array('Object', 'ViewableData', 'DataObject'))) continue;
|
||||
|
||||
if($component) {
|
||||
$hasOne = Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
||||
|
||||
if(isset($hasOne[$component])) {
|
||||
return $hasOne[$component];
|
||||
}
|
||||
} else {
|
||||
$newItems = (array)Config::inst()->get($class, 'has_one', Config::UNINHERITED);
|
||||
// Validate the data
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$has_one has a bad entry: "
|
||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
|
||||
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
$items = isset($items) ? array_merge($newItems, (array)$items) : $newItems;
|
||||
}
|
||||
public function hasOne($component = null) {
|
||||
if($component) {
|
||||
Deprecation::notice(
|
||||
'3.2',
|
||||
'Please use DataObject::hasOneComponent() instead of passing a component name to hasOne()',
|
||||
Deprecation::SCOPE_GLOBAL
|
||||
);
|
||||
return $this->hasOneComponent($component);
|
||||
}
|
||||
return isset($items) ? $items : null;
|
||||
|
||||
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) {
|
||||
$hasOnes = (array)Config::inst()->get($this->class, 'has_one', Config::INHERITED);
|
||||
|
||||
if(isset($hasOnes[$component])) {
|
||||
return $hasOnes[$component];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.0 Method has been replaced by belongsTo() and belongsToComponent()
|
||||
* @param string $component
|
||||
* @param bool $classOnly
|
||||
* @return array|null
|
||||
*/
|
||||
public function belongs_to($component = null, $classOnly = true) {
|
||||
if($component) {
|
||||
Deprecation::notice('3.2', 'Please use belongsToComponent() instead');
|
||||
return $this->belongsToComponent($component, $classOnly);
|
||||
}
|
||||
|
||||
Deprecation::notice('3.2', 'Please use belongsTo() instead');
|
||||
return $this->belongsTo(null, $classOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param string $component
|
||||
* @param string $component - Name of 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|array
|
||||
*/
|
||||
public function belongs_to($component = null, $classOnly = true) {
|
||||
$belongsTo = $this->config()->belongs_to;
|
||||
|
||||
public function belongsTo($component = null, $classOnly = true) {
|
||||
if($component) {
|
||||
if($belongsTo && array_key_exists($component, $belongsTo)) {
|
||||
$belongsTo = $belongsTo[$component];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
Deprecation::notice(
|
||||
'3.2',
|
||||
'Please use DataObject::belongsToComponent() instead of passing a component name to belongsTo()',
|
||||
Deprecation::SCOPE_GLOBAL
|
||||
);
|
||||
return $this->belongsToComponent($component, $classOnly);
|
||||
}
|
||||
|
||||
$belongsTo = (array)Config::inst()->get($this->class, 'belongs_to', Config::INHERITED);
|
||||
if($belongsTo && $classOnly) {
|
||||
return preg_replace('/(.+)?\..+/', '$1', $belongsTo);
|
||||
} else {
|
||||
@ -1808,9 +1852,28 @@ 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|false
|
||||
*/
|
||||
public function belongsToComponent($component, $classOnly = true) {
|
||||
$belongsTo = (array)Config::inst()->get($this->class, 'belongs_to', Config::INHERITED);
|
||||
|
||||
if($belongsTo && array_key_exists($component, $belongsTo)) {
|
||||
$belongsTo = $belongsTo[$component];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($classOnly) ? preg_replace('/(.+)?\..+/', '$1', $belongsTo) : $belongsTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the database fields defined in self::$db and all the parent classes.
|
||||
* Doesn't include any fields specified by self::$has_one. Use $this->has_one() to get these fields
|
||||
* Doesn't include any fields specified by self::$has_one. Use $this->hasOne() to get these fields
|
||||
*
|
||||
* @param string $fieldName Limit the output to a specific field name
|
||||
* @return array The database fields
|
||||
@ -1837,15 +1900,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $dbItems[$fieldName];
|
||||
}
|
||||
} else {
|
||||
// Validate the data
|
||||
foreach($dbItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$db has a bad entry: "
|
||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
|
||||
. " property name, and the map value should be the property type.", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
$items = isset($items) ? array_merge((array) $items, $dbItems) : $dbItems;
|
||||
}
|
||||
}
|
||||
@ -1853,26 +1907,42 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.0 Method has been replaced by hasMany() and hasManyComponent()
|
||||
* @param string $component
|
||||
* @param bool $classOnly
|
||||
* @return array|null
|
||||
*/
|
||||
public function has_many($component = null, $classOnly = true) {
|
||||
if($component) {
|
||||
Deprecation::notice('3.2', 'Please use hasManyComponent() instead');
|
||||
return $this->hasManyComponent($component, $classOnly);
|
||||
}
|
||||
|
||||
Deprecation::notice('3.2', 'Please use hasMany() instead');
|
||||
return $this->hasMany(null, $classOnly);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param string $component Name of component
|
||||
* @param string $component Deprecated - Name of 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|array
|
||||
* @return string|array|false
|
||||
*/
|
||||
public function has_many($component = null, $classOnly = true) {
|
||||
$hasMany = $this->config()->has_many;
|
||||
|
||||
public function hasMany($component = null, $classOnly = true) {
|
||||
if($component) {
|
||||
if($hasMany && array_key_exists($component, $hasMany)) {
|
||||
$hasMany = $hasMany[$component];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
Deprecation::notice(
|
||||
'3.2',
|
||||
'Please use DataObject::hasManyComponent() instead of passing a component name to hasMany()',
|
||||
Deprecation::SCOPE_GLOBAL
|
||||
);
|
||||
return $this->hasManyComponent($component, $classOnly);
|
||||
}
|
||||
|
||||
$hasMany = (array)Config::inst()->get($this->class, 'has_many', Config::INHERITED);
|
||||
if($hasMany && $classOnly) {
|
||||
return preg_replace('/(.+)?\..+/', '$1', $hasMany);
|
||||
} else {
|
||||
@ -1880,170 +1950,217 @@ 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|false
|
||||
*/
|
||||
public function hasManyComponent($component, $classOnly = true) {
|
||||
$hasMany = (array)Config::inst()->get($this->class, 'has_many', Config::INHERITED);
|
||||
|
||||
if($hasMany && array_key_exists($component, $hasMany)) {
|
||||
$hasMany = $hasMany[$component];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ($classOnly) ? preg_replace('/(.+)?\..+/', '$1', $hasMany) : $hasMany;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.0 Method has been replaced by manyManyExtraFields() and
|
||||
* manyManyExtraFieldsForComponent()
|
||||
* @param string $component
|
||||
* @return array
|
||||
*/
|
||||
public function many_many_extraFields($component = null) {
|
||||
if($component) {
|
||||
Deprecation::notice('3.2', 'Please use manyManyExtraFieldsForComponent() instead');
|
||||
return $this->manyManyExtraFieldsForComponent($component);
|
||||
}
|
||||
|
||||
Deprecation::notice('3.2', 'Please use manyManyExtraFields() instead');
|
||||
return $this->manyManyExtraFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the many-to-many extra fields specification.
|
||||
*
|
||||
* If you don't specify a component name, it returns all
|
||||
* extra fields for all components available.
|
||||
*
|
||||
* @param string $component Name of component
|
||||
* @return array
|
||||
* @param string $component Deprecated - Name of component
|
||||
* @return array|null
|
||||
*/
|
||||
public function many_many_extraFields($component = null) {
|
||||
$classes = ClassInfo::ancestry($this);
|
||||
public function manyManyExtraFields($component = null) {
|
||||
if($component) {
|
||||
Deprecation::notice(
|
||||
'3.2',
|
||||
'Please use DataObject::manyManyExtraFieldsForComponent() instead of passing a component name
|
||||
to manyManyExtraFields()',
|
||||
Deprecation::SCOPE_GLOBAL
|
||||
);
|
||||
return $this->manyManyExtraFieldsForComponent($component);
|
||||
}
|
||||
|
||||
foreach($classes as $class) {
|
||||
if(in_array($class, array('ViewableData', 'Object', 'DataObject'))) continue;
|
||||
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);
|
||||
}
|
||||
|
||||
// Find extra fields for one component
|
||||
if($component) {
|
||||
$SNG_class = singleton($class);
|
||||
$extraFields = $SNG_class->stat('many_many_extraFields');
|
||||
// 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);
|
||||
|
||||
// Extra fields are immediately available on this class
|
||||
if(isset($extraFields[$component])) {
|
||||
return $extraFields[$component];
|
||||
}
|
||||
|
||||
$manyMany = $SNG_class->stat('many_many');
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$SNG_candidate = singleton($candidate);
|
||||
$candidateManyMany = $SNG_candidate->stat('belongs_many_many');
|
||||
|
||||
// Find the relation given the class
|
||||
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
|
||||
if($relatedClass == $class) {
|
||||
$relationName = $relation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if($relationName) {
|
||||
$extraFields = $SNG_candidate->stat('many_many_extraFields');
|
||||
if(isset($extraFields[$relationName])) {
|
||||
return $extraFields[$relationName];
|
||||
}
|
||||
foreach($candidateManyManys as $relation => $relatedClass) {
|
||||
if($relatedClass === $this->class) {
|
||||
$relationName = $relation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$manyMany = $SNG_class->stat('belongs_many_many');
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$SNG_candidate = singleton($candidate);
|
||||
$candidateManyMany = $SNG_candidate->stat('many_many');
|
||||
|
||||
// Find the relation given the class
|
||||
if($candidateManyMany) foreach($candidateManyMany as $relation => $relatedClass) {
|
||||
if($relatedClass == $class) {
|
||||
$relationName = $relation;
|
||||
}
|
||||
}
|
||||
|
||||
$extraFields = $SNG_candidate->stat('many_many_extraFields');
|
||||
if(isset($extraFields[$relationName])) {
|
||||
return $extraFields[$relationName];
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Find all the extra fields for all components
|
||||
$newItems = (array)Config::inst()->get($class, 'many_many_extraFields', Config::UNINHERITED);
|
||||
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_array($v)) {
|
||||
user_error(
|
||||
"$class::\$many_many_extraFields has a bad entry: "
|
||||
. var_export($k, true) . " => " . var_export($v, true)
|
||||
. ". Each many_many_extraFields entry should map to a field specification array.",
|
||||
E_USER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
|
||||
// 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 4.0 Method has been renamed to manyMany()
|
||||
* @param string $component
|
||||
* @return array|null
|
||||
*/
|
||||
public function many_many($component = null) {
|
||||
if($component) {
|
||||
Deprecation::notice('3.2', 'Please use manyManyComponent() instead');
|
||||
return $this->manyManyComponent($component);
|
||||
}
|
||||
|
||||
Deprecation::notice('3.2', 'Please use manyMany() instead');
|
||||
return $this->manyMany();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param string $component Name of component
|
||||
*
|
||||
* @return array An array of (parentclass, childclass), or an array of all many-many components
|
||||
* @see DataObject::manyManyComponent()
|
||||
* @param string $component Deprecated - Name of component
|
||||
* @return array|null An array of (parentclass, childclass), or an array of all many-many components
|
||||
*/
|
||||
public function many_many($component = null) {
|
||||
$classes = ClassInfo::ancestry($this);
|
||||
|
||||
foreach($classes as $class) {
|
||||
// Wait until after we reach DataObject
|
||||
if(in_array($class, array('ViewableData', 'Object', 'DataObject'))) continue;
|
||||
|
||||
if($component) {
|
||||
$manyMany = Config::inst()->get($class, 'many_many', Config::UNINHERITED);
|
||||
// Try many_many
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$parentField = $class . "ID";
|
||||
$childField = ($class == $candidate) ? "ChildID" : $candidate . "ID";
|
||||
return array($class, $candidate, $parentField, $childField, "{$class}_$component");
|
||||
}
|
||||
|
||||
// Try belongs_many_many
|
||||
$belongsManyMany = Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
|
||||
$candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$childField = $candidate . "ID";
|
||||
|
||||
// We need to find the inverse component name
|
||||
$otherManyMany = Config::inst()->get($candidate, 'many_many', Config::UNINHERITED);
|
||||
if(!$otherManyMany) {
|
||||
user_error("Inverse component of $candidate not found ({$this->class})", E_USER_ERROR);
|
||||
}
|
||||
|
||||
foreach($otherManyMany as $inverseComponentName => $candidateClass) {
|
||||
if($candidateClass == $class || is_subclass_of($class, $candidateClass)) {
|
||||
$parentField = ($class == $candidate) ? "ChildID" : $candidateClass . "ID";
|
||||
|
||||
return array($class, $candidate, $parentField, $childField,
|
||||
"{$candidate}_$inverseComponentName");
|
||||
}
|
||||
}
|
||||
user_error("Orphaned \$belongs_many_many value for $this->class.$component", E_USER_ERROR);
|
||||
}
|
||||
} else {
|
||||
$newItems = (array)Config::inst()->get($class, 'many_many', Config::UNINHERITED);
|
||||
// Validate the data
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$many_many has a bad entry: "
|
||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
|
||||
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
|
||||
|
||||
$newItems = (array)Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
|
||||
// Validate the data
|
||||
foreach($newItems as $k => $v) {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
user_error("$class::\$belongs_many_many has a bad entry: "
|
||||
. var_export($k,true). " => " . var_export($v,true) . ". Each map key should be a"
|
||||
. " relationship name, and the map value should be the data class to join to.", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
$items = isset($items) ? array_merge($newItems, $items) : $newItems;
|
||||
}
|
||||
public function manyMany($component = null) {
|
||||
if($component) {
|
||||
Deprecation::notice(
|
||||
'3.2',
|
||||
'Please use DataObject::manyManyComponent() instead of passing a component name to manyMany()',
|
||||
Deprecation::SCOPE_GLOBAL
|
||||
);
|
||||
return $this->manyManyComponent($component);
|
||||
}
|
||||
|
||||
return isset($items) ? $items : null;
|
||||
$manyManys = (array)Config::inst()->get($this->class, 'many_many', Config::INHERITED);
|
||||
$belongsManyManys = (array)Config::inst()->get($this->class, 'belongs_many_many', Config::INHERITED);
|
||||
|
||||
$items = array_merge($manyManys, $belongsManyManys);
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return information about a specific many_many component. Returns a numeric array of:
|
||||
* array(
|
||||
* <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"
|
||||
* <joinTable> The join table between the two classes e.g. "Product_Categories"
|
||||
* )
|
||||
* @param string $component The component name
|
||||
* @return array|null
|
||||
*/
|
||||
public function manyManyComponent($component) {
|
||||
$classes = $this->getClassAncestry();
|
||||
foreach($classes as $class) {
|
||||
$manyMany = Config::inst()->get($class, 'many_many', Config::UNINHERITED);
|
||||
// Check if the component is defined in many_many on this class
|
||||
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
|
||||
if($candidate) {
|
||||
$parentField = $class . "ID";
|
||||
$childField = ($class == $candidate) ? "ChildID" : $candidate . "ID";
|
||||
return array($class, $candidate, $parentField, $childField, "{$class}_$component");
|
||||
}
|
||||
|
||||
// Check if the component is defined in belongs_many_many on this class
|
||||
$belongsManyMany = Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
|
||||
$candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null;
|
||||
if($candidate) {
|
||||
// Extract class and relation name from dot-notation
|
||||
if(strpos($candidate, '.') !== false) {
|
||||
list($candidate, $relationName) = explode('.', $candidate, 2);
|
||||
}
|
||||
|
||||
$childField = $candidate . "ID";
|
||||
|
||||
// We need to find the inverse component name
|
||||
$otherManyMany = Config::inst()->get($candidate, 'many_many', Config::UNINHERITED);
|
||||
if(!$otherManyMany) {
|
||||
throw new LogicException("Inverse component of $candidate not found ({$this->class})");
|
||||
}
|
||||
|
||||
// If we've got a relation name (extracted from dot-notation), we can already work out
|
||||
// the join table and candidate class name...
|
||||
if(isset($relationName) && isset($otherManyMany[$relationName])) {
|
||||
$candidateClass = $otherManyMany[$relationName];
|
||||
$joinTable = "{$candidate}_{$relationName}";
|
||||
} else {
|
||||
// ... otherwise, we need to loop over the many_manys and find a relation that
|
||||
// matches up to this class
|
||||
foreach($otherManyMany as $inverseComponentName => $candidateClass) {
|
||||
if($candidateClass == $class || is_subclass_of($class, $candidateClass)) {
|
||||
$joinTable = "{$candidate}_{$inverseComponentName}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we could work out the join table, we've got all the info we need
|
||||
if(isset($joinTable)) {
|
||||
$parentField = ($class == $candidate) ? "ChildID" : $candidateClass . "ID";
|
||||
return array($class, $candidate, $parentField, $childField, $joinTable);
|
||||
}
|
||||
|
||||
throw new LogicException("Orphaned \$belongs_many_many value for $this->class.$component");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2535,7 +2652,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return (
|
||||
array_key_exists($field, $this->record)
|
||||
|| $this->db($field)
|
||||
|| (substr($field,-2) == 'ID') && $this->has_one(substr($field,0, -2))
|
||||
|| (substr($field,-2) == 'ID') && $this->hasOneComponent(substr($field,0, -2))
|
||||
|| $this->hasMethod("get{$field}")
|
||||
);
|
||||
}
|
||||
@ -2624,7 +2741,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
if(Permission::checkMember($member, "ADMIN")) return true;
|
||||
|
||||
if($this->many_many('Can' . $perm)) {
|
||||
if($this->manyManyComponent('Can' . $perm)) {
|
||||
if($this->ParentID && $this->SecurityType == 'Inherit') {
|
||||
if(!($p = $this->Parent)) {
|
||||
return false;
|
||||
@ -2808,12 +2925,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
return $obj;
|
||||
|
||||
// Special case for has_one relationships
|
||||
} else if(preg_match('/ID$/', $fieldName) && $this->has_one(substr($fieldName,0,-2))) {
|
||||
} else if(preg_match('/ID$/', $fieldName) && $this->hasOneComponent(substr($fieldName,0,-2))) {
|
||||
$val = $this->$fieldName;
|
||||
return DBField::create_field('ForeignKey', $val, $fieldName, $this);
|
||||
|
||||
// has_one for polymorphic relations do not end in ID
|
||||
} else if(($type = $this->has_one($fieldName)) && ($type === 'DataObject')) {
|
||||
} else if(($type = $this->hasOneComponent($fieldName)) && ($type === 'DataObject')) {
|
||||
$val = $this->$fieldName();
|
||||
return DBField::create_field('PolymorphicForeignKey', $val, $fieldName, $this);
|
||||
|
||||
@ -2911,16 +3028,16 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* @return String
|
||||
*/
|
||||
public function getReverseAssociation($className) {
|
||||
if (is_array($this->many_many())) {
|
||||
$many_many = array_flip($this->many_many());
|
||||
if (is_array($this->manyMany())) {
|
||||
$many_many = array_flip($this->manyMany());
|
||||
if (array_key_exists($className, $many_many)) return $many_many[$className];
|
||||
}
|
||||
if (is_array($this->has_many())) {
|
||||
$has_many = array_flip($this->has_many());
|
||||
if (is_array($this->hasMany())) {
|
||||
$has_many = array_flip($this->hasMany());
|
||||
if (array_key_exists($className, $has_many)) return $has_many[$className];
|
||||
}
|
||||
if (is_array($this->has_one())) {
|
||||
$has_one = array_flip($this->has_one());
|
||||
if (is_array($this->hasOne())) {
|
||||
$has_one = array_flip($this->hasOne());
|
||||
if (array_key_exists($className, $has_one)) return $has_one[$className];
|
||||
}
|
||||
|
||||
@ -3192,6 +3309,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
$indexes = $this->databaseIndexes();
|
||||
|
||||
// Validate relationship configuration
|
||||
$this->validateModelDefinitions();
|
||||
|
||||
if($fields) {
|
||||
$hasAutoIncPK = ($this->class == ClassInfo::baseDataClass($this->class));
|
||||
DB::require_table($this->class, $fields, $indexes, $hasAutoIncPK, $this->stat('create_table_options'),
|
||||
@ -3228,6 +3348,42 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->extend('augmentDatabase', $dummy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that the configured relations for this class use the correct syntaxes
|
||||
* @throws LogicException
|
||||
*/
|
||||
protected function validateModelDefinitions() {
|
||||
$modelDefinitions = array(
|
||||
'db' => Config::inst()->get($this->class, 'db', Config::UNINHERITED),
|
||||
'has_one' => Config::inst()->get($this->class, 'has_one', Config::UNINHERITED),
|
||||
'has_many' => Config::inst()->get($this->class, 'has_many', Config::UNINHERITED),
|
||||
'belongs_to' => Config::inst()->get($this->class, 'belongs_to', Config::UNINHERITED),
|
||||
'many_many' => Config::inst()->get($this->class, 'many_many', Config::UNINHERITED),
|
||||
'belongs_many_many' => Config::inst()->get($this->class, 'belongs_many_many', Config::UNINHERITED),
|
||||
'many_many_extraFields' => Config::inst()->get($this->class, 'many_many_extraFields', Config::UNINHERITED)
|
||||
);
|
||||
|
||||
foreach($modelDefinitions as $defType => $relations) {
|
||||
if( ! $relations) continue;
|
||||
|
||||
foreach($relations as $k => $v) {
|
||||
if($defType === 'many_many_extraFields') {
|
||||
if(!is_array($v)) {
|
||||
throw new LogicException("$this->class::\$many_many_extraFields has a bad entry: "
|
||||
. var_export($k, true) . " => " . var_export($v, true)
|
||||
. ". Each many_many_extraFields entry should map to a field specification array.");
|
||||
}
|
||||
} else {
|
||||
if(!is_string($k) || is_numeric($k) || !is_string($v)) {
|
||||
throw new LogicException("$this->class::$defType has a bad entry: "
|
||||
. var_export($k, true). " => " . var_export($v, true) . ". Each map key should be a
|
||||
relationship name, and the map value should be the data class to join to.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add default records to database. This function is called whenever the
|
||||
* database is built, after the database tables have all been created. Overload
|
||||
@ -3789,7 +3945,7 @@ 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->has_one($field) && ($obj = $this->dbObject($field))) {
|
||||
if(!$this->hasOneComponent($field) && ($obj = $this->dbObject($field))) {
|
||||
return $obj->exists();
|
||||
} else {
|
||||
return parent::hasValue($field, $arguments, $cache);
|
||||
|
@ -645,7 +645,7 @@ class DataQuery {
|
||||
|
||||
foreach($relation as $rel) {
|
||||
$model = singleton($modelClass);
|
||||
if ($component = $model->has_one($rel)) {
|
||||
if ($component = $model->hasOneComponent($rel)) {
|
||||
if(!$this->query->isJoinedTo($component)) {
|
||||
$foreignKey = $rel;
|
||||
$realModelClass = ClassInfo::table_for_object_field($modelClass, "{$foreignKey}ID");
|
||||
@ -668,7 +668,7 @@ class DataQuery {
|
||||
}
|
||||
$modelClass = $component;
|
||||
|
||||
} elseif ($component = $model->has_many($rel)) {
|
||||
} elseif ($component = $model->hasManyComponent($rel)) {
|
||||
if(!$this->query->isJoinedTo($component)) {
|
||||
$ancestry = $model->getClassAncestry();
|
||||
$foreignKey = $model->getRemoteJoinField($rel);
|
||||
@ -690,7 +690,7 @@ class DataQuery {
|
||||
}
|
||||
$modelClass = $component;
|
||||
|
||||
} elseif ($component = $model->many_many($rel)) {
|
||||
} elseif ($component = $model->manyManyComponent($rel)) {
|
||||
list($parentClass, $componentClass, $parentField, $componentField, $relationTable) = $component;
|
||||
$parentBaseClass = ClassInfo::baseDataClass($parentClass);
|
||||
$componentBaseClass = ClassInfo::baseDataClass($componentClass);
|
||||
|
@ -28,7 +28,7 @@ class ForeignKey extends Int {
|
||||
|
||||
public function scaffoldFormField($title = null, $params = null) {
|
||||
$relationName = substr($this->name,0,-2);
|
||||
$hasOneClass = $this->object->has_one($relationName);
|
||||
$hasOneClass = $this->object->hasOneComponent($relationName);
|
||||
|
||||
if($hasOneClass && singleton($hasOneClass) instanceof Image) {
|
||||
$field = new UploadField($relationName, $title);
|
||||
|
@ -269,7 +269,7 @@ class PermissionCheckboxSetField extends FormField {
|
||||
$permission->delete();
|
||||
}
|
||||
|
||||
if($fieldname && $record && ($record->has_many($fieldname) || $record->many_many($fieldname))) {
|
||||
if($fieldname && $record && ($record->hasManyComponent($fieldname) || $record->manyManyComponent($fieldname))) {
|
||||
|
||||
if(!$record->ID) $record->write(); // We need a record ID to write permissions
|
||||
|
||||
|
@ -28,7 +28,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' =>
|
||||
'ManyManyRelation' =>
|
||||
array(
|
||||
array(
|
||||
"=>FixtureFactoryTest_DataObjectRelation.relation1" => array(),
|
||||
@ -48,18 +48,18 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(2, $obj->ManyMany()->Count());
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID));
|
||||
$this->assertEquals(2, $obj->ManyManyRelation()->Count());
|
||||
$this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation2->ID));
|
||||
|
||||
$this->assertEquals(
|
||||
array('Label' => 'This is a label for relation 1'),
|
||||
$obj->ManyMany()->getExtraData('ManyMany', $relation1->ID)
|
||||
$obj->ManyManyRelation()->getExtraData('ManyManyRelation', $relation1->ID)
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('Label' => 'This is a label for relation 2'),
|
||||
$obj->ManyMany()->getExtraData('ManyMany', $relation2->ID)
|
||||
$obj->ManyManyRelation()->getExtraData('ManyManyRelation', $relation2->ID)
|
||||
);
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' =>
|
||||
'ManyManyRelation' =>
|
||||
'=>FixtureFactoryTest_DataObjectRelation.relation1,' .
|
||||
'=>FixtureFactoryTest_DataObjectRelation.relation2'
|
||||
),
|
||||
@ -104,9 +104,9 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
)
|
||||
);
|
||||
|
||||
$this->assertEquals(2, $obj->ManyMany()->Count());
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyMany()->find('ID', $relation2->ID));
|
||||
$this->assertEquals(2, $obj->ManyManyRelation()->Count());
|
||||
$this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation1->ID));
|
||||
$this->assertNotNull($obj->ManyManyRelation()->find('ID', $relation2->ID));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,7 +119,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' => '=>UnknownClass.relation1'
|
||||
'ManyManyRelation' => '=>UnknownClass.relation1'
|
||||
),
|
||||
array(
|
||||
'FixtureFactoryTest_DataObjectRelation' => array(
|
||||
@ -139,7 +139,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' => '=>FixtureFactoryTest_DataObjectRelation.unknown_identifier'
|
||||
'ManyManyRelation' => '=>FixtureFactoryTest_DataObjectRelation.unknown_identifier'
|
||||
),
|
||||
array(
|
||||
'FixtureFactoryTest_DataObjectRelation' => array(
|
||||
@ -163,7 +163,7 @@ class FixtureBlueprintTest extends SapphireTest {
|
||||
$obj = $blueprint->createObject(
|
||||
'one',
|
||||
array(
|
||||
'ManyMany' => 'FixtureFactoryTest_DataObjectRelation.relation1'
|
||||
'ManyManyRelation' => 'FixtureFactoryTest_DataObjectRelation.relation1'
|
||||
),
|
||||
array(
|
||||
'FixtureFactoryTest_DataObjectRelation' => array(
|
||||
|
@ -163,11 +163,11 @@ class FixtureFactoryTest_DataObject extends DataObject implements TestOnly {
|
||||
);
|
||||
|
||||
private static $many_many = array(
|
||||
"ManyMany" => "FixtureFactoryTest_DataObjectRelation"
|
||||
"ManyManyRelation" => "FixtureFactoryTest_DataObjectRelation"
|
||||
);
|
||||
|
||||
private static $many_many_extraFields = array(
|
||||
"ManyMany" => array(
|
||||
"ManyManyRelation" => array(
|
||||
"Label" => "Varchar"
|
||||
)
|
||||
);
|
||||
|
@ -20,6 +20,8 @@ class DataListTest extends SapphireTest {
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_TeamComment',
|
||||
'DataObjectTest_ExtendedTeamComment',
|
||||
'DataObjectTest_EquipmentCompany',
|
||||
'DataObjectTest_SubEquipmentCompany',
|
||||
'DataObjectTest\NamespacedClass',
|
||||
'DataObjectTest_Company',
|
||||
'DataObjectTest_Fan',
|
||||
|
@ -21,6 +21,9 @@ class DataObjectLazyLoadingTest extends SapphireTest {
|
||||
'DataObjectTest_FieldlessSubTable',
|
||||
'DataObjectTest_ValidatedObject',
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_TeamComment',
|
||||
'DataObjectTest_EquipmentCompany',
|
||||
'DataObjectTest_SubEquipmentCompany',
|
||||
'VersionedTest_DataObject',
|
||||
'VersionedTest_Subclass',
|
||||
'VersionedLazy_DataObject',
|
||||
|
@ -17,6 +17,8 @@ class DataObjectTest extends SapphireTest {
|
||||
'DataObjectTest_ValidatedObject',
|
||||
'DataObjectTest_Player',
|
||||
'DataObjectTest_TeamComment',
|
||||
'DataObjectTest_EquipmentCompany',
|
||||
'DataObjectTest_SubEquipmentCompany',
|
||||
'DataObjectTest\NamespacedClass',
|
||||
'DataObjectTest\RelationClass',
|
||||
'DataObjectTest_ExtendedTeamComment',
|
||||
@ -1060,6 +1062,86 @@ class DataObjectTest extends SapphireTest {
|
||||
);
|
||||
}
|
||||
|
||||
protected function makeAccessible($object, $method) {
|
||||
$reflectionMethod = new ReflectionMethod($object, $method);
|
||||
$reflectionMethod->setAccessible(true);
|
||||
return $reflectionMethod;
|
||||
}
|
||||
|
||||
public function testValidateModelDefinitionsFailsWithArray() {
|
||||
Config::nest();
|
||||
|
||||
$object = new DataObjectTest_Team;
|
||||
$method = $this->makeAccessible($object, 'validateModelDefinitions');
|
||||
|
||||
Config::inst()->update('DataObjectTest_Team', 'has_one', array('NotValid' => array('NoArraysAllowed')));
|
||||
$this->setExpectedException('LogicException');
|
||||
|
||||
try {
|
||||
$method->invoke($object);
|
||||
} catch(Exception $e) {
|
||||
Config::unnest(); // Catch the exception so we can unnest config before failing the test
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidateModelDefinitionsFailsWithIntKey() {
|
||||
Config::nest();
|
||||
|
||||
$object = new DataObjectTest_Team;
|
||||
$method = $this->makeAccessible($object, 'validateModelDefinitions');
|
||||
|
||||
Config::inst()->update('DataObjectTest_Team', 'has_many', array(12 => 'DataObjectTest_Player'));
|
||||
$this->setExpectedException('LogicException');
|
||||
|
||||
try {
|
||||
$method->invoke($object);
|
||||
} catch(Exception $e) {
|
||||
Config::unnest(); // Catch the exception so we can unnest config before failing the test
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function testValidateModelDefinitionsFailsWithIntValue() {
|
||||
Config::nest();
|
||||
|
||||
$object = new DataObjectTest_Team;
|
||||
$method = $this->makeAccessible($object, 'validateModelDefinitions');
|
||||
|
||||
Config::inst()->update('DataObjectTest_Team', 'many_many', array('Players' => 12));
|
||||
$this->setExpectedException('LogicException');
|
||||
|
||||
try {
|
||||
$method->invoke($object);
|
||||
} catch(Exception $e) {
|
||||
Config::unnest(); // Catch the exception so we can unnest config before failing the test
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* many_many_extraFields is allowed to have an array value, so shouldn't throw an exception
|
||||
*/
|
||||
public function testValidateModelDefinitionsPassesWithExtraFields() {
|
||||
Config::nest();
|
||||
|
||||
$object = new DataObjectTest_Team;
|
||||
$method = $this->makeAccessible($object, 'validateModelDefinitions');
|
||||
|
||||
Config::inst()->update('DataObjectTest_Team', 'many_many_extraFields',
|
||||
array('Relations' => array('Price' => 'Int')));
|
||||
|
||||
try {
|
||||
$method->invoke($object);
|
||||
} catch(Exception $e) {
|
||||
Config::unnest();
|
||||
$this->fail('Exception should not be thrown');
|
||||
throw $e;
|
||||
}
|
||||
|
||||
Config::unnest();
|
||||
}
|
||||
|
||||
public function testNewClassInstance() {
|
||||
$dataObject = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$changedDO = $dataObject->newClassInstance('DataObjectTest_SubTeam');
|
||||
@ -1078,32 +1160,85 @@ class DataObjectTest extends SapphireTest {
|
||||
$this->assertEquals($changedDO->ClassName, 'DataObjectTest_SubTeam');
|
||||
}
|
||||
|
||||
public function testMultipleManyManyWithSameClass() {
|
||||
$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
$sponsors = $team->Sponsors();
|
||||
$equipmentSuppliers = $team->EquipmentSuppliers();
|
||||
|
||||
// Check that DataObject::many_many() works as expected
|
||||
list($class, $targetClass, $parentField, $childField, $joinTable) = $team->manyManyComponent('Sponsors');
|
||||
$this->assertEquals('DataObjectTest_Team', $class,
|
||||
'DataObject::many_many() didn\'t find the correct base class');
|
||||
$this->assertEquals('DataObjectTest_EquipmentCompany', $targetClass,
|
||||
'DataObject::many_many() didn\'t find the correct target class for the relation');
|
||||
$this->assertEquals('DataObjectTest_EquipmentCompany_SponsoredTeams', $joinTable,
|
||||
'DataObject::many_many() didn\'t find the correct relation table');
|
||||
|
||||
// Check that ManyManyList still works
|
||||
$this->assertEquals(2, $sponsors->count(), 'Rows are missing from relation');
|
||||
$this->assertEquals(1, $equipmentSuppliers->count(), 'Rows are missing from relation');
|
||||
|
||||
// Check everything works when no relation is present
|
||||
$teamWithoutSponsor = $this->objFromFixture('DataObjectTest_Team', 'team3');
|
||||
$this->assertInstanceOf('ManyManyList', $teamWithoutSponsor->Sponsors());
|
||||
$this->assertEquals(0, $teamWithoutSponsor->Sponsors()->count());
|
||||
|
||||
// Check many_many_extraFields still works
|
||||
$equipmentCompany = $this->objFromFixture('DataObjectTest_EquipmentCompany', 'equipmentcompany1');
|
||||
$equipmentCompany->SponsoredTeams()->add($teamWithoutSponsor, array('SponsorFee' => 1000));
|
||||
$sponsoredTeams = $equipmentCompany->SponsoredTeams();
|
||||
$this->assertEquals(1000, $sponsoredTeams->byID($teamWithoutSponsor->ID)->SponsorFee,
|
||||
'Data from many_many_extraFields was not stored/extracted correctly');
|
||||
|
||||
// Check subclasses correctly inherit multiple many_manys
|
||||
$subTeam = $this->objFromFixture('DataObjectTest_SubTeam', 'subteam1');
|
||||
$this->assertEquals(2, $subTeam->Sponsors()->count(),
|
||||
'Child class did not inherit multiple many_manys');
|
||||
$this->assertEquals(1, $subTeam->EquipmentSuppliers()->count(),
|
||||
'Child class did not inherit multiple many_manys');
|
||||
// Team 2 has one EquipmentCompany sponsor and one SubEquipmentCompany
|
||||
$team2 = $this->objFromFixture('DataObjectTest_Team', 'team2');
|
||||
$this->assertEquals(2, $team2->Sponsors()->count(),
|
||||
'Child class did not inherit multiple belongs_many_manys');
|
||||
|
||||
// Check many_many_extraFields also works from the belongs_many_many side
|
||||
$sponsors = $team2->Sponsors();
|
||||
$sponsors->add($equipmentCompany, array('SponsorFee' => 750));
|
||||
$this->assertEquals(750, $sponsors->byID($equipmentCompany->ID)->SponsorFee,
|
||||
'Data from many_many_extraFields was not stored/extracted correctly');
|
||||
|
||||
$subEquipmentCompany = $this->objFromFixture('DataObjectTest_SubEquipmentCompany', 'subequipmentcompany1');
|
||||
$subTeam->Sponsors()->add($subEquipmentCompany, array('SponsorFee' => 1200));
|
||||
$this->assertEquals(1200, $subTeam->Sponsors()->byID($subEquipmentCompany->ID)->SponsorFee,
|
||||
'Data from inherited many_many_extraFields was not stored/extracted correctly');
|
||||
}
|
||||
|
||||
public function testManyManyExtraFields() {
|
||||
$player = $this->objFromFixture('DataObjectTest_Player', 'player1');
|
||||
$team = $this->objFromFixture('DataObjectTest_Team', 'team1');
|
||||
|
||||
// Get all extra fields
|
||||
$teamExtraFields = $team->many_many_extraFields();
|
||||
$teamExtraFields = $team->manyManyExtraFields();
|
||||
$this->assertEquals(array(
|
||||
'Players' => array('Position' => 'Varchar(100)')
|
||||
), $teamExtraFields);
|
||||
|
||||
// Ensure fields from parent classes are included
|
||||
$subTeam = singleton('DataObjectTest_SubTeam');
|
||||
$teamExtraFields = $subTeam->many_many_extraFields();
|
||||
$teamExtraFields = $subTeam->manyManyExtraFields();
|
||||
$this->assertEquals(array(
|
||||
'Players' => array('Position' => 'Varchar(100)'),
|
||||
'FormerPlayers' => array('Position' => 'Varchar(100)')
|
||||
), $teamExtraFields);
|
||||
|
||||
|
||||
// Extra fields are immediately available on the Team class (defined in $many_many_extraFields)
|
||||
$teamExtraFields = $team->many_many_extraFields('Players');
|
||||
$teamExtraFields = $team->manyManyExtraFieldsForComponent('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->many_many_extraFields('Teams');
|
||||
$playerExtraFields = $player->manyManyExtraFieldsForComponent('Teams');
|
||||
$this->assertEquals($playerExtraFields, array(
|
||||
'Position' => 'Varchar(100)'
|
||||
));
|
||||
@ -1133,7 +1268,7 @@ class DataObjectTest extends SapphireTest {
|
||||
|
||||
// Check that ordering a many-many relation by an aggregate column doesn't fail
|
||||
$player = $this->objFromFixture('DataObjectTest_Player', 'player2');
|
||||
$player->Teams("", "count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
|
||||
$player->Teams()->sort("count(DISTINCT \"DataObjectTest_Team_Players\".\"DataObjectTest_PlayerID\") DESC");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1286,13 +1421,13 @@ class DataObjectTest extends SapphireTest {
|
||||
'CurrentStaff' => 'DataObjectTest_Staff',
|
||||
'PreviousStaff' => 'DataObjectTest_Staff'
|
||||
),
|
||||
$company->has_many(),
|
||||
$company->hasMany(),
|
||||
'has_many strips field name data by default.'
|
||||
);
|
||||
|
||||
$this->assertEquals (
|
||||
'DataObjectTest_Staff',
|
||||
$company->has_many('CurrentStaff'),
|
||||
$company->hasManyComponent('CurrentStaff'),
|
||||
'has_many strips field name data by default on single relationships.'
|
||||
);
|
||||
|
||||
@ -1301,13 +1436,13 @@ class DataObjectTest extends SapphireTest {
|
||||
'CurrentStaff' => 'DataObjectTest_Staff.CurrentCompany',
|
||||
'PreviousStaff' => 'DataObjectTest_Staff.PreviousCompany'
|
||||
),
|
||||
$company->has_many(null, false),
|
||||
$company->hasMany(null, false),
|
||||
'has_many returns field name data when $classOnly is false.'
|
||||
);
|
||||
|
||||
$this->assertEquals (
|
||||
'DataObjectTest_Staff.CurrentCompany',
|
||||
$company->has_many('CurrentStaff', false),
|
||||
$company->hasManyComponent('CurrentStaff', false),
|
||||
'has_many returns field name data on single records when $classOnly is false.'
|
||||
);
|
||||
}
|
||||
@ -1563,6 +1698,11 @@ class DataObjectTest_Team extends DataObject implements TestOnly {
|
||||
)
|
||||
);
|
||||
|
||||
private static $belongs_many_many = array(
|
||||
'Sponsors' => 'DataObjectTest_EquipmentCompany.SponsoredTeams',
|
||||
'EquipmentSuppliers' => 'DataObjectTest_EquipmentCompany.EquipmentCustomers'
|
||||
);
|
||||
|
||||
private static $summary_fields = array(
|
||||
'Title' => 'Custom Title',
|
||||
'Title.UpperCase' => 'Title',
|
||||
@ -1698,6 +1838,25 @@ class DataObjectTest_Company extends DataObject implements TestOnly {
|
||||
);
|
||||
}
|
||||
|
||||
class DataObjectTest_EquipmentCompany extends DataObjectTest_Company implements TestOnly {
|
||||
private static $many_many = array(
|
||||
'SponsoredTeams' => 'DataObjectTest_Team',
|
||||
'EquipmentCustomers' => 'DataObjectTest_Team'
|
||||
);
|
||||
|
||||
private static $many_many_extraFields = array(
|
||||
'SponsoredTeams' => array(
|
||||
'SponsorFee' => 'Int'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
class DataObjectTest_SubEquipmentCompany extends DataObjectTest_EquipmentCompany implements TestOnly {
|
||||
private static $db = array(
|
||||
'SubclassDatabaseField' => 'Varchar'
|
||||
);
|
||||
}
|
||||
|
||||
class DataObjectTest_Staff extends DataObject implements TestOnly {
|
||||
private static $has_one = array (
|
||||
'CurrentCompany' => 'DataObjectTest_Company',
|
||||
|
@ -1,68 +1,82 @@
|
||||
DataObjectTest_EquipmentCompany:
|
||||
equipmentcompany1:
|
||||
Name: Company corp
|
||||
equipmentcompany2:
|
||||
Name: 'Team co.'
|
||||
DataObjectTest_SubEquipmentCompany:
|
||||
subequipmentcompany1:
|
||||
Name: John Smith and co
|
||||
DataObjectTest_Team:
|
||||
team1:
|
||||
Title: Team 1
|
||||
team2:
|
||||
Title: Team 2
|
||||
team3:
|
||||
Title: Team 3
|
||||
team1:
|
||||
Title: Team 1
|
||||
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
|
||||
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany2
|
||||
team2:
|
||||
Title: Team 2
|
||||
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany2,=>DataObjectTest_SubEquipmentCompany.subequipmentcompany1
|
||||
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
|
||||
team3:
|
||||
Title: Team 3
|
||||
DataObjectTest_Player:
|
||||
captain1:
|
||||
FirstName: Captain
|
||||
ShirtNumber: 007
|
||||
FavouriteTeam: =>DataObjectTest_Team.team1
|
||||
Teams: =>DataObjectTest_Team.team1
|
||||
IsRetired: 1
|
||||
captain2:
|
||||
FirstName: Captain 2
|
||||
Teams: =>DataObjectTest_Team.team2
|
||||
player1:
|
||||
FirstName: Player 1
|
||||
player2:
|
||||
FirstName: Player 2
|
||||
Teams: =>DataObjectTest_Team.team1,=>DataObjectTest_Team.team2
|
||||
captain1:
|
||||
FirstName: Captain
|
||||
ShirtNumber: 007
|
||||
FavouriteTeam: =>DataObjectTest_Team.team1
|
||||
Teams: =>DataObjectTest_Team.team1
|
||||
IsRetired: 1
|
||||
captain2:
|
||||
FirstName: Captain 2
|
||||
Teams: =>DataObjectTest_Team.team2
|
||||
player1:
|
||||
FirstName: Player 1
|
||||
player2:
|
||||
FirstName: Player 2
|
||||
Teams: =>DataObjectTest_Team.team1,=>DataObjectTest_Team.team2
|
||||
DataObjectTest_SubTeam:
|
||||
subteam1:
|
||||
Title: Subteam 1
|
||||
SubclassDatabaseField: Subclassed 1
|
||||
ExtendedDatabaseField: Extended 1
|
||||
ParentTeam: =>DataObjectTest_Team.team1
|
||||
subteam2_with_player_relation:
|
||||
Title: Subteam 2
|
||||
SubclassDatabaseField: Subclassed 2
|
||||
ExtendedHasOneRelationship: =>DataObjectTest_Player.player1
|
||||
subteam3_with_empty_fields:
|
||||
Title: Subteam 3
|
||||
subteam1:
|
||||
Title: Subteam 1
|
||||
SubclassDatabaseField: Subclassed 1
|
||||
ExtendedDatabaseField: Extended 1
|
||||
ParentTeam: =>DataObjectTest_Team.team1
|
||||
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
|
||||
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany1
|
||||
subteam2_with_player_relation:
|
||||
Title: Subteam 2
|
||||
SubclassDatabaseField: Subclassed 2
|
||||
ExtendedHasOneRelationship: =>DataObjectTest_Player.player1
|
||||
subteam3_with_empty_fields:
|
||||
Title: Subteam 3
|
||||
DataObjectTest_TeamComment:
|
||||
comment1:
|
||||
Name: Joe
|
||||
Comment: This is a team comment by Joe
|
||||
Team: =>DataObjectTest_Team.team1
|
||||
comment2:
|
||||
Name: Bob
|
||||
Comment: This is a team comment by Bob
|
||||
Team: =>DataObjectTest_Team.team1
|
||||
comment3:
|
||||
Name: Phil
|
||||
Comment: Phil is a unique guy, and comments on team2
|
||||
Team: =>DataObjectTest_Team.team2
|
||||
comment1:
|
||||
Name: Joe
|
||||
Comment: This is a team comment by Joe
|
||||
Team: =>DataObjectTest_Team.team1
|
||||
comment2:
|
||||
Name: Bob
|
||||
Comment: This is a team comment by Bob
|
||||
Team: =>DataObjectTest_Team.team1
|
||||
comment3:
|
||||
Name: Phil
|
||||
Comment: Phil is a unique guy, and comments on team2
|
||||
Team: =>DataObjectTest_Team.team2
|
||||
DataObjectTest_Fan:
|
||||
fan1:
|
||||
Name: Damian
|
||||
Favourite: =>DataObjectTest_Team.team1
|
||||
fan2:
|
||||
Name: Stephen
|
||||
Favourite: =>DataObjectTest_Player.player1
|
||||
SecondFavourite: =>DataObjectTest_Team.team2
|
||||
fan3:
|
||||
Name: Richard
|
||||
Favourite: =>DataObjectTest_Team.team1
|
||||
fan4:
|
||||
Name: Mitch
|
||||
Favourite: =>DataObjectTest_SubTeam.subteam1
|
||||
fan1:
|
||||
Name: Damian
|
||||
Favourite: =>DataObjectTest_Team.team1
|
||||
fan2:
|
||||
Name: Stephen
|
||||
Favourite: =>DataObjectTest_Player.player1
|
||||
SecondFavourite: =>DataObjectTest_Team.team2
|
||||
fan3:
|
||||
Name: Richard
|
||||
Favourite: =>DataObjectTest_Team.team1
|
||||
fan4:
|
||||
Name: Mitch
|
||||
Favourite: =>DataObjectTest_SubTeam.subteam1
|
||||
DataObjectTest_Company:
|
||||
company1:
|
||||
Name: Company corp
|
||||
Owner: =>DataObjectTest_Player.player1
|
||||
company1:
|
||||
Name: 'Team co.'
|
||||
Owner: =>DataObjectTest_Player.player2
|
||||
company1:
|
||||
Name: Company corp
|
||||
Owner: =>DataObjectTest_Player.player1
|
||||
company1:
|
||||
Name: 'Team co.'
|
||||
Owner: =>DataObjectTest_Player.player2
|
||||
|
@ -46,7 +46,7 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
|
||||
$this->assertEquals($group->Permissions()->Count(), 0, 'The tested group has no permissions');
|
||||
|
||||
$this->assertEquals($untouchable->Permissions()->Count(), 1, 'The other group has one permission');
|
||||
$this->assertEquals($untouchable->Permissions("\"Code\"='ADMIN'")->Count(), 1,
|
||||
$this->assertEquals($untouchable->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
|
||||
'The other group has ADMIN permission');
|
||||
|
||||
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount, 'There are no orphaned permissions');
|
||||
@ -62,14 +62,14 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
|
||||
$untouchable->flushCache();
|
||||
$this->assertEquals($group->Permissions()->Count(), 2,
|
||||
'The tested group has two permissions permission');
|
||||
$this->assertEquals($group->Permissions("\"Code\"='ADMIN'")->Count(), 1,
|
||||
$this->assertEquals($group->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
|
||||
'The tested group has ADMIN permission');
|
||||
$this->assertEquals($group->Permissions("\"Code\"='NON-ADMIN'")->Count(), 1,
|
||||
$this->assertEquals($group->Permissions()->where("\"Code\"='NON-ADMIN'")->Count(), 1,
|
||||
'The tested group has CMS_ACCESS_AssetAdmin permission');
|
||||
|
||||
$this->assertEquals($untouchable->Permissions()->Count(), 1,
|
||||
'The other group has one permission');
|
||||
$this->assertEquals($untouchable->Permissions("\"Code\"='ADMIN'")->Count(), 1,
|
||||
$this->assertEquals($untouchable->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
|
||||
'The other group has ADMIN permission');
|
||||
|
||||
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount+2,
|
||||
@ -85,12 +85,12 @@ class PermissionCheckboxSetFieldTest extends SapphireTest {
|
||||
$untouchable->flushCache();
|
||||
$this->assertEquals($group->Permissions()->Count(), 1,
|
||||
'The tested group has 1 permission');
|
||||
$this->assertEquals($group->Permissions("\"Code\"='ADMIN'")->Count(), 1,
|
||||
$this->assertEquals($group->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
|
||||
'The tested group has ADMIN permission');
|
||||
|
||||
$this->assertEquals($untouchable->Permissions()->Count(), 1,
|
||||
'The other group has one permission');
|
||||
$this->assertEquals($untouchable->Permissions("\"Code\"='ADMIN'")->Count(), 1,
|
||||
$this->assertEquals($untouchable->Permissions()->where("\"Code\"='ADMIN'")->Count(), 1,
|
||||
'The other group has ADMIN permission');
|
||||
|
||||
$this->assertEquals(DataObject::get('Permission')->Count(), $baseCount+1,
|
||||
|
@ -49,7 +49,7 @@ class YamlFixtureTest extends SapphireTest {
|
||||
$factory->getId("YamlFixtureTest_DataObject", "testobject1")
|
||||
);
|
||||
$this->assertTrue(
|
||||
$object1->ManyMany()->Count() == 2,
|
||||
$object1->ManyManyRelation()->Count() == 2,
|
||||
"Should be two items in this relationship"
|
||||
);
|
||||
$this->assertGreaterThan(0, $factory->getId("YamlFixtureTest_DataObject", "testobject2"));
|
||||
@ -58,7 +58,7 @@ class YamlFixtureTest extends SapphireTest {
|
||||
$factory->getId("YamlFixtureTest_DataObject", "testobject2")
|
||||
);
|
||||
$this->assertTrue(
|
||||
$object2->ManyMany()->Count() == 1,
|
||||
$object2->ManyManyRelation()->Count() == 1,
|
||||
"Should be one item in this relationship"
|
||||
);
|
||||
}
|
||||
@ -79,7 +79,7 @@ class YamlFixtureTest_DataObject extends DataObject implements TestOnly {
|
||||
"Name" => "Varchar"
|
||||
);
|
||||
private static $many_many = array(
|
||||
"ManyMany" => "YamlFixtureTest_DataObjectRelation"
|
||||
"ManyManyRelation" => "YamlFixtureTest_DataObjectRelation"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ YamlFixtureTest_DataObjectRelation:
|
||||
YamlFixtureTest_DataObject:
|
||||
testobject1:
|
||||
Name: TestObject1
|
||||
ManyMany: =>YamlFixtureTest_DataObjectRelation.relation1,=>YamlFixtureTest_DataObjectRelation.relation2
|
||||
ManyManyRelation: =>YamlFixtureTest_DataObjectRelation.relation1,=>YamlFixtureTest_DataObjectRelation.relation2
|
||||
testobject2:
|
||||
Name: TestObject2
|
||||
ManyMany: =>YamlFixtureTest_DataObjectRelation.relation1
|
||||
ManyManyRelation: =>YamlFixtureTest_DataObjectRelation.relation1
|
Loading…
Reference in New Issue
Block a user