jberculo
jberculo

Reputation: 1113

Silverstripe: Many to many relation to same class

I have a class "performance", and to each performance other performances can be linked as recommendations. After some trial and error I have something semi-working, using the ORM:

public static $many_many = array(
    'Recommendations'       => 'Performance'
);

public static $belongs_many_many = array(
    'Recommendations'       => 'Performance'
);

The above lets me specify recommendations, but in normal many to manies (between two classes), the relation the other way around is also available. Here it is not. Any clue on how to make the inverse relation visible on the "Performance" class without creating the inverse many-to-many and manually inserting the inverse relation there?

Update(May 22, 2014): For now I've come to the conclusion that there isn't an out of the box solution to this problem. I made the following minimal example, based on @FinBoWa's solution, and that shows the "Inverse component of Degree.InterestingDegreesReversed not found (Degree)":

class Degree extends DataObject {

    private static $db = array(
        "Name"  => "Varchar(255)",
    );

    private static $many_many = array(
        'InterestingDegrees' => 'Degree'
    );

    private static $belongs_many_many = array(
        'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
    );

}

class DegreeAdmin extends ModelAdmin {
    public static $managed_models       = array('Degree'); 
    static $url_segment                 = 'degrees'; 
    static $menu_title                  = 'Degrees';
}

I also tried @g4b0's solution, but that has the serious drawback that it does not show the reverse relationship in the admin. For now I am using his solution, but it is not a real solution to the problem. Therefore I will not accept an answer for now...

Upvotes: 2

Views: 1190

Answers (3)

anselmdk
anselmdk

Reputation: 1

I've gone through both ideas, and this is what I've come up with for a project I'm working on (I'm using the Brand model). This is inspired by both solutions, the only additional item I'm adding is the onAfterWrite method. There I'm just looping through all brands, and adding the other side.

This takes for all brands to be edited through only the Brands tab. The other should be removed in getCMSFields like this: $fields->removeByName('RelatedBrands');.

<?php
class Brand extends DataObject {
    private static $many_many = [
        'Brands' => 'Brand' //part 1 of brand-brand relation
    ];

    private static $belongs_many_many = [
        'RelatedBrands' => 'Brand' //part 2 of brand-brand relation
    ];

    public function onAfterWrite() {
        parent::onAfterWrite();

        foreach ($this->Brands() as $brand) {
            $this->RelatedBrands()->add($brand);
        }
    }
}

Upvotes: 0

Olli Tyynel&#228;
Olli Tyynel&#228;

Reputation: 576

You should be able to use the dot notation in the relations.

One of our projects had degrees and we wanted to relate the interesting degrees using the same class:

private static $many_many = array(
    'InterestingDegrees' => 'Degree'
);

private static $belongs_many_many = array(
    'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
);

This works if you add the editor manually when relating site trees.

But if you use model admin, you have to declare the fields manually otherwise you will get erros. So a working example if you are using model admin would be:

class Degree extends DataObject {

    private static $db = array(
        "Name"  => "Varchar(255)",
    );

    private static $many_many = array(
        'InterestingDegrees' => 'Degree'
    );

    private static $belongs_many_many = array(
        'InterestingDegreesReversed' => 'Degree.InterestingDegrees'
    );

    public function getCMSFields() {

        $fields =  new FieldList();

        $fields->add(new TextField("Name"));

        // dont show the field untill we have something to relate to 
        if($this->ID){
            //Interesting degrees
            $gfc = GridFieldConfig_RelationEditor::create();
            //Remove add and edit features
            $gfc->removeComponentsByType('GridFieldAddNewButton');
            $gfc->removeComponentsByType('GridFieldEditButton');
            $gf = new GridField('InterestingDegrees', null, $this->InterestingDegrees(), $gfc);
            $fields->add($gf);
        }
        return $fields;
    }
}

class DegreeAdmin extends ModelAdmin {
    public static $managed_models       = array('Degree'); 
    static $url_segment                 = 'degrees'; 
    static $menu_title                  = 'Degrees';
}

Upvotes: 2

g4b0
g4b0

Reputation: 951

You have to specify two different names for many_many and belongs_many_many, so you can access them. For example:

public static $many_many = array(
    'RelatedRecommendations' => 'Performance'
);

public static $belongs_many_many = array(
    'Recommendations'        => 'Performance'
);

Upvotes: 2

Related Questions