Merge branch 'master' of git@github.com:sminnee/sapphire

From: Sam Minnee <sam@silverstripe.com>

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@90071 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2009-10-23 23:26:10 +00:00
parent 599adc97b1
commit 9a83be9140
5 changed files with 108 additions and 96 deletions

View File

@ -1202,14 +1202,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
// If no component exists, create placeholder object
if(!isset($component)) {
$component = $this->createComponent($componentName);
$component = new $componentClass();
// We may have had an orphaned ID that needs to be cleaned up
$this->setField($componentName . 'ID', 0);
}
// If no component exists, create placeholder object
if(!$component) {
$component = $this->createComponent($componentName);
$component = new $componentClass();
}
$this->components[$componentName] = $component;
@ -1248,7 +1248,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
user_error("DataObject::getComponents(): Unknown 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
}
$joinField = $this->getComponentJoinField($componentName);
$joinField = $this->getRemoteJoinField($componentName);
if($this->isInDB()) { //Check to see whether we should query the db
$query = $this->getComponentsQuery($componentName, $filter, $sort, $join, $limit);
@ -1288,7 +1288,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
user_error("DataObject::getComponentsQuery(): Unknown 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
}
$joinField = $this->getComponentJoinField($componentName);
$joinField = $this->getRemoteJoinField($componentName);
$id = $this->getField("ID");
@ -1298,46 +1298,35 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return singleton($componentClass)->extendedSQL($combinedFilter, $sort, $limit, $join);
}
/**
* Tries to find the db-key for storing a relation (defaults to "ParentID" if no relation is found).
* The iteration is necessary because the most specific class does not always have a database-table.
* 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'.
*
* @param string $componentName Name of one to many component
*
* @return string Fieldname for the parent-relation
* @param string $component
* @return string
*/
public function getComponentJoinField($componentName) {
if(!$componentClass = $this->has_many($componentName)) {
user_error("DataObject::getComponents(): Unknown 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
public function getRemoteJoinField($component) {
$remoteClass = $this->has_many($component, false);
if(!$remoteClass) {
throw new Exception("Unknown has_many component '$component' on class '$this->class'");
}
$componentObj = singleton($componentClass);
// get has-one relations
$reversedComponentRelations = array_flip($componentObj->has_one());
// get all parentclasses for the current class which have tables
$allClasses = ClassInfo::ancestry($this->class);
// use most specific relation by default (turn around order)
$allClasses = array_reverse($allClasses);
// traverse up through all classes with database-tables, starting with the most specific
// (mostly the classname of the calling DataObject)
foreach($allClasses as $class) {
// if this class does a "has-one"-representation, use it
if(isset($reversedComponentRelations[$class]) && false != $reversedComponentRelations[$class]) {
$joinField = $reversedComponentRelations[$class] . 'ID';
break;
}
if($fieldPos = strpos($remoteClass, '.')) {
return substr($remoteClass, $fieldPos + 1) . 'ID';
}
if(!isset($joinField)) {
$joinField = 'ParentID';
$remoteRelations = array_flip(Object::combined_static($this->has_many($component), 'has_one', 'DataObject'));
// look for remote has_one joins on this class or any parent classes
foreach(array_reverse(ClassInfo::ancestry($this)) as $class) {
if(array_key_exists($class, $remoteRelations)) return $remoteRelations[$class] . 'ID';
}
return $joinField;
return 'ParentID';
}
/**
* Sets the component of a relationship.
*
@ -1430,23 +1419,6 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $query;
}
/**
* Creates an empty component for the given one-one or one-many relationship
*
* @param string $componentName
*
* @return DataObject The empty component. The exact class will be that of the components class.
*/
protected function createComponent($componentName) {
if(($componentClass = $this->has_one($componentName)) || ($componentClass = $this->has_many($componentName))) {
$component = new $componentClass(null);
return $component;
} else {
user_error("DataObject::createComponent(): Unknown 1-to-1 or 1-to-many component '$componentName' on class '$this->class'", E_USER_ERROR);
}
}
/**
* Pull out a join clause for a many-many relationship.
*
@ -1551,41 +1523,29 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
/**
* Return the class of a one-to-many component. If $component is null, return all of the one-to-many components
* and their classes.
* 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
*
* @return string|array The class of the one-to-many component, or an array of all one-to-many components and their classes.
* @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 has_many($component = null) {
static $ignoreClasses = array('ViewableData', 'Object', 'DataObject');
$classes = ClassInfo::ancestry($this->class);
public function has_many($component = null, $classOnly = true) {
$hasMany = Object::combined_static($this->class, 'has_many', 'DataObject');
if($component) {
foreach($classes as $class) {
if(in_array($class, $ignoreClasses)) continue;
$hasMany = (array)Object::uninherited_static($class, 'has_many');
if(isset($hasMany[$component])) return $hasMany[$component];
if($hasMany && array_key_exists($component, $hasMany)) {
$hasMany = $hasMany[$component];
} else {
return false;
}
}
if($hasMany && $classOnly) {
return preg_replace('/(.+)?\..+/', '$1', $hasMany);
} else {
$items = array();
foreach($classes as $class) {
if(in_array($class, $ignoreClasses)) continue;
$newItems = (array)Object::uninherited_static($class, 'has_many');
// Validate the data
foreach($newItems as $k => $v) {
if(!is_string($k) || is_numeric($k) || !is_string($v)) user_error("$class::\$has_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 = array_merge($newItems, (array)$items);
}
return $items;
return $hasMany ? $hasMany : array();
}
}
@ -3287,16 +3247,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
public static $has_one = null;
/**
* one-to-many relationship definitions.
* This is a map from component name to data type.
* This defines a one-to-many relationship. It is a map of component name to the remote data class.
*
* Caution: Because this doesn't define any data structure itself, you should
* specify a $has_one relationship on the other end of the relationship.
* Also, if the $has_one relationship on the other end has multiple
* definitions of this class (e.g. two different relationships to the Member
* object), then you need to write a custom accessor (e.g. overload the
* function from the key of this array), because sapphire won't know which
* to access.
* This relationship type does not actually create a data structure itself - you need to define a matching $has_one
* relationship on the child class. Also, if the $has_one relationship on the child class has multiple links to this
* class you can use the syntax "ClassName.HasOneRelationshipName" in the remote data class definition to show
* which foreign key to use.
*
* @var array
*/

View File

@ -54,11 +54,12 @@ class VirtualPage extends Page {
}
}
function syncLinkTracking() {
$this->HasBrokenLink = DataObject::get_by_id('SiteTree', $this->CopyContentFromID) ? false : true;
public function syncLinkTracking() {
if($this->CopyContentFromID) {
$this->HasBrokenLink = !(bool) DataObject::get_by_id('SiteTree', $this->CopyContentFromID);
}
}
/**
* Generate the CMS fields from the fields from the original page.
*/

2
forms/FormScaffolder.php Normal file → Executable file
View File

@ -118,7 +118,7 @@ class FormScaffolder extends Object {
);
}
$relationshipFields = singleton($component)->summaryFields();
$foreignKey = $this->obj->getComponentJoinField($relationship);
$foreignKey = $this->obj->getRemoteJoinField($relationship);
$ctf = new ComplexTableField(
$this,
$relationship,

2
search/filters/SearchFilter.php Normal file → Executable file
View File

@ -187,7 +187,7 @@ abstract class SearchFilter extends Object {
} elseif ($component = $model->has_many($rel)) {
if(!$query->isJoinedTo($component)) {
$ancestry = $model->getClassAncestry();
$foreignKey = $model->getComponentJoinField($rel);
$foreignKey = $model->getRemoteJoinField($rel);
$query->leftJoin($component, "\"$component\".\"{$foreignKey}\" = \"{$ancestry[0]}\".\"ID\"");
/**
* add join clause to the component's ancestry classes so that the search filter could search on its

View File

@ -807,6 +807,47 @@ class DataObjectTest extends SapphireTest {
$this->assertTrue($team->hasValue('DatabaseField', null, false));
}
public function testHasMany() {
$company = new DataObjectTest_Company();
$this->assertEquals (
array (
'CurrentStaff' => 'DataObjectTest_Staff',
'PreviousStaff' => 'DataObjectTest_Staff'
),
$company->has_many(),
'has_many strips field name data by default.'
);
$this->assertEquals (
'DataObjectTest_Staff',
$company->has_many('CurrentStaff'),
'has_many strips field name data by default on single relationships.'
);
$this->assertEquals (
array (
'CurrentStaff' => 'DataObjectTest_Staff.CurrentCompany',
'PreviousStaff' => 'DataObjectTest_Staff.PreviousCompany'
),
$company->has_many(null, false),
'has_many returns field name data when $classOnly is false.'
);
$this->assertEquals (
'DataObjectTest_Staff.CurrentCompany',
$company->has_many('CurrentStaff', false),
'has_many returns field name data on single records when $classOnly is false.'
);
}
public function testGetRemoteJoinField() {
$company = new DataObjectTest_Company();
$this->assertEquals('CurrentCompanyID', $company->getRemoteJoinField('CurrentStaff'));
$this->assertEquals('PreviousCompanyID', $company->getRemoteJoinField('PreviousStaff'));
}
}
class DataObjectTest_Player extends Member implements TestOnly {
@ -921,6 +962,20 @@ class DataObjectTest_ValidatedObject extends DataObject implements TestOnly {
}
}
class DataObjectTest_Company extends DataObject {
public static $has_many = array (
'CurrentStaff' => 'DataObjectTest_Staff.CurrentCompany',
'PreviousStaff' => 'DataObjectTest_Staff.PreviousCompany'
);
}
class DataObjectTest_Staff extends DataObject {
public static $has_one = array (
'CurrentCompany' => 'DataObjectTest_Company',
'PreviousCompany' => 'DataObjectTest_Company'
);
}
DataObject::add_extension('DataObjectTest_Team', 'DataObjectTest_Team_Decorator');
?>