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@90072 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sam Minnee 2009-10-23 23:27:51 +00:00
parent 9a83be9140
commit 709f162f13
3 changed files with 131 additions and 31 deletions

View File

@ -483,6 +483,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$this->addWrapperMethod($relationship, 'getComponent');
}
}
if($belongsTo = $this->belongs_to()) foreach(array_keys($belongsTo) as $relationship) {
$this->addWrapperMethod($relationship, 'getComponent');
}
}
/**
@ -1193,30 +1196,35 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return $this->components[$componentName];
}
if($componentClass = $this->has_one($componentName)) {
$childID = $this->getField($componentName . 'ID');
if($class = $this->has_one($componentName)) {
$joinField = $componentName . 'ID';
$joinID = $this->getField($joinField);
if($childID && is_numeric($childID)) {
$component = DataObject::get_by_id($componentClass,$childID);
if($joinID) {
$component = DataObject::get_by_id($class, $joinID);
}
// If no component exists, create placeholder object
if(!isset($component)) {
$component = new $componentClass();
// We may have had an orphaned ID that needs to be cleaned up
$this->setField($componentName . 'ID', 0);
if(!isset($component) || !$component) {
$component = new $class();
}
} elseif($class = $this->belongs_to($componentName)) {
$joinField = $this->getRemoteJoinField($componentName, 'belongs_to');
$joinID = $this->ID;
if($joinID) {
$component = DataObject::get_one($class, "\"$joinField\" = $joinID");
}
// If no component exists, create placeholder object
if(!$component) {
$component = new $componentClass();
if(!isset($component) || !$component) {
$component = new $class();
$component->$joinField = $this->ID;
}
$this->components[$componentName] = $component;
return $component;
} else {
user_error("DataObject::getComponent(): Unknown 1-to-1 component '$componentName' on class '$this->class'", E_USER_ERROR);
throw new Exception("DataObject->getComponent(): Could not find component '$componentName'.");
}
$this->components[$componentName] = $component;
return $component;
}
/**
@ -1248,7 +1256,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->getRemoteJoinField($componentName);
$joinField = $this->getRemoteJoinField($componentName, 'has_many');
if($this->isInDB()) { //Check to see whether we should query the db
$query = $this->getComponentsQuery($componentName, $filter, $sort, $join, $limit);
@ -1288,7 +1296,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->getRemoteJoinField($componentName);
$joinField = $this->getRemoteJoinField($componentName, 'has_many');
$id = $this->getField("ID");
@ -1304,20 +1312,21 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* field can be found it defaults to 'ParentID'.
*
* @param string $component
* @param string $type the join type - either 'has_many' or 'belongs_to'
* @return string
*/
public function getRemoteJoinField($component) {
$remoteClass = $this->has_many($component, false);
public function getRemoteJoinField($component, $type = 'has_many') {
$remoteClass = $this->$type($component, false);
if(!$remoteClass) {
throw new Exception("Unknown has_many component '$component' on class '$this->class'");
throw new Exception("Unknown $type component '$component' on class '$this->class'");
}
if($fieldPos = strpos($remoteClass, '.')) {
return substr($remoteClass, $fieldPos + 1) . 'ID';
}
$remoteRelations = array_flip(Object::combined_static($this->has_many($component), 'has_one', 'DataObject'));
$remoteRelations = array_flip(Object::combined_static($remoteClass, 'has_one', 'DataObject'));
// look for remote has_one joins on this class or any parent classes
foreach(array_reverse(ClassInfo::ancestry($this)) as $class) {
@ -1481,6 +1490,33 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
return isset($items) ? $items : null;
}
/**
* 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 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 = Object::combined_static($this->class, 'belongs_to', 'DataObject');
if($component) {
if($belongsTo && array_key_exists($component, $belongsTo)) {
$belongsTo = $belongsTo[$component];
} else {
return false;
}
}
if($belongsTo && $classOnly) {
return preg_replace('/(.+)?\..+/', '$1', $belongsTo);
} else {
return $belongsTo ? $belongsTo : array();
}
}
/**
* 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
@ -3240,12 +3276,28 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
public static $default_records = null;
/**
* one-to-one relationship definitions.
* This is a map from component name to data type.
* One-to-zero relationship defintion. This is a map of component name to data type. In order to turn this into a
* true one-to-one relationship you can add a {@link DataObject::$belongs_to} relationship on the child class.
*
* Note that you cannot have a has_one and belongs_to relationship with the same name.
*
* @var array
*/
public static $has_one = null;
/**
* A meta-relationship that allows you to define the reverse side of a {@link DataObject::$has_one}.
*
* This does not actually create any data structures, but allows you to query the other object in a one-to-one
* relationship from the child object. If you have multiple belongs_to links to another object you can use the
* syntax "ClassName.HasOneName" to specify which foreign has_one key on the other object to use.
*
* Note that you cannot have a has_one and belongs_to relationship with the same name.
*
* @var array
*/
public static $belongs_to;
/**
* This defines a one-to-many relationship. It is a map of component name to the remote data class.
*

View File

@ -24,6 +24,7 @@ abstract class DataObjectDecorator extends Extension {
protected static $decoratable_statics = array(
'db' => true,
'has_one' => true,
'belongs_to' => true,
'indexes' => true,
'defaults' => true,
'has_many' => true,

View File

@ -846,6 +846,41 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals('CurrentCompanyID', $company->getRemoteJoinField('CurrentStaff'));
$this->assertEquals('PreviousCompanyID', $company->getRemoteJoinField('PreviousStaff'));
$ceo = new DataObjectTest_CEO();
$this->assertEquals('CEOID', $ceo->getRemoteJoinField('Company', 'belongs_to'));
$this->assertEquals('PreviousCEOID', $ceo->getRemoteJoinField('PreviousCompany', 'belongs_to'));
}
public function testBelongsTo() {
$company = new DataObjectTest_Company();
$ceo = new DataObjectTest_CEO();
$company->write();
$ceo->write();
$company->CEOID = $ceo->ID;
$company->write();
$this->assertEquals($company->ID, $ceo->Company()->ID, 'belongs_to returns the right results.');
$ceo = new DataObjectTest_CEO();
$ceo->write();
$this->assertTrue (
$ceo->Company() instanceof DataObjectTest_Company,
'DataObjects across belongs_to relations are automatically created.'
);
$this->assertEquals($ceo->ID, $ceo->Company()->CEOID, 'Remote IDs are automatically set.');
$ceo->write(false, false, false, true);
$this->assertTrue($ceo->Company()->isInDB(), 'write() writes belongs_to components to the database.');
$newCEO = DataObject::get_by_id('DataObjectTest_CEO', $ceo->ID);
$this->assertEquals (
$ceo->Company()->ID, $newCEO->Company()->ID, 'belongs_to can be retrieved from the database.'
);
}
}
@ -963,6 +998,11 @@ class DataObjectTest_ValidatedObject extends DataObject implements TestOnly {
}
class DataObjectTest_Company extends DataObject {
public static $has_one = array (
'CEO' => 'DataObjectTest_CEO',
'PreviousCEO' => 'DataObjectTest_CEO'
);
public static $has_many = array (
'CurrentStaff' => 'DataObjectTest_Staff.CurrentCompany',
'PreviousStaff' => 'DataObjectTest_Staff.PreviousCompany'
@ -976,6 +1016,13 @@ class DataObjectTest_Staff extends DataObject {
);
}
class DataObjectTest_CEO extends DataObjectTest_Staff {
public static $belongs_to = array (
'Company' => 'DataObjectTest_Company.CEO',
'AnotherCompany' => 'DataObjectTest_Company.PreviousCEO'
);
}
DataObject::add_extension('DataObjectTest_Team', 'DataObjectTest_Team_Decorator');
?>