Relationships
Introduction
The relationships
member on a resource object is a relationships object, as defined by the JSON:API specification. Members of the relationships object represent references from the resource object in which it is defined to other resource objects.
A relationship object must contain at least one of the following:
links
data
meta
Field Names
As noted in the Attributes chapter, members on the relationships object share a common namespace with attribute members, and the resource object's type
and id
. In other words, a resource cannot have an attribute or relationship with the same name, nor can it have an attribute or relationship named type
or id
.
It is recommended that the field name case of relationships follows the same convention as a resource's attributes. In other words, if you are using camel-case for attributes, you should also use camel-case for relationships.
Defining Relationships
Relationships are defined on resources by creating a relationship object via the relation
method. To add relationships to your resource object, add them to the relationships
method.
For example, a posts
resource may have the following relationships:
namespace App\JsonApi\V1\Posts;
use LaravelJsonApi\Core\Resources\JsonApiResource;
class PostResource extends JsonApiResource
{
/**
* Get the resource's relationships.
*
* @param \Illuminate\Http\Request|null $request
* @return iterable
*/
public function relationships($request): iterable
{
return [
$this->relation('author'),
$this->relation('comments'),
$this->relation('tags'),
];
}
// ...
}
TIP
Note that there is no distinction between to-one relationships (author
in this example) or to-many relationships (comments
, tags
). This is because here we are defining the JSON:API relationship object, and whether it is a to-one or to-many relation can be determined by the data
member.
When calling the relation
method, we provide the JSON:API field name as the first argument. The relationship object will assume that the relationship name on the model is the camel-case form of the field name. For example, if we were using dash-case for our field names, a field name of blog-tags
would assume the model's relationship is called blogTags
.
If the model relationship name is different from this convention, provide it as the second argument. For example, if our JSON:API field name was author
but the relationship on the model was user
, we would define our relation as follows:
$this->relation('author', 'user')
Conditional Relationships
Just as with conditional attributes, you can also define conditional relationships. To do this, use the when()
and mergeWhen()
methods. For example:
public function relationships($request): iterable
{
return [
$this->relation('author'),
$this->when($request->user()->isAdmin(), $this->relation('publishedBy')),
$this->mergeWhen($request->user()->isOwner(), [
$this->relation('foo'),
$this->relation('bar'),
]),
];
}
Default Serialization
By default, relationship objects are serialized with the links
member, and will include the self
and related
links.
Given our posts
example above, the resultant JSON:API relationships object will be:
{
"author": {
"links": {
"self": "http://localhost/api/v1/posts/123/relationships/author",
"related": "http://localhost/api/v1/posts/123/author"
}
},
"comments": {
"links": {
"self": "http://localhost/api/v1/posts/123/relationships/comments",
"related": "http://localhost/api/v1/posts/123/comments"
}
},
"tags": {
"links": {
"self": "http://localhost/api/v1/posts/123/relationships/tags",
"related": "http://localhost/api/v1/posts/123/tags"
}
}
}
The data
member of each relationship object will only be included if the relationship data has been requested by the client using an Include Path.
For example, if the client requested that the author
and tags
relationships were included, the resultant JSON:API relationships object would be:
{
"author": {
"data": {
"type": "users",
"id": "456"
},
"links": {
"self": "http://localhost/api/v1/posts/123/relationships/author",
"related": "http://localhost/api/v1/posts/123/author"
}
},
"comments": {
"links": {
"self": "http://localhost/api/v1/posts/123/relationships/comments",
"related": "http://localhost/api/v1/posts/123/comments"
}
},
"tags": {
"data": [
{
"type": "tags",
"id": "1"
},
{
"type": "tags",
"id": "3"
}
],
"links": {
"self": "http://localhost/api/v1/posts/123/relationships/tags",
"related": "http://localhost/api/v1/posts/123/tags"
}
}
}
The rest of this chapter describes how to customise the relationship serialization.
Links
The self
and related
links are automatically included in the serialized relationship object. The URL is calculated by appending the either /relationships/{FIELD-NAME}
(for the self
link) or /{FIELD-NAME}
(for the related
link) to the resource's self
URL.
TIP
The resource's self
URL is calculated on the resource class, i.e. PostResource
in our example. See the Links Chapter if you need to customise the URL.
The relationship's URL field name is obtained from the schema field. By default, we dash-case the field name when using it in URLs. You can override this on your schema if needed.
Removing Links
If you do not want the relationship object to have a self
link, use the withoutSelfLink
method:
$this->relation('author')->withoutSelfLink()
Likewise, if you do not want the relationship object to have a related
link, use the withoutRelatedLink
method:
$this->relation('author')->withoutRelatedLink()
If you want to remove both the self
and related
links, you can use the withoutLinks
method:
$this->relation('author')->withoutLinks()
Data
By default, we only include the data
member in the relationship object if the client has requested it via the Include Path query parameter. This is because the data
member provides resource linkage in a JSON:API Compound Document. It allows a client to link together all of the included resource objects within the document. Therefore the data
member is only required in a relationship object if the related resource will be in the top-level included
member of the Compound Document.
If the data
member is being serialized because of an include path, our schema classes will already have taken care of eager loading the related models, so that you do not get "N+1" query problems.
Specifying Data
By default we return the value of the model relationship to calculate the data
member of the relationship. If you need the data to be something else, use the withData
method:
$this->relation('author')->withData($this->someOtherValue)
Typically you would probably want to defer the calculation of data, so that it is only calculated if it will be serialized in the relationship. To do this, provide a Closure
to the withData
method:
$this->relation('author')->withData(fn() => $this->someOtherValue)
TIP
Relationship data
may be omitted if the client has not requested it via an include path. Additionally, a relationship may not be serialized if a client requests Sparse Fieldsets and does not request the named relationship field.
When manually returning data, you need to return a value that our encoder can convert to a JSON:API resource identifier. For a to-one relationship, this means you need to return either a related model or null
. For a to-many relation, you need to return an iterable of related models.
Showing Eager Loaded Data
By default data
will only be included if it was requested via an include path query parameter. However, you may want to include the data if the related model is eager loaded.
For example, on our posts
resource the related author
may have already been loaded during authorization (e.g. to check whether the related author is the current signed-in user).
In this scenario we can show the eager loaded data
by using the showDataIfLoaded
method:
$this->relation('author')->showDataIfLoaded()
Note that this will not include the related resource in the included
member of the JSON:API document. The included
resources are always determined by the include path query parameter.
Always Including Data
If you would always like your relationship object to have the data
member, use the alwaysShowData
method:
$this->relation('author')->alwaysShowData()
Note that this will not include the related resource in the included
member of the JSON:API document. The included
resources are always determined by the include path query parameter.
WARNING
If you are using the alwaysShowData
method, you will need to ensure that the relation is always eager loaded to avoid "N+1" database query problems. See the Eager Loading chapter for details.
Meta
Relationship objects may also contain a meta
member. This contains any non-standard meta-information about the relationship.
To add meta
to the relationship object, use the withMeta
method to provide an array:
$this->relation('author')->withMeta(['foo' => 'bar'])
If the meta needs to be calculated, it is best to provide a Closure
so that the calculation is only incurred if the relationship is being serialized:
$this->relation('author')->withMeta(fn() => ['foo' => 'bar'])
TIP
A relationship may not be serialized if a client requests Sparse Fieldsets and does not request the named relationship field.