Fixes, tests and documentation for multiple many_manys between the same class

This commit is contained in:
Loz Calver 2015-01-23 23:13:17 +00:00
parent 3f1805bfd2
commit 203f77116b
6 changed files with 209 additions and 91 deletions

View File

@ -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.

View File

@ -1931,29 +1931,25 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$manyMany = $SNG_class->stat('belongs_many_many');
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
if($candidate) {
list($candidate,$varName) = explode('.',$candidate);
$relationName = null;
// Extract class and relation name from dot-notation
if(strpos($candidate, '.') !== false) {
list($candidate, $relationName) = explode('.', $candidate, 2);
}
$SNG_candidate = singleton($candidate);
$candidateManyMany = $SNG_candidate->stat('many_many');
if( empty($varName) ) {
// Find the relation given the class
if($candidateManyMany) {
foreach($candidateManyMany as $relation => $relatedClass) {
if($relatedClass == $class) {
$relationName = $relation;
}
// Find the relation given the class
if(!$relationName && $candidateManyMany) {
foreach($candidateManyMany as $relation => $relatedClass) {
if($relatedClass == $class) {
$relationName = $relation;
}
}
} else {
$relationName = $varName;
}
if( !isset($relationName) ) {
user_error("Inverse component of $candidate not found ({$this->class})", E_USER_ERROR);
}
$extraFields = $SNG_candidate->stat('many_many_extraFields');
$extraFields = $SNG_candidate->stat('many_many_extraFields');
if(isset($extraFields[$relationName])) {
return $extraFields[$relationName];
}
@ -2011,8 +2007,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
$belongsManyMany = Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
$candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null;
if($candidate) {
list($candidate,$varName) = explode('.',$candidate);
// Extract class and relation name from dot-notation
if(strpos($candidate, '.') !== false) {
list($candidate, $relationName) = explode('.', $candidate, 2);
}
$childField = $candidate . "ID";
@ -2022,22 +2020,23 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
user_error("Inverse component of $candidate not found ({$this->class})", E_USER_ERROR);
}
if( empty($varName) ) {
if(isset($relationName) && isset($otherManyMany[$relationName])) {
$candidateClass = $otherManyMany[$relationName];
$joinTable = "{$candidate}_{$relationName}";
} else {
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");
$joinTable = "{$candidate}_{$inverseComponentName}";
break;
}
}
} else {
if( isset($otherManyMany[$varName]) ) {
$candidateClass = $otherManyMany[$varName];
$parentField = ($class == $candidate) ? "ChildID" : $candidateClass . "ID";
return array($class, $candidate, $parentField, $childField,"{$candidate}_$varName");
}
}
if(isset($joinTable)) {
$parentField = ($class == $candidate) ? "ChildID" : $candidateClass . "ID";
return array($class, $candidate, $parentField, $childField, $joinTable);
}
user_error("Orphaned \$belongs_many_many value for $this->class.$component", E_USER_ERROR);
}
} else {

View File

@ -20,6 +20,8 @@ class DataListTest extends SapphireTest {
'DataObjectTest_Player',
'DataObjectTest_TeamComment',
'DataObjectTest_ExtendedTeamComment',
'DataObjectTest_EquipmentCompany',
'DataObjectTest_SubEquipmentCompany',
'DataObjectTest\NamespacedClass',
'DataObjectTest_Company',
'DataObjectTest_Fan',

View File

@ -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',

View File

@ -17,6 +17,8 @@ class DataObjectTest extends SapphireTest {
'DataObjectTest_ValidatedObject',
'DataObjectTest_Player',
'DataObjectTest_TeamComment',
'DataObjectTest_EquipmentCompany',
'DataObjectTest_SubEquipmentCompany',
'DataObjectTest\NamespacedClass',
'DataObjectTest\RelationClass',
'DataObjectTest_ExtendedTeamComment',
@ -1078,6 +1080,59 @@ 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->many_many('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');
@ -1563,6 +1618,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 +1758,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',

View File

@ -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