Skip to content

Commit c6e209f

Browse files
authored
Merge pull request #8 from Lomkit/morph-relationships
Morph relationships
2 parents 0c231b6 + e1516ff commit c6e209f

File tree

67 files changed

+5018
-78
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+5018
-78
lines changed

README.md

+2-4
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,10 @@ TODO
7676

7777
TODO
7878

79-
## Roadmap
79+
## Roadmap for the end of bêta (Estimated delivery October 2023)
8080

81-
- Morph support
82-
- Through relation support
8381
- Custom directives (Filters / sorting)
8482
- Actions / Metrics
85-
- Automatic Gates
83+
- Automatic Gates (with config customisation)
8684
- Aggregating
8785
- Automatic documentation with extension possible

src/Concerns/PerformsRestOperations.php

-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ public function destroy(DestroyRequest $request) {
6060
$resource->performDelete($request, $model);
6161
}
6262

63-
//@TODO: il faut prévoir de pouvoir load des relations ici ?
6463
return $resource::newResponse()
6564
->resource($resource)
6665
->responsable($models);

src/Concerns/Relations/HasPivotFields.php

+15
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
trait HasPivotFields
66
{
77
protected array $pivotFields = [];
8+
protected array $pivotRules = [];
89

910
public function getPivotFields() {
1011
return $this->pivotFields;
@@ -15,4 +16,18 @@ public function withPivotFields(array $pivotFields) {
1516
$this->pivotFields = $pivotFields;
1617
});
1718
}
19+
20+
public function withPivotRules(array $pivotRules) {
21+
return tap($this, function () use ($pivotRules) {
22+
$this->pivotRules = $pivotRules;
23+
});
24+
}
25+
26+
/**
27+
* @return array
28+
*/
29+
public function getPivotRules(): array
30+
{
31+
return $this->pivotRules;
32+
}
1833
}

src/Concerns/Resource/Relationable.php

-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public function relation($name) {
1919
return $relation->relation === $relationName;
2020
});
2121

22-
//TODO: be careful here, nested morph relation here are not supported, might need to protect params validator
2322
if ($isSubRelation && Str::contains($nestedRelation = Str::after($name, '.'), '.')) {
2423
return $relation->resource()->relation($nestedRelation);
2524
}

src/Concerns/Resource/Rulable.php

