diff --git a/en/02_Developer_Guides/00_Model/02_Relations.md b/en/02_Developer_Guides/00_Model/02_Relations.md index 61995037a..053b846f3 100644 --- a/en/02_Developer_Guides/00_Model/02_Relations.md +++ b/en/02_Developer_Guides/00_Model/02_Relations.md @@ -163,6 +163,70 @@ be necessary. For example additional parent classes for each respective relation duplication of code. [/warning] +### Multi-relational `has_one` {#multi-relational-has-one} + +A single `has_one` relation can be allowed to manage multiple `has_many` relations - this is especially useful +in situations like adding multiple lists of links to a [`SiteConfig`](api:SilverStripe\SiteConfig\SiteConfig) to build a menu. + +An additional column is created called `Relation`, along with the `Class` +and `ID` columns of a normal polymorphic `has_one` relation. + +[warning] +If you save records into the `has_one` relation programatically, you must set the relation in the +`Relation` field, or it won't be included when you fetch the `has_many` relation list. + +Generally it is better to instead add the record with the `has_one` relation into its corresponding `has_many` relation +directly - see [adding relations](#adding-relations). +[/warning] + +To specify that a `has_one` relation is multi-relational define the relation like so: + +```php +namespace App\Model; + +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\DataObjectSchema; + +class Fan extends DataObject +{ + // ... + + private static array $has_one = [ + 'FanOf' => [ + // The class here is the class for the has_one - it must be polymorphic. + 'class' => DataObject::class, + // Setting this to true is what defines this has_one relation as multi-relational + DataObjectSchema::HAS_ONE_MULTI_RELATIONAL => true, + ], + ]; +} +``` + +[hint] +Multi-relational `has_one` relations *must* be [polymorphic](#polymorphic-has-one). +[/hint] + +It is best practice for your `has_many` relations to indicate which relation they're pointing at using dot notation. For example: + +```php +namespace App\Model; + +use SilverStripe\ORM\DataObject; + +class Team extends DataObject +{ + // ... + + private static array $has_many = [ + // Notice that these are both pointing at the same has_one relation! + 'CheapFans' => Fan::class . '.FanOf', + 'VipFans' => Fan::class . '.FanOf', + ]; +} +``` + +See [`has_many`](#has_many) below for more details about `has_many` relations. + ### `belongs_to` Defines a one-to-one relationship with another object, which declares the other end of the relationship with a @@ -215,6 +279,8 @@ SilverStripe\Assets\Image: Team: App\Model\Team ``` +You can point multiple `has_many` relations at a single `has_one` relation if you use a [multi-relational `has_one`](#multi-relational-has-one). + Note that in some cases you may be better off using a `many_many` relation instead. Carefully consider whether you are defining a "one-to-many" or a "many-to-many" relationship. [/alert] diff --git a/en/04_Changelogs/5.2.0.md b/en/04_Changelogs/5.2.0.md index 9c9662daf..d6cfdb90c 100644 --- a/en/04_Changelogs/5.2.0.md +++ b/en/04_Changelogs/5.2.0.md @@ -23,6 +23,47 @@ title: 5.2.0 (unreleased) This release comes jampacked with new ORM features, granting you access to some new abstractions for more powerful and efficient queries. +#### Multi-relational `has_one` relations + +Traditionally, if you wanted to have multiple `has_many` relations for the same class, you would have to include a separate `has_one` relation for *each* `has_many` relation. + +This release includes a new `has_one` syntax to declare that your `has_one` should be allowed to handle multiple reciprocal `has_many` relations. The syntax for that is as follows: + +[hint] +Multi-relational `has_one` relations *must* be polymorphic. +[/hint] + +```php +namespace App\Model; + +use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\DataObjectSchema; + +class MyExample extends DataObject +{ + // ... + + private static array $has_one = [ + 'MyMultiRelationalRelation' => [ + // The class here is the class for the has_one - it must be polymorphic. + 'class' => DataObject::class, + // Setting this to true is what defines this has_one relation as multi-relational + DataObjectSchema::HAS_ONE_MULTI_RELATIONAL => true, + ], + ]; +} +``` + +Multiple `has_many` relations on a single class can point to the above `has_one` relation, and they will be correctly saved and resolved when you get the relation list. + +[warning] +This new feature means sometimes the value in the associative `has_one` configuration array will be an array, rather than a class name. +If you are relying on fetching this configuration to find the class names of `has_one` relations, consider using +[`DataObject::hasOne()`](api:SilverStripe\ORM\DataObject::hasOne()) or [`DataObjectSchema::hasOneComponent()`](api:SilverStripe\ORM\DataObjectSchema::hasOneComponent()) instead. +[/warning] + +See [multi-relational `has_one` in the relations docs](/developer_guides/model/relations/#multi-relational-has-one) for more details about this relation type. + #### UNION clause {#orm-union-clause} Abstractions for the SQL `UNION` clause have been added to `SQLSelect` and `DataQuery`.