Skip to content

Commit 2663cb0

Browse files
[Feature] Add has-one-through relationship (#429)
Adds a has-one-through relationship to work with the Eloquent relation of the same name that was added in Laravel 5.8.
1 parent 91dcd5f commit 2663cb0

File tree

18 files changed

+623
-9
lines changed

18 files changed

+623
-9
lines changed

docs/basics/adapters.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ for Eloquent models. The relationship types available are `belongsTo`, `hasOne`,
224224
| Eloquent | JSON API |
225225
| :-- | :-- |
226226
| `hasOne` | `hasOne` |
227+
| `hasOneThrough` | `hasOneThrough` |
227228
| `belongsTo` | `belongsTo` |
228229
| `hasMany` | `hasMany` |
229230
| `belongsToMany` | `hasMany` |
@@ -338,15 +339,16 @@ class Adapter extends AbstractAdapter
338339
}
339340
```
340341

341-
#### Has-Many-Through
342+
#### Has-One-Through and Has-Many-Through
342343

343-
The JSON API `hasManyThrough` relation can be used for an Eloquent `hasManyThrough` relation. The important thing
344-
to note about this relationship is it is **read-only**. This is because the relationship can be modified in your API
345-
by modifying the intermediary model. For example, a `countries` resource might have many `posts` resources through
346-
an intermediate `users` resource. The relationship is effectively modified by creating and deleting posts and/or a
347-
user changing which country they are associated to.
344+
The JSON API `hasOneThrough` and `hasManyThrough` relations can be used for an Eloquent `hasOneThrough`
345+
and `hasManyThrough` relation. The important thing to note about these relationships is that both are **read-only**.
346+
This is because the relationship can be modified in your API by modifying the intermediary model.
347+
For example, a `countries` resource might have many `posts` resources through an intermediate `users` resource.
348+
The relationship is effectively modified by creating and deleting posts and/or a user changing which country they
349+
are associated to.
348350

349-
Define a has-many-through relationship on an adapter as follows:
351+
Use the `hasOneThrough()` or `hasManyThrough()` methods on your adapter as follows:
350352

351353
```php
352354
class Adapter extends AbstractAdapter

src/Eloquent/AbstractAdapter.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,15 @@ protected function hasOne($modelKey = null)
555555
return new HasOne($modelKey ?: $this->guessRelation());
556556
}
557557

558+
/**
559+
* @param string|null $modelKey
560+
* @return HasOneThrough
561+
*/
562+
protected function hasOneThrough($modelKey = null)
563+
{
564+
return new HasOneThrough($modelKey ?: $this->guessRelation());
565+
}
566+
558567
/**
559568
* @param string|null $modelKey
560569
* @return HasMany

src/Eloquent/HasOneThrough.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
/**
3+
* Copyright 2019 Cloud Creativity Limited
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace CloudCreativity\LaravelJsonApi\Eloquent;
19+
20+
use CloudCreativity\LaravelJsonApi\Exceptions\RuntimeException;
21+
use Illuminate\Database\Eloquent\Relations;
22+
use Neomerx\JsonApi\Contracts\Encoder\Parameters\EncodingParametersInterface;
23+
24+
class HasOneThrough extends BelongsTo
25+
{
26+
27+
/**
28+
* @inheritDoc
29+
*/
30+
public function update($record, array $relationship, EncodingParametersInterface $parameters)
31+
{
32+
throw new RuntimeException('Modifying a has-one-through Eloquent relation is not supported.');
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
*/
38+
public function replace($record, array $relationship, EncodingParametersInterface $parameters)
39+
{
40+
throw new RuntimeException('Modifying a has-one-through Eloquent relation is not supported.');
41+
}
42+
43+
/**
44+
* @inheritdoc
45+
*/
46+
protected function acceptRelation($relation)
47+
{
48+
return $relation instanceof Relations\HasOneThrough;
49+
}
50+
}

