Skip to content

Commit e558e4a

Browse files
samatcdphpsa
andauthored
Implement API to many relations (#69)
* Implement API to many relations • Added method to process belongToMany and morphToMany relationships. * Added notes about relationship sync * Apply fixes from StyleCI (#70) Co-authored-by: Craig Smith <[email protected]> Co-authored-by: Craig Smith <[email protected]> Co-authored-by: Craig Smith <[email protected]>
1 parent 8952b45 commit e558e4a

File tree

5 files changed

+61
-29
lines changed

5 files changed

+61
-29
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,12 @@ Simply add a `protected static $mapResources` to your `Resource` to define which
156156
];
157157
```
158158

159+
- You can automatically update and create related records for most types of relationships. Just include the related resource name in your POST or PUT request.
160+
161+
For `BelongsToMany` or `MorphToMany` relationships, you can choose the sync strategy. By default, this will take an *additive* strategy. That is to say, related records sent will be ADDED to any existing related records. On a request-by-request basis, you can opt for a *sync* strategy which will remove the pivot for any related records not listed in the request. Note the actual related record will not be removed, just the pivot entry.
162+
163+
To opt for the *sync* behavaiour, set `?sync[field]=true` in your request.
164+
159165
## Sorting
160166

161167
- Sorts can be passed as comma list aswell, ie `sort=age asc` or `sort=age asc,name desc,eyes` - generates sql of `sort age asc` and `sort age asc, name desc, eyes asc` respectively

src/Contracts/Parser.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ protected function parseFilterParams(): void
201201
protected function parseMethodParams($request): void
202202
{
203203
foreach ($this->/** @scrutinizer ignore-call */ getAllowedScopes() as $scope) {
204-
if ($request->has(Helpers::snake($scope)) || $request->has(Helpers::camel($scope)) ) {
205-
$value = $request->has(Helpers::snake($scope)) ? $request->get(Helpers::snake($scope)) : $request->get(Helpers::camel($scope));
204+
if ($request->has(Helpers::snake($scope)) || $request->has(Helpers::camel($scope))) {
205+
$value = $request->has(Helpers::snake($scope)) ? $request->get(Helpers::snake($scope)) : $request->get(Helpers::camel($scope));
206206
call_user_func([$this->repository, Helpers::camel($scope)], $value);
207207
}
208208
}

src/Contracts/Relationships.php

+48-20
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ protected function storeRelated($item, array $includes, array $data): void
8888
$relation = $item->$with();
8989
$type = class_basename(get_class($relation));
9090

91-
if (! in_array($type, ['HasOne', 'HasMany', 'BelongsTo', 'BelongsToMany', 'MorphOne','MorphMany'])) {
91+
if (! in_array($type, ['HasOne', 'HasMany', 'BelongsTo', 'MorphOne', 'MorphMany', 'MorphTo', 'MorphToMany', 'BelongsToMany'])) {
9292
throw new ApiException("$type mapping not implemented yet");
9393
}
9494

@@ -106,10 +106,12 @@ protected function storeRelated($item, array $includes, array $data): void
106106
$this->processHasRelation($relation, $relatedRecords, $item);
107107
break;
108108
case 'BelongsTo':
109-
$this->processBelongsToRelation($relation, [$relatedRecords], $item, $data);
109+
case 'MorphTo':
110+
$this->processBelongsToRelation($relation, $relatedRecords, $item, $data);
110111
break;
111112
case 'BelongsToMany':
112-
$this->processBelongsToRelation($relation, $relatedRecords, $item, $data);
113+
case 'MorphToMany':
114+
$this->processBelongsToManyRelation($relation, $relatedRecords, $item, $data, $with);
113115
break;
114116
}
115117
}
@@ -157,33 +159,59 @@ protected function processHasRelation($relation, array $relatedRecords, $item):
157159
}
158160
}
159161

160-
protected function processBelongsToRelation($relation, array $relatedRecords, $item, array $data): void
162+
protected function processBelongsToRelation($relation, $relatedRecord, $item, array $data): void
161163
{
162164
$ownerKey = $relation->getOwnerKeyName();
163165
$localKey = $relation->getForeignKeyName();
164166

165167
$model = $relation->getRelated();
166168

169+
if (! isset($relatedRecord[$ownerKey])) {
170+
$relatedRecord[$ownerKey] = $item->getAttribute($localKey);
171+
}
172+
if ($relatedRecord[$ownerKey]) {
173+
$existanceCheck = [$ownerKey => $relatedRecord[$ownerKey]];
174+
$relation->associate(
175+
$model->updateOrCreate($existanceCheck, $relatedRecord)
176+
);
177+
} elseif (isset($data[$localKey])) {
178+
$existanceCheck = [$ownerKey => $data[$localKey]];
179+
$relation->associate(
180+
$model->updateOrCreate($existanceCheck, $relatedRecord)
181+
);
182+
} else {
183+
$relation->associate(
184+
$model->create($relatedRecord)
185+
);
186+
}
187+
$item->save();
188+
}
189+
190+
protected function processBelongsToManyRelation($relation, array $relatedRecords, $item, array $data, $with): void
191+
{
192+
$parentKey = $relation->getParentKeyName();
193+
$relatedKey = $relation->getRelatedKeyName();
194+
195+
$model = $relation->getRelated();
196+
$sync = collect();
197+
167198
foreach ($relatedRecords as $relatedRecord) {
168-
if (! isset($relatedRecord[$ownerKey])) {
169-
$relatedRecord[$ownerKey] = $item->getAttribute($localKey);
199+
if (! isset($relatedRecord[$parentKey])) {
200+
$relatedRecord[$parentKey] = $item->getAttribute($relatedKey);
170201
}
171-
if ($relatedRecord[$ownerKey]) {
172-
$existanceCheck = [$ownerKey => $relatedRecord[$ownerKey]];
173-
$relation->associate(
174-
$model->updateOrCreate($existanceCheck, $relatedRecord)
175-
);
176-
} elseif (isset($data[$localKey])) {
177-
$existanceCheck = [$ownerKey => $data[$localKey]];
178-
$relation->associate(
179-
$model->updateOrCreate($existanceCheck, $relatedRecord)
180-
);
202+
if ($relatedRecord[$parentKey]) {
203+
$existanceCheck = [$parentKey => $relatedRecord[$parentKey]];
204+
$sync->push($model->updateOrCreate($existanceCheck, $relatedRecord));
205+
} elseif (isset($data[$relatedKey])) {
206+
$existanceCheck = [$parentKey => $data[$relatedKey]];
207+
$sync->push($model->updateOrCreate($existanceCheck, $relatedRecord));
181208
} else {
182-
$relation->associate(
183-
$model->create($relatedRecord)
184-
);
209+
$sync->push($model->create($relatedRecord));
185210
}
186-
$item->save();
187211
}
212+
213+
$detach = filter_var(request()->get('sync')[$with] ?? false, FILTER_VALIDATE_BOOLEAN);
214+
215+
$relation->sync($sync->pluck('id'), $detach);
188216
}
189217
}

src/Http/Resources/Contracts/AllowableFields.php

+3-4
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,14 @@ protected function mapRelatedResources($resources)
7171

7272
protected function mapFieldData($request, $fields)
7373
{
74-
7574
$data = parent::toArray($request);
7675

77-
$missing = array_filter($fields, function($field) use ($data){
76+
$missing = array_filter($fields, function ($field) use ($data) {
7877
return array_key_exists(Helpers::camel($field), $data) || array_key_exists(Helpers::snake($field), $data) ? false : true;
7978
});
8079

81-
foreach($missing as $field){
82-
if(method_exists($this->resource, 'get' . Helpers::camel($field) . 'Attribute')){
80+
foreach ($missing as $field) {
81+
if (method_exists($this->resource, 'get'.Helpers::camel($field).'Attribute')) {
8382
$data[$field] = $this->resource->{ Helpers::camel($field)};
8483
}
8584
}

src/Repository/BaseRepository.php

+2-3
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33
namespace Phpsa\LaravelApiController\Repository;
44

5-
use Illuminate\Database\Eloquent\Model;
65
use Illuminate\Database\Eloquent\Collection;
6+
use Illuminate\Database\Eloquent\Model;
7+
use Illuminate\Database\Eloquent\ModelNotFoundException;
78
use Illuminate\Support\Collection as BaseCollection;
89
use Phpsa\LaravelApiController\Exceptions\ApiException;
9-
use Illuminate\Database\Eloquent\ModelNotFoundException;
10-
use Illuminate\Pagination\LengthAwarePaginator;
1110

1211
/**
1312
* Class BaseRepository.

0 commit comments

Comments
 (0)