Skip to content

field action #415

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Actions/Action.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,17 @@
use Closure;
use Exception;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use JsonSerializable;

/**
* Class Action
* @method JsonResponse handle(Request $request, Model|Collection $models)
* @package Binaryk\LaravelRestify\Actions
*/
abstract class Action implements JsonSerializable
{
use AuthorizedToSee;
Expand Down
28 changes: 28 additions & 0 deletions src/Fields/Concerns/HasAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace Binaryk\LaravelRestify\Fields\Concerns;

use Binaryk\LaravelRestify\Actions\Action;

trait HasAction
{
protected ?Action $actionHandler = null;

public function action(Action $action): self
{
if (! $action->onlyOnShow()) {
$key = $action::$uriKey;

abort(400, "The action $key should be only for show.");
}

$this->actionHandler = $action;

return $this;
}

public function isActionable(): bool
{
return $this->actionHandler instanceof Action;
}
}
104 changes: 71 additions & 33 deletions src/Fields/Field.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,21 @@

namespace Binaryk\LaravelRestify\Fields;

use Binaryk\LaravelRestify\Fields\Concerns\HasAction;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Repositories\Repository;
use Binaryk\LaravelRestify\Traits\Make;
use Closure;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Illuminate\Validation\Rules\Unique;
use JsonSerializable;

class Field extends OrganicField implements JsonSerializable
{
use Make;
use HasAction;

/**
* The resource associated with the field.
Expand Down Expand Up @@ -132,8 +135,8 @@ class Field extends OrganicField implements JsonSerializable
/**
* Create a new field.
*
* @param string|callable|null $attribute
* @param callable|null $resolveCallback
* @param string|callable|null $attribute
* @param callable|null $resolveCallback
*/
public function __construct($attribute, callable $resolveCallback = null)
{
Expand Down Expand Up @@ -162,7 +165,7 @@ public function indexCallback(Closure $callback)
}

/**
* @param Closure $callback
* @param Closure $callback
* @return $this
*/
public function showCallback(Closure $callback)
Expand Down Expand Up @@ -197,7 +200,7 @@ public function updateCallback(Closure $callback)
* Callback called when trying to fill this attribute, this callback will override the fill action, so make
* sure you assign the attribute to the model over this callback.
*
* @param Closure $callback
* @param Closure $callback
* @return $this
*/
public function fillCallback(Closure $callback)
Expand All @@ -210,9 +213,9 @@ public function fillCallback(Closure $callback)
/**
* Fill attribute with value from the request or delegate this action to the user defined callback.
*
* @param RestifyRequest $request
* @param RestifyRequest $request
* @param $model
* @param int|null $bulkRow
* @param int|null $bulkRow
* @return mixed|void
*/
public function fillAttribute(RestifyRequest $request, $model, int $bulkRow = null)
Expand Down Expand Up @@ -263,10 +266,10 @@ public function fillAttribute(RestifyRequest $request, $model, int $bulkRow = nu
/**
* Fill the model with value from the request.
*
* @param RestifyRequest $request
* @param RestifyRequest $request
* @param $model
* @param $attribute
* @param int|null $bulkRow
* @param int|null $bulkRow
*/
protected function fillAttributeFromRequest(RestifyRequest $request, $model, $attribute, int $bulkRow = null)
{
Expand All @@ -281,18 +284,18 @@ protected function fillAttributeFromRequest(RestifyRequest $request, $model, $at
tap(
($request->input($attribute) ?? $request[$attribute]),
fn ($value) => $model->{$this->attribute} = $request->has($attribute)
? $value
: $model->{$this->attribute}
? $value
: $model->{$this->attribute}
);
}

/**
* Fill the model with value from the callback.
*
* @param RestifyRequest $request
* @param RestifyRequest $request
* @param $model
* @param $attribute
* @param int|null $bulkRow
* @param int|null $bulkRow
*/
protected function fillAttributeFromCallback(RestifyRequest $request, $model, $attribute, int $bulkRow = null)
{
Expand All @@ -304,7 +307,7 @@ protected function fillAttributeFromCallback(RestifyRequest $request, $model, $a
/**
* Fill the model with the value from value.
*
* @param RestifyRequest $request
* @param RestifyRequest $request
* @param $model
* @param $attribute
* @return Field
Expand Down Expand Up @@ -469,8 +472,8 @@ public function isSortable()
/**
* Resolve the attribute's value for display.
*
* @param mixed $repository
* @param string|null $attribute
* @param mixed $repository
* @param string|null $attribute
* @return Field|void
*/
public function resolveForShow($repository, $attribute = null)
Expand All @@ -486,9 +489,12 @@ public function resolveForShow($repository, $attribute = null)
if (! $this->showCallback) {
$this->resolve($repository, $attribute);
} elseif (is_callable($this->showCallback)) {
tap($this->value ?? $this->resolveAttribute($repository, $attribute), function ($value) use ($repository, $attribute) {
$this->value = call_user_func($this->showCallback, $value, $repository, $attribute);
});
tap(
$this->value ?? $this->resolveAttribute($repository, $attribute),
function ($value) use ($repository, $attribute) {
$this->value = call_user_func($this->showCallback, $value, $repository, $attribute);
}
);
}

return $this;
Expand All @@ -509,9 +515,12 @@ public function resolveForIndex($repository, $attribute = null)
if (! $this->indexCallback) {
$this->resolve($repository, $attribute);
} elseif (is_callable($this->indexCallback)) {
tap($this->value ?? $this->resolveAttribute($repository, $attribute), function ($value) use ($repository, $attribute) {
$this->value = call_user_func($this->indexCallback, $value, $repository, $attribute);
});
tap(
$this->value ?? $this->resolveAttribute($repository, $attribute),
function ($value) use ($repository, $attribute) {
$this->value = call_user_func($this->indexCallback, $value, $repository, $attribute);
}
);
}

return $this;
Expand Down Expand Up @@ -543,8 +552,8 @@ public function resolve($repository, $attribute = null)
/**
* Resolve the given attribute from the given repository.
*
* @param mixed $repository
* @param string $attribute
* @param mixed $repository
* @param string $attribute
* @return mixed
*/
protected function resolveAttribute($repository, $attribute)
Expand Down Expand Up @@ -599,7 +608,7 @@ public function default($callback)
/**
* Resolve the default value for the field.
*
* @param RestifyRequest $request
* @param RestifyRequest $request
* @return callable|mixed
*/
protected function resolveDefaultValue(RestifyRequest $request)
Expand All @@ -614,7 +623,7 @@ protected function resolveDefaultValue(RestifyRequest $request)
/**
* Define the callback that should be used to resolve the field's value.
*
* @param callable $resolveCallback
* @param callable $resolveCallback
* @return $this
*/
public function resolveCallback(callable $resolveCallback)
Expand All @@ -638,21 +647,50 @@ public function afterStore(Closure $callback)
return $this;
}

public function invokeAfter(RestifyRequest $request, $repository)
{
if ($request->isStoreRequest() && is_callable($this->afterStoreCallback)) {
call_user_func($this->afterStoreCallback, data_get($repository, $this->attribute), $repository, $request);
public function invokeAfter(RestifyRequest $request, Model $model): void
{
if ($request->isStoreRequest()) {
$request->repository()
->collectFields($request)
->forStore($request, $request->repository())
->withActions($request, $this)
->authorizedStore($request)
->each(fn (Field $field) => $field->actionHandler->handle($request, $model));

if (is_callable($this->afterStoreCallback)) {
call_user_func(
$this->afterStoreCallback,
data_get($model, $this->attribute),
$model,
$request
);
}
}

if ($request->isUpdateRequest() && is_callable($this->afterUpdateCallback)) {
call_user_func($this->afterUpdateCallback, $this->resolveAttribute($repository, $this->attribute), $this->valueBeforeUpdate, $repository, $request);
if ($request->isUpdateRequest()) {
$request->repository()
->collectFields($request)
->forUpdate($request, $request->repository())
->withActions($request, $this)
->authorizedUpdate($request)
->each(fn (Field $field) => $field->actionHandler->handle($request, $model));

if (is_callable($this->afterUpdateCallback)) {
call_user_func(
$this->afterUpdateCallback,
$this->resolveAttribute($model, $this->attribute),
$this->valueBeforeUpdate,
$model,
$request
);
}
}
}

/**
* Indicate whatever the input is hidden or not.
*
* @param bool $callback
* @param bool $callback
* @return $this
*/
public function hidden($callback = true)
Expand All @@ -668,7 +706,7 @@ public function hidden($callback = true)
/**
* Force set values when store/update.
*
* @param callable|string $value
* @param callable|string $value
* @return $this
*/
public function value($value)
Expand Down
14 changes: 14 additions & 0 deletions src/Fields/FieldCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,20 @@ public function forStore(RestifyRequest $request, $repository): self
})->values();
}

public function withActions(RestifyRequest $request, $repository): self
{
return $this
->filter(fn (Field $field) => $field->isActionable())
->values();
}

public function withoutActions(RestifyRequest $request, $repository): self
{
return $this
->reject(fn (Field $field) => $field->isActionable())
->values();
}

public function forStoreBulk(RestifyRequest $request, $repository): self
{
return $this->filter(function (Field $field) use ($repository, $request) {
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Requests/Concerns/InteractWithRepositories.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public function repository($key = null): Repository
});

return $repository::isMock()
? $repository::getMock()::resolveWith($repository::newModel())
? $repository::getMock()
: $repository::resolveWith($repository::newModel());
}

Expand Down
2 changes: 2 additions & 0 deletions src/Repositories/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@ public function store(RestifyRequest $request)
$this->resource,
$fields = $this->collectFields($request)
->forStore($request, $this)
->withoutActions($request, $this)
->authorizedStore($request)
->merge($this->collectFields($request)->forBelongsTo($request))
);
Expand Down Expand Up @@ -682,6 +683,7 @@ public function update(RestifyRequest $request, $repositoryId)
DB::transaction(function () use ($request) {
$fields = $this->collectFields($request)
->forUpdate($request, $this)
->withoutActions($request, $this)
->authorizedUpdate($request)
->merge($this->collectFields($request)->forBelongsTo($request));

Expand Down
52 changes: 52 additions & 0 deletions tests/Actions/FieldActionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Binaryk\LaravelRestify\Tests\Actions;

use Binaryk\LaravelRestify\Actions\Action;
use Binaryk\LaravelRestify\Fields\Field;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post;
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository;
use Binaryk\LaravelRestify\Tests\IntegrationTest;
use Illuminate\Testing\Fluent\AssertableJson;

class FieldActionTest extends IntegrationTest
{
/** * @test */
public function can_use_actionable_field(): void
{
$action = new class extends Action {
public bool $showOnShow = true;

public function handle(RestifyRequest $request, Post $post)
{
$description = $request->input('description');

$post->update([
'description' => 'Actionable ' . $description,
]);
}
};

PostRepository::partialMock()
->shouldReceive('fieldsForStore')
->andreturn([
Field::new('title'),

Field::new('description')->action($action),
]);

$this
->withoutExceptionHandling()
->postJson(PostRepository::to(), [
'description' => 'Description',
'title' => $updated = 'Title',
])
->assertJson(
fn (AssertableJson $json) => $json
->where('data.attributes.title', $updated)
->where('data.attributes.description', 'Actionable Description')
->etc()
);
}
}