+4
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
trait Rulable
1111
{
12+
public function rules(RestRequest $request) {
13+
return [];
14+
}
15+
1216
public function createRules(RestRequest $request) {
1317
return [];
1418
}

src/Http/Requests/MutateRequest.php

+5-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
use Lomkit\Rest\Relations\BelongsToMany;
1414
use Lomkit\Rest\Relations\HasMany;
1515
use Lomkit\Rest\Relations\HasManyThrough;
16+
use Lomkit\Rest\Relations\MorphedByMany;
17+
use Lomkit\Rest\Relations\MorphMany;
18+
use Lomkit\Rest\Relations\MorphToMany;
1619
use Lomkit\Rest\Rules\CustomRulable;
1720
use Lomkit\Rest\Rules\Includable;
1821
use Lomkit\Rest\Rules\RequiredRelation;
@@ -70,15 +73,13 @@ protected function relationRules(Resource $resource, string $prefix = '', $loade
7073
) {
7174
$prefixRelation = $prefix.'.'.$relation->relation;
7275

73-
if ($relation instanceof BelongsToMany || $relation instanceof HasMany || $relation instanceof HasManyThrough) {
76+
if ($relation->hasMultipleEntries()) {
7477
$prefixRelation .= '.*';
7578
}
7679

7780
$rules = array_merge_recursive(
7881
$rules,
79-
[
80-
$prefix.'.'.$relation->relation => $relation->rules($resource)
81-
],
82+
$relation->rules($resource, $prefix.'.'.$relation->relation),
8283
$this->mutateRules($relation->resource(), $prefixRelation, array_merge($loadedRelations, [$relation->relation]))
8384
);
8485
}

src/Query/Traits/PerformSearch.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public function sort($field, $direction = 'asc') {
8585

8686
public function applySorts($sorts) {
8787
foreach ($sorts as $sort) {
88-
$this->sort($sort['field'], $sort['direction'] ?? 'asc');
88+
$this->sort($this->queryBuilder->getModel()->getTable().'.'.$sort['field'], $sort['direction'] ?? 'asc');
8989
}
9090
}
9191

src/Relations/BelongsToMany.php

+39-7
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,55 @@
55
use Closure;
66
use Illuminate\Database\Eloquent\Builder;
77
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Support\Arr;
9+
use Illuminate\Validation\Rule;
810
use Lomkit\Rest\Concerns\Relations\HasPivotFields;
911
use Lomkit\Rest\Contracts\QueryBuilder;
1012
use Lomkit\Rest\Contracts\RelationResource;
13+
use Lomkit\Rest\Http\Resource;
14+
use Lomkit\Rest\Relations\Traits\HasMultipleResults;
1115

1216
class BelongsToMany extends Relation implements RelationResource
1317
{
14-
use HasPivotFields;
18+
use HasPivotFields, HasMultipleResults;
19+
20+
public function rules(Resource $resource, string $prefix)
21+
{
22+
return array_merge(
23+
parent::rules($resource, $prefix),
24+
[
25+
$prefix.'.*.pivot' => [
26+
'prohibited_if:'.$prefix.'.*.operation,detach',
27+
'array:'.Arr::join($this->getPivotFields(), ',')
28+
]
29+
]
30+
);
31+
}
1532

1633
public function afterMutating(Model $model, Relation $relation, array $mutationRelations)
1734
{
1835
foreach ($mutationRelations[$relation->relation] as $mutationRelation) {
19-
$model
20-
->{$relation->relation}()
21-
->{$mutationRelation['operation'] === 'detach' ? 'detach' : 'attach'}(
22-
app()->make(QueryBuilder::class, ['resource' => $relation->resource()])
23-
->applyMutation($mutationRelation)
24-
);
36+
if ($mutationRelation['operation'] === 'detach') {
37+
$model
38+
->{$relation->relation}()
39+
->detach(
40+
app()->make(QueryBuilder::class, ['resource' => $relation->resource()])
41+
->applyMutation($mutationRelation)
42+
->getKey()
43+
);
44+
} else {
45+
$model
46+
->{$relation->relation}()
47+
->attach(
48+
[
49+
app()->make(QueryBuilder::class, ['resource' => $relation->resource()])
50+
->applyMutation($mutationRelation)
51+
->getKey()
52+
=>
53+
$mutationRelation['pivot'] ?? []
54+
]
55+
);
56+
}
2557
}
2658
}
2759
}

src/Relations/HasMany.php

+3
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,12 @@
77
use Illuminate\Database\Eloquent\Model;
88
use Lomkit\Rest\Contracts\QueryBuilder;
99
use Lomkit\Rest\Contracts\RelationResource;
10+
use Lomkit\Rest\Relations\Traits\HasMultipleResults;
1011

1112
class HasMany extends Relation implements RelationResource
1213
{
14+
use HasMultipleResults;
15+
1316
public function afterMutating(Model $model, Relation $relation, array $mutationRelations)
1417
{
1518
foreach ($mutationRelations[$relation->relation] as $mutationRelation) {

src/Relations/HasManyThrough.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,21 @@
88
use Lomkit\Rest\Contracts\QueryBuilder;
99
use Lomkit\Rest\Contracts\RelationResource;
1010
use Lomkit\Rest\Http\Resource;
11+
use Lomkit\Rest\Relations\Traits\HasMultipleResults;
1112

1213
class HasManyThrough extends Relation implements RelationResource
1314
{
15+
use HasMultipleResults;
16+
1417
public function afterMutating(Model $model, Relation $relation, array $mutationRelations)
1518
{
1619
throw new \RuntimeException('You can\'t mutate a \'HasManyThrough\' relation.');
1720
}
1821

19-
public function rules(Resource $resource)
22+
public function rules(Resource $resource, string $prefix)
2023
{
2124
return [
22-
'prohibited'
25+
$prefix => 'prohibited'
2326
];
2427
}
2528
}

src/Relations/HasOneOfMany.php

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Lomkit\Rest\Relations;
4+
5+
use Closure;
6+
use Illuminate\Database\Eloquent\Builder;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Lomkit\Rest\Contracts\QueryBuilder;
9+
use Lomkit\Rest\Contracts\RelationResource;
10+
11+
class HasOneOfMany extends Relation implements RelationResource
12+
{
13+
public function afterMutating(Model $model, Relation $relation, array $mutationRelations)
14+
{
15+
$attributes = [
16+
$model->{$relation->relation}()->getForeignKeyName() => $mutationRelations[$relation->relation]['operation'] === 'detach' ? null : $model->{$relation->relation}()->getParentKey()
17+
];
18+
19+
app()->make(QueryBuilder::class, ['resource' => $relation->resource()])
20+
->applyMutation($mutationRelations[$relation->relation], $attributes);
21+
}
22+
}

src/Relations/HasOneThrough.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ public function afterMutating(Model $model, Relation $relation, array $mutationR
1616
throw new \RuntimeException('You can\'t mutate a \'HasOneThrough\' relation.');
1717
}
1818

19-
public function rules(Resource $resource)
19+
public function rules(Resource $resource, string $prefix)
2020
{
2121
return [
22-
'prohibited'
22+
$prefix => 'prohibited'
2323
];
2424
}
2525
}

src/Relations/MorphMany.php

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Lomkit\Rest\Relations;
4+
5+
use Closure;
6+
use Illuminate\Database\Eloquent\Builder;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Lomkit\Rest\Contracts\QueryBuilder;
9+
use Lomkit\Rest\Contracts\RelationResource;
10+
use Lomkit\Rest\Relations\Traits\HasMultipleResults;
11+
12+
class MorphMany extends MorphRelation implements RelationResource
13+
{
14+
use HasMultipleResults;
15+
16+
public function afterMutating(Model $model, Relation $relation, array $mutationRelations)
17+
{
18+
foreach ($mutationRelations[$relation->relation] as $mutationRelation) {
19+
$attributes = [
20+
$model->{$relation->relation}()->getForeignKeyName() => $mutationRelation['operation'] === 'detach' ? null : $model->{$relation->relation}()->getParentKey(),
21+
$model->{$relation->relation}()->getMorphType() => $model::class
22+
];
23+
24+
app()->make(QueryBuilder::class, ['resource' => $relation->resource()])
25+
->applyMutation($mutationRelation, $attributes);
26+
}
27+
}
28+
}

src/Relations/MorphOne.php

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace Lomkit\Rest\Relations;
4+
5+
use Closure;
6+
use Illuminate\Database\Eloquent\Builder;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Lomkit\Rest\Contracts\QueryBuilder;
9+
use Lomkit\Rest\Contracts\RelationResource;
10+
11+
class MorphOne extends MorphRelation implements RelationResource
12+
{
13+
public function afterMutating(Model $model, Relation $relation, array $mutationRelations)
14+
{
15+
$attributes = [
16+
$model->{$relation->relation}()->getForeignKeyName() => $mutationRelations[$relation->relation]['operation'] === 'detach' ? null : $model->{$relation->relation}()->getParentKey(),
17+
$model->{$relation->relation}()->getMorphType() => $model::class
18+
];
19+
20+
app()->make(QueryBuilder::class, ['resource' => $relation->resource()])
21+
->applyMutation($mutationRelations[$relation->relation], $attributes);
22+
}
23+
}

src/Relations/MorphOneOfMany.php

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
namespace Lomkit\Rest\Relations;
4+
5+
use Closure;
6+
use Illuminate\Database\Eloquent\Builder;
7+
use Lomkit\Rest\Contracts\RelationResource;
8+
9+
class MorphOneOfMany extends MorphRelation implements RelationResource
10+
{
11+
12+
}

src/Relations/MorphRelation.php

-15
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,4 @@
1111
class MorphRelation extends Relation
1212
{
1313
use Makeable;
14-
15-
public function __construct($relation, $types)
16-
{
17-
$this->relation = $relation;
18-
$this->types = $types;
19-
}
20-
21-
// @TODO: handle morphs in general
22-
// public function filter(Builder $query, $relation, $operator, $value, $boolean = 'and', Closure $callback = null)
23-
// {
24-
// return $query->hasMorph($query->getModel()->getTable().'.'.Str::beforeLast($relation, '.'), $this->types, '>=', 1, $boolean, function (Builder $query) use ($value, $operator, $relation, $callback) {
25-
// $query->where(Str::afterLast($relation, '.'), $operator, $value);
26-
// $callback($query);
27-
// });
28-
// }
2914
}

src/Relations/MorphTo.php

+31
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,40 @@
44

55
use Closure;
66
use Illuminate\Database\Eloquent\Builder;
7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Validation\Rule;
9+
use Lomkit\Rest\Contracts\QueryBuilder;
710
use Lomkit\Rest\Contracts\RelationResource;
11+
use Lomkit\Rest\Http\Resource;
812

913
class MorphTo extends MorphRelation implements RelationResource
1014
{
15+
public function __construct($relation, array $types)
16+
{
17+
$this->relation = $relation;
18+
$this->types = $types;
19+
}
1120

21+
public function beforeMutating(Model $model, Relation $relation, array $mutationRelations)
22+
{
23+
$model
24+
->{$relation->relation}()
25+
->{$mutationRelations[$relation->relation]['operation'] === 'detach' ? 'dissociate' : 'associate'}(
26+
app()->make(QueryBuilder::class, ['resource' => new $mutationRelations[$relation->relation]['type']])
27+
->applyMutation($mutationRelations[$relation->relation])
28+
);
29+
}
30+
31+
public function rules(Resource $resource, string $prefix)
32+
{
33+
return [
34+
...parent::rules($resource, $prefix),
35+
$prefix.'.type' => [
36+
'required_with:'.$prefix,
37+
Rule::in(
38+
$this->types
39+
)
40+
]
41+
];
42+
}
1243
}

0 commit comments

Comments
 (0)