Skip to content

Commit 4a2e4d5

Browse files
authored
field action (#415)
* Fields actions. * Fix styling * Actionable fields. * Fix styling Co-authored-by: binaryk <[email protected]>
1 parent 6e95e3f commit 4a2e4d5

File tree

7 files changed

+175
-34
lines changed

7 files changed

+175
-34
lines changed

src/Actions/Action.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@
1414
use Closure;
1515
use Exception;
1616
use Illuminate\Database\Eloquent\Model;
17+
use Illuminate\Http\JsonResponse;
1718
use Illuminate\Http\Request;
19+
use Illuminate\Support\Collection;
1820
use Illuminate\Support\Str;
1921
use JsonSerializable;
2022

23+
/**
24+
* Class Action
25+
* @method JsonResponse handle(Request $request, Model|Collection $models)
26+
* @package Binaryk\LaravelRestify\Actions
27+
*/
2128
abstract class Action implements JsonSerializable
2229
{
2330
use AuthorizedToSee;

src/Fields/Concerns/HasAction.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Fields\Concerns;
4+
5+
use Binaryk\LaravelRestify\Actions\Action;
6+
7+
trait HasAction
8+
{
9+
protected ?Action $actionHandler = null;
10+
11+
public function action(Action $action): self
12+
{
13+
if (! $action->onlyOnShow()) {
14+
$key = $action::$uriKey;
15+
16+
abort(400, "The action $key should be only for show.");
17+
}
18+
19+
$this->actionHandler = $action;
20+
21+
return $this;
22+
}
23+
24+
public function isActionable(): bool
25+
{
26+
return $this->actionHandler instanceof Action;
27+
}
28+
}

src/Fields/Field.php

Lines changed: 71 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,21 @@
22

33
namespace Binaryk\LaravelRestify\Fields;
44

5+
use Binaryk\LaravelRestify\Fields\Concerns\HasAction;
56
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
67
use Binaryk\LaravelRestify\Repositories\Repository;
78
use Binaryk\LaravelRestify\Traits\Make;
89
use Closure;
910
use Illuminate\Contracts\Validation\Rule;
11+
use Illuminate\Database\Eloquent\Model;
1012
use Illuminate\Support\Str;
1113
use Illuminate\Validation\Rules\Unique;
1214
use JsonSerializable;
1315

1416
class Field extends OrganicField implements JsonSerializable
1517
{
1618
use Make;
19+
use HasAction;
1720

1821
/**
1922
* The resource associated with the field.
@@ -132,8 +135,8 @@ class Field extends OrganicField implements JsonSerializable
132135
/**
133136
* Create a new field.
134137
*
135-
* @param string|callable|null $attribute
136-
* @param callable|null $resolveCallback
138+
* @param string|callable|null $attribute
139+
* @param callable|null $resolveCallback
137140
*/
138141
public function __construct($attribute, callable $resolveCallback = null)
139142
{
@@ -162,7 +165,7 @@ public function indexCallback(Closure $callback)
162165
}
163166

164167
/**
165-
* @param Closure $callback
168+
* @param Closure $callback
166169
* @return $this
167170
*/
168171
public function showCallback(Closure $callback)
@@ -197,7 +200,7 @@ public function updateCallback(Closure $callback)
197200
* Callback called when trying to fill this attribute, this callback will override the fill action, so make
198201
* sure you assign the attribute to the model over this callback.
199202
*
200-
* @param Closure $callback
203+
* @param Closure $callback
201204
* @return $this
202205
*/
203206
public function fillCallback(Closure $callback)
@@ -210,9 +213,9 @@ public function fillCallback(Closure $callback)
210213
/**
211214
* Fill attribute with value from the request or delegate this action to the user defined callback.
212215
*
213-
* @param RestifyRequest $request
216+
* @param RestifyRequest $request
214217
* @param $model
215-
* @param int|null $bulkRow
218+
* @param int|null $bulkRow
216219
* @return mixed|void
217220
*/
218221
public function fillAttribute(RestifyRequest $request, $model, int $bulkRow = null)
@@ -263,10 +266,10 @@ public function fillAttribute(RestifyRequest $request, $model, int $bulkRow = nu
263266
/**
264267
* Fill the model with value from the request.
265268
*
266-
* @param RestifyRequest $request
269+
* @param RestifyRequest $request
267270
* @param $model
268271
* @param $attribute
269-
* @param int|null $bulkRow
272+
* @param int|null $bulkRow
270273
*/
271274
protected function fillAttributeFromRequest(RestifyRequest $request, $model, $attribute, int $bulkRow = null)
272275
{
@@ -281,18 +284,18 @@ protected function fillAttributeFromRequest(RestifyRequest $request, $model, $at
281284
tap(
282285
($request->input($attribute) ?? $request[$attribute]),
283286
fn ($value) => $model->{$this->attribute} = $request->has($attribute)
284-
? $value
285-
: $model->{$this->attribute}
287+
? $value
288+
: $model->{$this->attribute}
286289
);
287290
}
288291

289292
/**
290293
* Fill the model with value from the callback.
291294
*
292-
* @param RestifyRequest $request
295+
* @param RestifyRequest $request
293296
* @param $model
294297
* @param $attribute
295-
* @param int|null $bulkRow
298+
* @param int|null $bulkRow
296299
*/
297300
protected function fillAttributeFromCallback(RestifyRequest $request, $model, $attribute, int $bulkRow = null)
298301
{
@@ -304,7 +307,7 @@ protected function fillAttributeFromCallback(RestifyRequest $request, $model, $a
304307
/**
305308
* Fill the model with the value from value.
306309
*
307-
* @param RestifyRequest $request
310+
* @param RestifyRequest $request
308311
* @param $model
309312
* @param $attribute
310313
* @return Field
@@ -469,8 +472,8 @@ public function isSortable()
469472
/**
470473
* Resolve the attribute's value for display.
471474
*
472-
* @param mixed $repository
473-
* @param string|null $attribute
475+
* @param mixed $repository
476+
* @param string|null $attribute
474477
* @return Field|void
475478
*/
476479
public function resolveForShow($repository, $attribute = null)
@@ -486,9 +489,12 @@ public function resolveForShow($repository, $attribute = null)
486489
if (! $this->showCallback) {
487490
$this->resolve($repository, $attribute);
488491
} elseif (is_callable($this->showCallback)) {
489-
tap($this->value ?? $this->resolveAttribute($repository, $attribute), function ($value) use ($repository, $attribute) {
490-
$this->value = call_user_func($this->showCallback, $value, $repository, $attribute);
491-
});
492+
tap(
493+
$this->value ?? $this->resolveAttribute($repository, $attribute),
494+
function ($value) use ($repository, $attribute) {
495+
$this->value = call_user_func($this->showCallback, $value, $repository, $attribute);
496+
}
497+
);
492498
}
493499

494500
return $this;
@@ -509,9 +515,12 @@ public function resolveForIndex($repository, $attribute = null)
509515
if (! $this->indexCallback) {
510516
$this->resolve($repository, $attribute);
511517
} elseif (is_callable($this->indexCallback)) {
512-
tap($this->value ?? $this->resolveAttribute($repository, $attribute), function ($value) use ($repository, $attribute) {
513-
$this->value = call_user_func($this->indexCallback, $value, $repository, $attribute);
514-
});
518+
tap(
519+
$this->value ?? $this->resolveAttribute($repository, $attribute),
520+
function ($value) use ($repository, $attribute) {
521+
$this->value = call_user_func($this->indexCallback, $value, $repository, $attribute);
522+
}
523+
);
515524
}
516525

517526
return $this;
@@ -543,8 +552,8 @@ public function resolve($repository, $attribute = null)
543552
/**
544553
* Resolve the given attribute from the given repository.
545554
*
546-
* @param mixed $repository
547-
* @param string $attribute
555+
* @param mixed $repository
556+
* @param string $attribute
548557
* @return mixed
549558
*/
550559
protected function resolveAttribute($repository, $attribute)
@@ -599,7 +608,7 @@ public function default($callback)
599608
/**
600609
* Resolve the default value for the field.
601610
*
602-
* @param RestifyRequest $request
611+
* @param RestifyRequest $request
603612
* @return callable|mixed
604613
*/
605614
protected function resolveDefaultValue(RestifyRequest $request)
@@ -614,7 +623,7 @@ protected function resolveDefaultValue(RestifyRequest $request)
614623
/**
615624
* Define the callback that should be used to resolve the field's value.
616625
*
617-
* @param callable $resolveCallback
626+
* @param callable $resolveCallback
618627
* @return $this
619628
*/
620629
public function resolveCallback(callable $resolveCallback)
@@ -638,21 +647,50 @@ public function afterStore(Closure $callback)
638647
return $this;
639648
}
640649

641-
public function invokeAfter(RestifyRequest $request, $repository)
642-
{
643-
if ($request->isStoreRequest() && is_callable($this->afterStoreCallback)) {
644-
call_user_func($this->afterStoreCallback, data_get($repository, $this->attribute), $repository, $request);
650+
public function invokeAfter(RestifyRequest $request, Model $model): void
651+
{
652+
if ($request->isStoreRequest()) {
653+
$request->repository()
654+
->collectFields($request)
655+
->forStore($request, $request->repository())
656+
->withActions($request, $this)
657+
->authorizedStore($request)
658+
->each(fn (Field $field) => $field->actionHandler->handle($request, $model));
659+
660+
if (is_callable($this->afterStoreCallback)) {
661+
call_user_func(
662+
$this->afterStoreCallback,
663+
data_get($model, $this->attribute),
664+
$model,
665+
$request
666+
);
667+
}
645668
}
646669

647-
if ($request->isUpdateRequest() && is_callable($this->afterUpdateCallback)) {
648-
call_user_func($this->afterUpdateCallback, $this->resolveAttribute($repository, $this->attribute), $this->valueBeforeUpdate, $repository, $request);
670+
if ($request->isUpdateRequest()) {
671+
$request->repository()
672+
->collectFields($request)
673+
->forUpdate($request, $request->repository())
674+
->withActions($request, $this)
675+
->authorizedUpdate($request)
676+
->each(fn (Field $field) => $field->actionHandler->handle($request, $model));
677+
678+
if (is_callable($this->afterUpdateCallback)) {
679+
call_user_func(
680+
$this->afterUpdateCallback,
681+
$this->resolveAttribute($model, $this->attribute),
682+
$this->valueBeforeUpdate,
683+
$model,
684+
$request
685+
);
686+
}
649687
}
650688
}
651689

652690
/**
653691
* Indicate whatever the input is hidden or not.
654692
*
655-
* @param bool $callback
693+
* @param bool $callback
656694
* @return $this
657695
*/
658696
public function hidden($callback = true)
@@ -668,7 +706,7 @@ public function hidden($callback = true)
668706
/**
669707
* Force set values when store/update.
670708
*
671-
* @param callable|string $value
709+
* @param callable|string $value
672710
* @return $this
673711
*/
674712
public function value($value)

src/Fields/FieldCollection.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,20 @@ public function forStore(RestifyRequest $request, $repository): self
7878
})->values();
7979
}
8080

81+
public function withActions(RestifyRequest $request, $repository): self
82+
{
83+
return $this
84+
->filter(fn (Field $field) => $field->isActionable())
85+
->values();
86+
}
87+
88+
public function withoutActions(RestifyRequest $request, $repository): self
89+
{
90+
return $this
91+
->reject(fn (Field $field) => $field->isActionable())
92+
->values();
93+
}
94+
8195
public function forStoreBulk(RestifyRequest $request, $repository): self
8296
{
8397
return $this->filter(function (Field $field) use ($repository, $request) {

src/Http/Requests/Concerns/InteractWithRepositories.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function repository($key = null): Repository
4747
});
4848

4949
return $repository::isMock()
50-
? $repository::getMock()::resolveWith($repository::newModel())
50+
? $repository::getMock()
5151
: $repository::resolveWith($repository::newModel());
5252
}
5353

src/Repositories/Repository.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,7 @@ public function store(RestifyRequest $request)
609609
$this->resource,
610610
$fields = $this->collectFields($request)
611611
->forStore($request, $this)
612+
->withoutActions($request, $this)
612613
->authorizedStore($request)
613614
->merge($this->collectFields($request)->forBelongsTo($request))
614615
);
@@ -682,6 +683,7 @@ public function update(RestifyRequest $request, $repositoryId)
682683
DB::transaction(function () use ($request) {
683684
$fields = $this->collectFields($request)
684685
->forUpdate($request, $this)
686+
->withoutActions($request, $this)
685687
->authorizedUpdate($request)
686688
->merge($this->collectFields($request)->forBelongsTo($request));
687689

tests/Actions/FieldActionTest.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace Binaryk\LaravelRestify\Tests\Actions;
4+
5+
use Binaryk\LaravelRestify\Actions\Action;
6+
use Binaryk\LaravelRestify\Fields\Field;
7+
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
8+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post;
9+
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository;
10+
use Binaryk\LaravelRestify\Tests\IntegrationTest;
11+
use Illuminate\Testing\Fluent\AssertableJson;
12+
13+
class FieldActionTest extends IntegrationTest
14+
{
15+
/** * @test */
16+
public function can_use_actionable_field(): void
17+
{
18+
$action = new class extends Action {
19+
public bool $showOnShow = true;
20+
21+
public function handle(RestifyRequest $request, Post $post)
22+
{
23+
$description = $request->input('description');
24+
25+
$post->update([
26+
'description' => 'Actionable ' . $description,
27+
]);
28+
}
29+
};
30+
31+
PostRepository::partialMock()
32+
->shouldReceive('fieldsForStore')
33+
->andreturn([
34+
Field::new('title'),
35+
36+
Field::new('description')->action($action),
37+
]);
38+
39+
$this
40+
->withoutExceptionHandling()
41+
->postJson(PostRepository::to(), [
42+
'description' => 'Description',
43+
'title' => $updated = 'Title',
44+
])
45+
->assertJson(
46+
fn (AssertableJson $json) => $json
47+
->where('data.attributes.title', $updated)
48+
->where('data.attributes.description', 'Actionable Description')
49+
->etc()
50+
);
51+
}
52+
}

0 commit comments

Comments
 (0)