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:
parent
3f1805bfd2
commit
203f77116b
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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',
|
||||
@ -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',
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user