tests/dummy/app/History.php

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace DummyApp;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7+
8+
class History extends Model
9+
{
10+
11+
/**
12+
* @var array
13+
*/
14+
protected $fillable = ['detail'];
15+
16+
/**
17+
* @return BelongsTo
18+
*/
19+
public function user(): BelongsTo
20+
{
21+
return $this->belongsTo(User::class);
22+
}
23+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php
2+
/**
3+
* Copyright 2019 Cloud Creativity Limited
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace DummyApp\JsonApi\Histories;
19+
20+
use CloudCreativity\LaravelJsonApi\Eloquent\AbstractAdapter;
21+
use DummyApp\History;
22+
use Illuminate\Support\Collection;
23+
24+
class Adapter extends AbstractAdapter
25+
{
26+
27+
/**
28+
* Adapter constructor.
29+
*/
30+
public function __construct()
31+
{
32+
parent::__construct(new History());
33+
}
34+
35+
/**
36+
* @inheritDoc
37+
*/
38+
protected function filter($query, Collection $filters)
39+
{
40+
// TODO: Implement filter() method.
41+
}
42+
43+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
namespace DummyApp\JsonApi\Histories;
4+
5+
use DummyApp\History;
6+
use Neomerx\JsonApi\Schema\SchemaProvider;
7+
8+
class Schema extends SchemaProvider
9+
{
10+
11+
/**
12+
* @var string
13+
*/
14+
protected $resourceType = 'histories';
15+
16+
/**
17+
* @param History $resource
18+
* @return string
19+
*/
20+
public function getId($resource)
21+
{
22+
return (string) $resource->getRouteKey();
23+
}
24+
25+
/**
26+
* @param History $resource
27+
* @return array
28+
*/
29+
public function getAttributes($resource)
30+
{
31+
return ['detail' => $resource->detail];
32+
}
33+
34+
/**
35+
* @param History $resource
36+
* @param bool $isPrimary
37+
* @param array $includeRelationships
38+
* @return array
39+
*/
40+
public function getRelationships($resource, $isPrimary, array $includeRelationships)
41+
{
42+
return [
43+
'user' => [
44+
self::SHOW_SELF => false,
45+
self::SHOW_RELATED => false,
46+
self::SHOW_DATA => isset($includeRelationships['user']),
47+
self::DATA => static function () use ($resource) {
48+
return $resource->user;
49+
},
50+
],
51+
];
52+
}
53+
54+
55+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
/**
3+
* Copyright 2019 Cloud Creativity Limited
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace DummyApp\JsonApi\Histories;
19+
20+
use CloudCreativity\LaravelJsonApi\Validation\AbstractValidators;
21+
22+
class Validators extends AbstractValidators
23+
{
24+
25+
/**
26+
* @var array
27+
*/
28+
protected $allowedIncludePaths = ['user'];
29+
30+
/**
31+
* @var array
32+
*/
33+
protected $allowedSortParameters = [
34+
'created-at',
35+
'updated-at',
36+
];
37+
38+
/**
39+
* @inheritDoc
40+
*/
41+
protected function rules($record = null): array
42+
{
43+
return [
44+
'detail' => ['required', 'string'],
45+
];
46+
}
47+
48+
/**
49+
* @inheritDoc
50+
*/
51+
protected function queryRules(): array
52+
{
53+
return [];
54+
}
55+
56+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* Copyright 2019 Cloud Creativity Limited
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
namespace DummyApp\JsonApi\Suppliers;
19+
20+
use CloudCreativity\LaravelJsonApi\Eloquent\AbstractAdapter;
21+
use CloudCreativity\LaravelJsonApi\Eloquent\HasOneThrough;
22+
use DummyApp\Supplier;
23+
use Illuminate\Support\Collection;
24+
25+
class Adapter extends AbstractAdapter
26+
{
27+
28+
/**
29+
* Adapter constructor.
30+
*/
31+
public function __construct()
32+
{
33+
parent::__construct(new Supplier());
34+
}
35+
36+
/**
37+
* @return HasOneThrough
38+
*/
39+
protected function userHistory(): HasOneThrough
40+
{
41+
return $this->hasOneThrough();
42+
}
43+
44+
/**
45+
* @inheritDoc
46+
*/
47+
protected function filter($query, Collection $filters)
48+
{
49+
// TODO: Implement filter() method.
50+
}
51+
52+
}

0 commit comments

Comments
 (0)