Skip to content

Commit 214a3f1

Browse files
authored
Store / update bulk endpoints (#210)
* Store bulk endpoint * Apply fixes from StyleCI (#211) * docs * Bulk update * Update bulk * Apply fixes from StyleCI (#212)
1 parent 3c05d4d commit 214a3f1

18 files changed

+667
-40
lines changed

docs/docs/3.0/repository-pattern/repository-pattern.md

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ You have available the follow endpoints:
4343
| GET | `/restify-api/posts` | index |
4444
| GET | `/restify-api/posts/{post}` | show |
4545
| POST | `/restify-api/posts` | store |
46+
| POST | `/restify-api/posts/bulk` | store multiple |
47+
| POST | `/restify-api/posts/bulk/update` | store multiple |
4648
| PATCH | `/restify-api/posts/{post}` | update |
4749
| PUT | `/restify-api/posts/{post}` | update |
4850
| POST | `/restify-api/posts/{post}` | update |
@@ -133,7 +135,7 @@ public static function collectMiddlewares(RestifyRequest $request): ?Collection
133135

134136
## Dependency injection
135137

136-
The Laravel [service container](https://laravel.com/docs/6.x/container) is used to resolve all Laravel Restify repositories.
138+
The Laravel [service container](https://laravel.com/docs/7.x/container) is used to resolve all Laravel Restify repositories.
137139
As a result, you are able to type-hint any dependencies your `Repository` may need in its constructor.
138140
The declared dependencies will automatically be resolved and injected into the repository instance:
139141

@@ -208,6 +210,15 @@ entire logic of a specific action. Let's say your `save` method has to do someth
208210
}
209211
```
210212

213+
### store bulk
214+
215+
```php
216+
public function storeBulk(Binaryk\LaravelRestify\Http\Requests\RepositoryStoreBulkRequest $request)
217+
{
218+
// Silence is golden
219+
}
220+
```
221+
211222
### update
212223

213224
```php
@@ -217,6 +228,17 @@ entire logic of a specific action. Let's say your `save` method has to do someth
217228
}
218229
```
219230

231+
### update bulk
232+
233+
// $row is the payload row to be updated
234+
235+
```php
236+
public function updateBulk(RestifyRequest $request, $repositoryId, int $row)
237+
{
238+
// Silence is golden
239+
}
240+
```
241+
220242
### destroy
221243

222244
```php
@@ -592,4 +614,71 @@ you may want to force eager load a relationship in terms of using it in fields,
592614
public static $with = ['posts'];
593615
```
594616

617+
## Store bulk flow
618+
619+
However, the `store` method is a common one, the `store bulk` requires a bit of attention.
620+
621+
### Bulk field validations
622+
623+
Similar with `store` and `update` methods, `bulk` rules has their own field rule definition:
624+
625+
```php
626+
->storeBulkRules('required', function () {}, Rule::in('posts:id'))
627+
```
628+
629+
The validation rules will be merged with the rules provided into the `rules()` method. The validation will be performed
630+
by using native Laravel validator, so you will have exactly the same experience. The validation `messages` could still be used as usual.
631+
632+
### Bulk Payload
633+
634+
The payload for a bulk store should contain an array of objects:
635+
636+
```json
637+
[
638+
{
639+
"title": "First post"
640+
},
641+
{
642+
"title": "Second post"
643+
}
644+
]
645+
```
646+
647+
### Bulk after store
648+
649+
After storing an entity, the repository will call the static `bulkStored` method from the repository, so you can override:
650+
651+
```php
652+
public static function storedBulk(Collection $repositories, $request)
653+
{
654+
//
655+
}
656+
```
657+
658+
## Update bulk flow
659+
660+
As the store bulk, the update bulk uses DB transaction to perform the action. So you can make sure that even all entries, even no one where updated.
661+
662+
### Bulk update field validations
663+
664+
```php
665+
->updateBulkRules('required', function () {}, Rule::in('posts:id'))
666+
```
667+
668+
### Bulk Payload
669+
670+
The payload for a bulk update should contain an array of objects. Each object SHOULD contain an `id` key, based on this, the Laravel Restify will find the entity:
671+
672+
```json
673+
[
674+
{
675+
"id": 1,
676+
"title": "First post"
677+
},
678+
{
679+
"id": 2,
680+
"title": "Second post"
681+
}
682+
]
683+
```
595684

routes/api.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
use Binaryk\LaravelRestify\Http\Controllers\RepositoryFilterController;
1111
use Binaryk\LaravelRestify\Http\Controllers\RepositoryIndexController;
1212
use Binaryk\LaravelRestify\Http\Controllers\RepositoryShowController;
13+
use Binaryk\LaravelRestify\Http\Controllers\RepositoryStoreBulkController;
1314
use Binaryk\LaravelRestify\Http\Controllers\RepositoryStoreController;
15+
use Binaryk\LaravelRestify\Http\Controllers\RepositoryUpdateBulkController;
1416
use Binaryk\LaravelRestify\Http\Controllers\RepositoryUpdateController;
1517
use Illuminate\Support\Facades\Route;
1618

@@ -27,6 +29,8 @@
2729
// API CRUD
2830
Route::get('/{repository}', '\\'.RepositoryIndexController::class);
2931
Route::post('/{repository}', '\\'.RepositoryStoreController::class);
32+
Route::post('/{repository}/bulk', '\\'.RepositoryStoreBulkController::class);
33+
Route::post('/{repository}/bulk/update', '\\'.RepositoryUpdateBulkController::class);
3034
Route::get('/{repository}/{repositoryId}', '\\'.RepositoryShowController::class);
3135
Route::patch('/{repository}/{repositoryId}', '\\'.RepositoryUpdateController::class);
3236
Route::put('/{repository}/{repositoryId}', '\\'.RepositoryUpdateController::class);

src/Commands/stubs/policy.stub

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ class {{ class }}
4444
//
4545
}
4646

47+
/**
48+
* Determine whether the user can create multiple models at once.
49+
*
50+
* @param \App\User $user
51+
* @return mixed
52+
*/
53+
public function storeBulk(User $user)
54+
{
55+
//
56+
}
57+
4758
/**
4859
* Determine whether the user can update the model.
4960
*

src/Fields/Field.php

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ class Field extends OrganicField implements JsonSerializable
6161
*/
6262
public $storeCallback;
6363

64+
/**
65+
* Callback called when the value is filled from a store bulk, this callback will do not override the fill action.
66+
* @var Closure
67+
*/
68+
public $storeBulkCallback;
69+
6470
/**
6571
* Callback called when update.
6672
* @var Closure
@@ -165,6 +171,13 @@ public function storeCallback(Closure $callback)
165171
return $this;
166172
}
167173

174+
public function storeCallbackCallback(Closure $callback)
175+
{
176+
$this->storeBulkCallback = $callback;
177+
178+
return $this;
179+
}
180+
168181
public function updateCallback(Closure $callback)
169182
{
170183
$this->updateCallback = $callback;
@@ -191,9 +204,10 @@ public function fillCallback(Closure $callback)
191204
*
192205
* @param RestifyRequest $request
193206
* @param $model
207+
* @param int|null $bulkRow
194208
* @return mixed|void
195209
*/
196-
public function fillAttribute(RestifyRequest $request, $model)
210+
public function fillAttribute(RestifyRequest $request, $model, int $bulkRow = null)
197211
{
198212
$this->resolveValueBeforeUpdate($request, $model);
199213

@@ -205,28 +219,34 @@ public function fillAttribute(RestifyRequest $request, $model)
205219

206220
if (! $this->isHidden($request) && isset($this->fillCallback)) {
207221
return call_user_func(
208-
$this->fillCallback, $request, $model, $this->attribute
222+
$this->fillCallback, $request, $model, $this->attribute, $bulkRow
209223
);
210224
}
211225

212226
if (isset($this->appendCallback)) {
213-
return $this->fillAttributeFromAppend($request, $model, $this->attribute);
227+
return $this->fillAttributeFromAppend($request, $model, $this->attribute, $bulkRow);
214228
}
215229

216230
if ($request->isStoreRequest() && is_callable($this->storeCallback)) {
217231
return call_user_func(
218-
$this->storeCallback, $request, $model, $this->attribute
232+
$this->storeCallback, $request, $model, $this->attribute, $bulkRow
233+
);
234+
}
235+
236+
if ($request->isStoreBulkRequest() && is_callable($this->storeBulkCallback)) {
237+
return call_user_func(
238+
$this->storeBulkCallback, $request, $model, $this->attribute, $bulkRow
219239
);
220240
}
221241

222242
if ($request->isUpdateRequest() && is_callable($this->updateCallback)) {
223243
return call_user_func(
224-
$this->updateCallback, $request, $model, $this->attribute
244+
$this->updateCallback, $request, $model, $this->attribute, $bulkRow
225245
);
226246
}
227247

228248
$this->fillAttributeFromRequest(
229-
$request, $model, $this->attribute
249+
$request, $model, $this->attribute, $bulkRow
230250
);
231251
}
232252

@@ -236,11 +256,22 @@ public function fillAttribute(RestifyRequest $request, $model)
236256
* @param RestifyRequest $request
237257
* @param $model
238258
* @param $attribute
259+
* @param int|null $bulkRow
239260
*/
240-
protected function fillAttributeFromRequest(RestifyRequest $request, $model, $attribute)
261+
protected function fillAttributeFromRequest(RestifyRequest $request, $model, $attribute, int $bulkRow = null)
241262
{
242-
if ($request->exists($attribute) || $request->get($attribute)) {
243-
$model->{$attribute} = $request[$attribute] ?? $request->get($attribute);
263+
if (is_null($bulkRow)) {
264+
if ($request->exists($attribute) || $request->input($attribute)) {
265+
$model->{$attribute} = $request[$attribute] ?? $request->input($attribute);
266+
}
267+
268+
return;
269+
}
270+
271+
$bulkableAttribute = $bulkRow.'.'.$attribute;
272+
273+
if ($request->exists($bulkableAttribute) || $request->get($bulkableAttribute)) {
274+
$model->{$attribute} = $request[$bulkableAttribute] ?? $request->get($bulkableAttribute);
244275
}
245276
}
246277

@@ -286,6 +317,20 @@ public function storingRules($rules)
286317
return $this;
287318
}
288319

320+
public function storeBulkRules($rules)
321+
{
322+
$this->storingBulkRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules;
323+
324+
return $this;
325+
}
326+
327+
public function updateBulkRules($rules)
328+
{
329+
$this->updateBulkRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules;
330+
331+
return $this;
332+
}
333+
289334
/**
290335
* Alias for storingRules - to maintain it consistent.
291336
*
@@ -334,11 +379,21 @@ public function getStoringRules(): array
334379
return array_merge($this->rules, $this->storingRules);
335380
}
336381

382+
public function getStoringBulkRules(): array
383+
{
384+
return array_merge($this->rules, $this->storingBulkRules);
385+
}
386+
337387
public function getUpdatingRules(): array
338388
{
339389
return array_merge($this->rules, $this->updatingRules);
340390
}
341391

392+
public function getUpdatingBulkRules(): array
393+
{
394+
return array_merge($this->rules, $this->updateBulkRules);
395+
}
396+
342397
/**
343398
* Resolve the field's value for display.
344399
*

src/Fields/FieldCollection.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ public function authorizedUpdate(Request $request): self
2222
})->values();
2323
}
2424

25+
public function authorizedUpdateBulk(Request $request): self
26+
{
27+
return $this->filter(function (OrganicField $field) use ($request) {
28+
return $field->authorizedToUpdateBulk($request);
29+
})->values();
30+
}
31+
2532
public function authorizedStore(Request $request): self
2633
{
2734
return $this->filter(function (OrganicField $field) use ($request) {
@@ -57,10 +64,24 @@ public function forStore(RestifyRequest $request, $repository): self
5764
})->values();
5865
}
5966

67+
public function forStoreBulk(RestifyRequest $request, $repository): self
68+
{
69+
return $this->filter(function (Field $field) use ($repository, $request) {
70+
return $field->isShownOnStoreBulk($request, $repository);
71+
})->values();
72+
}
73+
6074
public function forUpdate(RestifyRequest $request, $repository): self
6175
{
6276
return $this->filter(function (Field $field) use ($repository, $request) {
6377
return $field->isShownOnUpdate($request, $repository);
6478
})->values();
6579
}
80+
81+
public function forUpdateBulk(RestifyRequest $request, $repository): self
82+
{
83+
return $this->filter(function (Field $field) use ($repository, $request) {
84+
return $field->isShownOnUpdateBulk($request, $repository);
85+
})->values();
86+
}
6687
}

0 commit comments

Comments
 (0)