Skip to content

Commit

Permalink
Feature: Add support for many_many and through relationships
Browse files Browse the repository at this point in the history
  • Loading branch information
chrispenny committed Oct 7, 2021
1 parent 697869b commit 6181816
Show file tree
Hide file tree
Showing 17 changed files with 767 additions and 23 deletions.
116 changes: 113 additions & 3 deletions docs/en/relationship-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ there are some implementations where that is not required by Silverstripe, but *
- [`has_one` <-> `has_many`](#has_one---has_many)
- [`has_one` <-> `has_one`](#has_one---has_one)
- [`many_many` relationships](#many_many-relationships)
- [Method one, `care` or `touch` the `many_many` relationship](#method-one-care-or-touch-the-many_many-relationship)
- [Method two, `care` or `touch` the `has_many` relationship](#method-two-care-or-touch-the-has_many-relationship)

## `has_one` relationships

Expand Down Expand Up @@ -77,7 +79,8 @@ Our `LinkBlock` can now `care` about `CtaLink`.

## `many_many` relationships

Similarly to the `has_one`, Silverstripe does not force us to define our `many_many` relationships in both directions.
Similarly to the `has_one`, Silverstripe does not force us to define our `many_many` relationships in both directions,
however, it is recommended. https://docs.silverstripe.org/en/4/developer_guides/model/relations/#many-many

A common example of this would be a `Page` that has `many_many` `TaxonomyTerm`:

Expand All @@ -93,7 +96,7 @@ If I would like my `Page` to `care` about its `Terms`, then I also need to defin
Since the `TaxonomyTerm` class is part of a vendor module, I could add this in an `Extension`:

```php
private static array $many_many = [
private static array $belongs_many_many = [
'Pages' => Page::class,
];
```
Expand All @@ -102,6 +105,113 @@ Or through yaml:

```yaml
SilverStripe\Taxonomy\TaxonomyTerm:
many_many:
belongs_many_many:
Pages: Page
```

## `many_many` with `through` relationships

If you are using `through` models for your `many_many` relationship, first of all - well done.

### Method one, `care` or `touch` the `many_many` relationship

Your `many_many` should look something like the example below, where we are making a `many_many` between our `Pages`
and `TaxonomyTerms`.

```php
private static array $many_many = [
'Terms' => [
'through' => PageTaxonomyTerm::class,
'from' => 'Parent',
'to' => 'TaxonomyTerm',
],
];
```

You could now decide to `care` about `Terms`.

```php
private static array $cares = [
'Terms',
];
```

However, like the examples above, we will also need to understand the relationship from `TaxonomyTerms` back to `Page`.
This is something that Silverstripe does not initially force you to do, but we require it.

We could add the relationship info through an extension that we apply to `TaxonomyTerm`. Note, the key things that
changed here are the relationship name (from `Terms` to `Pages`), and the `from` and `to` values are switched, because
we're now traversing the relationship in the oposite direction.

```php
private static array $belongs_many_many = [
'Pages' => [
'through' => PageTaxonomyTerm::class,
'from' => 'TaxonomyTerm',
'to' => 'Parent',
],
];
```

If you were wanting to `touch` rather than `cares`, then you could add this as well.

```php
private static array $touches = [
'Pages',
];
```

### Method two, `care` or `touch` the `has_many` relationship

If you would like more control, then the example above can be expanded. Instead of only defining your `many_many`, you
can also add a `has_many` to the `through` model, and you could decide to `care` about that instead.

```php
private static array $many_many = [
'Terms' => [
'through' => PageTaxonomyTerm::class,
'from' => 'Parent',
'to' => 'TaxonomyTerm',
],
];
private static array $has_many = [
'TermRelationships' => PageTaxonomyTerm::class,
];
private static array $cares = [
'TermRelationships',
];
```

`PageTaxonomyTerm` can then decide what it `cares` or `touches`. Below our `PageTaxonomyTerm` `cares` about the assigned
`TaxonomyTerm`.

```php
class PageTaxonomyTerm extends DataObject
{
private static string $table_name = 'PageTaxonomyTerm';
private static array $has_one = [
'Parent' => Page::class,
'TaxonomyTerm' => TaxonomyTerm::class,
];
private static array $owned_by = [
'Parent',
];
private static array $cares = [
'TaxonomyTerm',
];
}
```

And of course, because `PageTaxonomyTerm` `cares` about the `TaxonomyTerm`, then we also need to define the relationship
from `TaxonomyTerm` to `PageTaxonomyTerm`.

```php
private static array $has_many = [
'TermRelationships' => PageTaxonomyTerm::class,
];
```
Loading

0 comments on commit 6181816

Please sign in to comment.