1
0
mirror of https://github.com/silverstripe/silverstripe-framework synced 2024-10-22 14:05:37 +02:00

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_if %>
<% end_with %> <% 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? ## 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. 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'); $manyMany = $SNG_class->stat('belongs_many_many');
$candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null; $candidate = (isset($manyMany[$component])) ? $manyMany[$component] : null;
if($candidate) { 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); $SNG_candidate = singleton($candidate);
$candidateManyMany = $SNG_candidate->stat('many_many'); $candidateManyMany = $SNG_candidate->stat('many_many');
if( empty($varName) ) { // Find the relation given the class
// Find the relation given the class if(!$relationName && $candidateManyMany) {
if($candidateManyMany) { foreach($candidateManyMany as $relation => $relatedClass) {
foreach($candidateManyMany as $relation => $relatedClass) { if($relatedClass == $class) {
if($relatedClass == $class) { $relationName = $relation;
$relationName = $relation;
}
} }
} }
} else {
$relationName = $varName;
} }
if( !isset($relationName) ) { $extraFields = $SNG_candidate->stat('many_many_extraFields');
user_error("Inverse component of $candidate not found ({$this->class})", E_USER_ERROR);
}
$extraFields = $SNG_candidate->stat('many_many_extraFields');
if(isset($extraFields[$relationName])) { if(isset($extraFields[$relationName])) {
return $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); $belongsManyMany = Config::inst()->get($class, 'belongs_many_many', Config::UNINHERITED);
$candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null; $candidate = (isset($belongsManyMany[$component])) ? $belongsManyMany[$component] : null;
if($candidate) { if($candidate) {
// Extract class and relation name from dot-notation
list($candidate,$varName) = explode('.',$candidate); if(strpos($candidate, '.') !== false) {
list($candidate, $relationName) = explode('.', $candidate, 2);
}
$childField = $candidate . "ID"; $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); 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) { foreach($otherManyMany as $inverseComponentName => $candidateClass) {
if($candidateClass == $class || is_subclass_of($class, $candidateClass)) { if($candidateClass == $class || is_subclass_of($class, $candidateClass)) {
$parentField = ($class == $candidate) ? "ChildID" : $candidateClass . "ID"; $joinTable = "{$candidate}_{$inverseComponentName}";
break;
return array($class, $candidate, $parentField,
$childField,"{$candidate}_$inverseComponentName");
} }
} }
} 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); user_error("Orphaned \$belongs_many_many value for $this->class.$component", E_USER_ERROR);
} }
} else { } else {

View File

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

View File

@ -21,6 +21,9 @@ class DataObjectLazyLoadingTest extends SapphireTest {
'DataObjectTest_FieldlessSubTable', 'DataObjectTest_FieldlessSubTable',
'DataObjectTest_ValidatedObject', 'DataObjectTest_ValidatedObject',
'DataObjectTest_Player', 'DataObjectTest_Player',
'DataObjectTest_TeamComment',
'DataObjectTest_EquipmentCompany',
'DataObjectTest_SubEquipmentCompany',
'VersionedTest_DataObject', 'VersionedTest_DataObject',
'VersionedTest_Subclass', 'VersionedTest_Subclass',
'VersionedLazy_DataObject', 'VersionedLazy_DataObject',

View File

@ -17,6 +17,8 @@ class DataObjectTest extends SapphireTest {
'DataObjectTest_ValidatedObject', 'DataObjectTest_ValidatedObject',
'DataObjectTest_Player', 'DataObjectTest_Player',
'DataObjectTest_TeamComment', 'DataObjectTest_TeamComment',
'DataObjectTest_EquipmentCompany',
'DataObjectTest_SubEquipmentCompany',
'DataObjectTest\NamespacedClass', 'DataObjectTest\NamespacedClass',
'DataObjectTest\RelationClass', 'DataObjectTest\RelationClass',
'DataObjectTest_ExtendedTeamComment', 'DataObjectTest_ExtendedTeamComment',
@ -1078,6 +1080,59 @@ class DataObjectTest extends SapphireTest {
$this->assertEquals($changedDO->ClassName, 'DataObjectTest_SubTeam'); $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() { public function testManyManyExtraFields() {
$player = $this->objFromFixture('DataObjectTest_Player', 'player1'); $player = $this->objFromFixture('DataObjectTest_Player', 'player1');
$team = $this->objFromFixture('DataObjectTest_Team', 'team1'); $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( private static $summary_fields = array(
'Title' => 'Custom Title', 'Title' => 'Custom Title',
'Title.UpperCase' => '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 { class DataObjectTest_Staff extends DataObject implements TestOnly {
private static $has_one = array ( private static $has_one = array (
'CurrentCompany' => 'DataObjectTest_Company', '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: DataObjectTest_Team:
team1: team1:
Title: Team 1 Title: Team 1
team2: Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
Title: Team 2 EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany2
team3: team2:
Title: Team 3 Title: Team 2
Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany2,=>DataObjectTest_SubEquipmentCompany.subequipmentcompany1
EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
team3:
Title: Team 3
DataObjectTest_Player: DataObjectTest_Player:
captain1: captain1:
FirstName: Captain FirstName: Captain
ShirtNumber: 007 ShirtNumber: 007
FavouriteTeam: =>DataObjectTest_Team.team1 FavouriteTeam: =>DataObjectTest_Team.team1
Teams: =>DataObjectTest_Team.team1 Teams: =>DataObjectTest_Team.team1
IsRetired: 1 IsRetired: 1
captain2: captain2:
FirstName: Captain 2 FirstName: Captain 2
Teams: =>DataObjectTest_Team.team2 Teams: =>DataObjectTest_Team.team2
player1: player1:
FirstName: Player 1 FirstName: Player 1
player2: player2:
FirstName: Player 2 FirstName: Player 2
Teams: =>DataObjectTest_Team.team1,=>DataObjectTest_Team.team2 Teams: =>DataObjectTest_Team.team1,=>DataObjectTest_Team.team2
DataObjectTest_SubTeam: DataObjectTest_SubTeam:
subteam1: subteam1:
Title: Subteam 1 Title: Subteam 1
SubclassDatabaseField: Subclassed 1 SubclassDatabaseField: Subclassed 1
ExtendedDatabaseField: Extended 1 ExtendedDatabaseField: Extended 1
ParentTeam: =>DataObjectTest_Team.team1 ParentTeam: =>DataObjectTest_Team.team1
subteam2_with_player_relation: Sponsors: =>DataObjectTest_EquipmentCompany.equipmentcompany1,=>DataObjectTest_EquipmentCompany.equipmentcompany2
Title: Subteam 2 EquipmentSuppliers: =>DataObjectTest_EquipmentCompany.equipmentcompany1
SubclassDatabaseField: Subclassed 2 subteam2_with_player_relation:
ExtendedHasOneRelationship: =>DataObjectTest_Player.player1 Title: Subteam 2
subteam3_with_empty_fields: SubclassDatabaseField: Subclassed 2
Title: Subteam 3 ExtendedHasOneRelationship: =>DataObjectTest_Player.player1
subteam3_with_empty_fields:
Title: Subteam 3
DataObjectTest_TeamComment: DataObjectTest_TeamComment:
comment1: comment1:
Name: Joe Name: Joe
Comment: This is a team comment by Joe Comment: This is a team comment by Joe
Team: =>DataObjectTest_Team.team1 Team: =>DataObjectTest_Team.team1
comment2: comment2:
Name: Bob Name: Bob
Comment: This is a team comment by Bob Comment: This is a team comment by Bob
Team: =>DataObjectTest_Team.team1 Team: =>DataObjectTest_Team.team1
comment3: comment3:
Name: Phil Name: Phil
Comment: Phil is a unique guy, and comments on team2 Comment: Phil is a unique guy, and comments on team2
Team: =>DataObjectTest_Team.team2 Team: =>DataObjectTest_Team.team2
DataObjectTest_Fan: DataObjectTest_Fan:
fan1: fan1:
Name: Damian Name: Damian
Favourite: =>DataObjectTest_Team.team1 Favourite: =>DataObjectTest_Team.team1
fan2: fan2:
Name: Stephen Name: Stephen
Favourite: =>DataObjectTest_Player.player1 Favourite: =>DataObjectTest_Player.player1
SecondFavourite: =>DataObjectTest_Team.team2 SecondFavourite: =>DataObjectTest_Team.team2
fan3: fan3:
Name: Richard Name: Richard
Favourite: =>DataObjectTest_Team.team1 Favourite: =>DataObjectTest_Team.team1
fan4: fan4:
Name: Mitch Name: Mitch
Favourite: =>DataObjectTest_SubTeam.subteam1 Favourite: =>DataObjectTest_SubTeam.subteam1
DataObjectTest_Company: DataObjectTest_Company:
company1: company1:
Name: Company corp Name: Company corp
Owner: =>DataObjectTest_Player.player1 Owner: =>DataObjectTest_Player.player1
company1: company1:
Name: 'Team co.' Name: 'Team co.'
Owner: =>DataObjectTest_Player.player2 Owner: =>DataObjectTest_Player.player2