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
docs/en/02_Developer_Guides/00_Model
model
tests/model
@ -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.
|
||||||
|
@ -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 {
|
||||||
|
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user