From c4278c59676df48fd016972ff3fa58b7f11a90c6 Mon Sep 17 00:00:00 2001 From: Eduard Lupacescu Date: Sat, 16 Jan 2021 14:14:24 +0200 Subject: [PATCH 1/5] Searchble model. --- .../repository-pattern/repository-pattern.md | 6 +- src/Eager/Related.php | 65 ++++++++++- src/Repositories/Repository.php | 110 +++++++----------- src/Resolvable.php | 12 ++ .../Search/RepositorySearchService.php | 31 +++-- .../RepositoryIndexControllerTest.php | 4 +- .../Feature/Filters/FilterDefinitionTest.php | 3 +- 7 files changed, 136 insertions(+), 95 deletions(-) create mode 100644 src/Resolvable.php diff --git a/docs/docs/4.0/repository-pattern/repository-pattern.md b/docs/docs/4.0/repository-pattern/repository-pattern.md index f97dff36a..31af0da1c 100644 --- a/docs/docs/4.0/repository-pattern/repository-pattern.md +++ b/docs/docs/4.0/repository-pattern/repository-pattern.md @@ -817,9 +817,13 @@ eager load a relationship in terms of using it in fields, or whatever else: ```php // UserRepository.php -public static $with = ['posts']; +public static $withs = ['posts']; ``` +:::warn `withs` is not type +Laravel uses the `with` property on models, on repositories we use `$withs`, it's not a typo. +::: + ## Store bulk flow The bulk store means that you can create many entries at once, for example if you have a list of invoice entries, diff --git a/src/Eager/Related.php b/src/Eager/Related.php index 7b713b463..bb03e49a7 100644 --- a/src/Eager/Related.php +++ b/src/Eager/Related.php @@ -3,16 +3,30 @@ namespace Binaryk\LaravelRestify\Eager; use Binaryk\LaravelRestify\Fields\EagerField; +use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Repository; +use Binaryk\LaravelRestify\Resolvable; use Binaryk\LaravelRestify\Traits\Make; +use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Support\Arr; +use Illuminate\Support\Collection; +use Illuminate\Support\Str; use JsonSerializable; -class Related implements JsonSerializable +class Related implements JsonSerializable, Resolvable { use Make; private string $relation; + /** + * This is the default value. + * + * @var callable|string|int + */ + private $value; + private ?EagerField $field; public function __construct(string $relation, EagerField $field = null) @@ -23,7 +37,7 @@ public function __construct(string $relation, EagerField $field = null) public function isEager(): bool { - return ! is_null($this->field); + return !is_null($this->field); } public function getRelation(): string @@ -31,11 +45,58 @@ public function getRelation(): string return $this->relation; } + public function getValue() + { + return $this->value; + } + public function resolveField(Repository $repository): EagerField { return $this->field->resolve($repository); } + public function resolve(RestifyRequest $request, Repository $repository): self + { + if (Str::contains($this->getRelation(), '.')) { + $repository->resource->loadMissing($this->getRelation()); + + $key = Str::before($this->getRelation(), '.'); + + $this->value = Arr::get($repository->resource->relationsToArray(), $key); + + return $this; + } + + /** * To avoid circular relationships and deep stack calls, we will do not load eager fields. */ + if ($this->isEager() && $repository->isEagerState() === false) { + $this->value = $this->resolveField($repository)->value; + + return $this; + } + + $paginator = $repository->resource->relationLoaded($this->getRelation()) + ? $repository->resource->{$this->getRelation()} + : $repository->resource->{$this->getRelation()}(); + + switch ($paginator) { + case $paginator instanceof Builder: + $this->value = ($repository::$relatedCast)::fromBuilder($request, $paginator, $repository); + break; + case $paginator instanceof Relation: + $this->value = ($repository::$relatedCast)::fromRelation($request, $paginator, $repository); + break; + case $paginator instanceof Collection: + $this->value = $paginator; + break; + default: + dd('here'); + $this->value = $paginator; + } + + return $this; + } + + public function jsonSerialize() { return [ diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index e039f82be..7a655f0d8 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -283,7 +283,7 @@ public static function newModel(): Model public static function query(RestifyRequest $request) { - if (! $request->isViaRepository()) { + if (!$request->isViaRepository()) { return static::newModel()->query(); } @@ -450,16 +450,16 @@ public function resolveShowAttributes(RestifyRequest $request) { $fields = $this->collectFields($request) ->forShow($request, $this) - ->filter(fn (Field $field) => $field->authorize($request)) + ->filter(fn(Field $field) => $field->authorize($request)) ->when( $this->isEagerState(), function ($items) { - return $items->filter(fn (Field $field) => ! $field instanceof EagerField); + return $items->filter(fn(Field $field) => !$field instanceof EagerField); } ) - ->each(fn (Field $field) => $field->resolveForShow($this)) - ->map(fn (Field $field) => $field->serializeToValue($request)) - ->mapWithKeys(fn ($value) => $value) + ->each(fn(Field $field) => $field->resolveForShow($this)) + ->map(fn(Field $field) => $field->serializeToValue($request)) + ->mapWithKeys(fn($value) => $value) ->all(); if ($this instanceof Mergeable) { @@ -477,7 +477,7 @@ function ($items) { return false; } - if (! $field->authorize($request)) { + if (!$field->authorize($request)) { return false; } @@ -500,16 +500,16 @@ public function resolveIndexAttributes($request) $fields = $this ->collectFields($request) ->forIndex($request, $this) - ->filter(fn (Field $field) => $field->authorize($request)) + ->filter(fn(Field $field) => $field->authorize($request)) ->when( $this->eagerState, function ($items) { - return $items->filter(fn (Field $field) => ! $field instanceof EagerField); + return $items->filter(fn(Field $field) => !$field instanceof EagerField); } ) - ->each(fn (Field $field) => $field->resolveForIndex($this)) - ->map(fn (Field $field) => $field->serializeToValue($request)) - ->mapWithKeys(fn ($value) => $value) + ->each(fn(Field $field) => $field->resolveForIndex($this)) + ->map(fn(Field $field) => $field->serializeToValue($request)) + ->mapWithKeys(fn($value) => $value) ->all(); if ($this instanceof Mergeable) { @@ -527,7 +527,7 @@ function ($items) { return false; } - if (! $field->authorize($request)) { + if (!$field->authorize($request)) { return false; } @@ -555,10 +555,10 @@ public function resolveShowPivots(RestifyRequest $request): array } return $pivots - ->filter(fn (Field $field) => $field->authorize($request)) - ->each(fn (Field $field) => $field->resolve($this)) - ->map(fn (Field $field) => $field->serializeToValue($request)) - ->mapWithKeys(fn ($value) => $value) + ->filter(fn(Field $field) => $field->authorize($request)) + ->each(fn(Field $field) => $field->resolve($this)) + ->map(fn(Field $field) => $field->serializeToValue($request)) + ->mapWithKeys(fn($value) => $value) ->all(); } @@ -575,9 +575,7 @@ public function resolveIndexPivots(RestifyRequest $request): array */ public function resolveRelationships($request): array { - $withs = collect(); - - static::collectRelated() + return static::collectRelated() ->authorized($request) ->inRequest($request) ->when($request->isShowRequest(), function (RelatedCollection $collection) use ($request) { @@ -587,39 +585,9 @@ public function resolveRelationships($request): array return $collection->forIndex($request, $this); }) ->mapIntoRelated($request) - ->each(function (Related $related) use ($request, $withs) { - $relation = $related->getRelation(); - - if (Str::contains($relation, '.')) { - $this->resource->loadMissing($relation); - - return $withs->put($key = Str::before($relation, '.'), Arr::get($this->resource->relationsToArray(), $key)); - } - - /** * To avoid circular relationships and deep stack calls, we will do not load eager fields. */ - if ($related->isEager() && $this->isEagerState() === false) { - return $withs->put($relation, $related->resolveField($this)->value); - } - - $paginator = $this->resource->relationLoaded($relation) - ? $this->resource->{$relation} - : $this->resource->{$relation}(); - - collect([ - Builder::class => fn () => $withs->put($relation, (static::$relatedCast)::fromBuilder($request, $paginator, $this)), - - Relation::class => fn () => $withs->put($relation, (static::$relatedCast)::fromRelation($request, $paginator, $this)), - - Collection::class => fn () => $withs->put($relation, $paginator), - - Model::class => fn () => fn () => $withs->put($relation, $paginator), - - ])->first(fn ($fn, $class) => $paginator instanceof $class, - fn () => fn () => $withs->put($relation, $paginator) - )(); - }); - - return $withs->all(); + ->map(function (Related $related) use ($request) { + return $related->resolve($request, $this)->getValue(); + })->all(); } /** @@ -669,14 +637,14 @@ public function index(RestifyRequest $request) [ 'meta' => $this->when( $meta = $this->resolveIndexMainMeta( - $request, $models = $items->map(fn (self $repository) => $repository->resource), RepositoryCollection::meta($paginator->toArray()) + $request, $models = $items->map(fn(self $repository) => $repository->resource), RepositoryCollection::meta($paginator->toArray()) ), $meta ), 'links' => $this->when( $links = $this->resolveIndexLinks($request, $models, RepositoryCollection::paginationLinks($paginator->toArray())), $links ), - 'data' => $items->map(fn (self $repository) => $repository->serializeForIndex($request)), + 'data' => $items->map(fn(self $repository) => $repository->serializeForIndex($request)), ] ) ); @@ -734,7 +702,7 @@ public function store(RestifyRequest $request) ->save(); } - $fields->each(fn (Field $field) => $field->invokeAfter($request, $this->resource)); + $fields->each(fn(Field $field) => $field->invokeAfter($request, $this->resource)); }); static::stored($this->resource, $request); @@ -765,7 +733,7 @@ public function storeBulk(RepositoryStoreBulkRequest $request) $this->resource->save(); - $fields->each(fn (Field $field) => $field->invokeAfter($request, $this->resource)); + $fields->each(fn(Field $field) => $field->invokeAfter($request, $this->resource)); return $this->resource; }); @@ -798,7 +766,7 @@ public function update(RestifyRequest $request, $repositoryId) return $fields; })->each( - fn (Field $field) => $field->invokeAfter($request, $this->resource) + fn(Field $field) => $field->invokeAfter($request, $this->resource) ); return $this->response() @@ -827,7 +795,7 @@ public function attach(RestifyRequest $request, $repositoryId, Collection $pivot $eagerField = $this->authorizeBelongsToMany($request)->belongsToManyField($request); DB::transaction(function () use ($request, $pivots, $eagerField) { - $fields = $eagerField->collectPivotFields()->filter(fn ($pivotField) => $request->has($pivotField->attribute))->values(); + $fields = $eagerField->collectPivotFields()->filter(fn($pivotField) => $request->has($pivotField->attribute))->values(); $pivots->map(function ($pivot) use ($request, $fields, $eagerField) { static::validatorForAttach($request)->validate(); @@ -854,7 +822,7 @@ public function detach(RestifyRequest $request, $repositoryId, Collection $pivot $deleted = DB::transaction(function () use ($pivots, $eagerField, $request) { return $pivots - ->map(fn ($pivot) => $eagerField->authorizeToDetach($request, $pivot) && $pivot->delete()); + ->map(fn($pivot) => $eagerField->authorizeToDetach($request, $pivot) && $pivot->delete()); }); return $this->response() @@ -892,18 +860,18 @@ public function allowToUpdate(RestifyRequest $request, $payload = null): self public function allowToAttach(RestifyRequest $request, Collection $attachers): self { - $methodGuesser = 'attach'.Str::studly($request->relatedRepository); + $methodGuesser = 'attach' . Str::studly($request->relatedRepository); - $attachers->each(fn ($model) => $this->authorizeToAttach($request, $methodGuesser, $model)); + $attachers->each(fn($model) => $this->authorizeToAttach($request, $methodGuesser, $model)); return $this; } public function allowToDetach(RestifyRequest $request, Collection $attachers): self { - $methodGuesser = 'detach'.Str::studly($request->relatedRepository); + $methodGuesser = 'detach' . Str::studly($request->relatedRepository); - $attachers->each(fn ($model) => $this->authorizeToDetach($request, $methodGuesser, $model)); + $attachers->each(fn($model) => $this->authorizeToDetach($request, $methodGuesser, $model)); return $this; } @@ -993,7 +961,7 @@ public function response($content = '', $status = 200, array $headers = []): Res public function serializeForShow(RestifyRequest $request): array { return $this->filter([ - 'id' => $this->when(optional($this->resource)->id, fn () => $this->getId($request)), + 'id' => $this->when(optional($this->resource)->id, fn() => $this->getId($request)), 'type' => $this->when($type = $this->getType($request), $type), 'attributes' => $request->isShowRequest() ? $this->resolveShowAttributes($request) : $this->resolveIndexAttributes($request), 'relationships' => $this->when(value($related = $this->resolveRelationships($request)), $related), @@ -1007,7 +975,7 @@ public function serializeForIndex(RestifyRequest $request): array return $this->filter([ 'id' => $this->when($id = $this->getId($request), $id), 'type' => $this->when($type = $this->getType($request), $type), - 'attributes' => $this->when((bool) $attrs = $this->resolveIndexAttributes($request), $attrs), + 'attributes' => $this->when((bool)$attrs = $this->resolveIndexAttributes($request), $attrs), 'relationships' => $this->when(value($related = $this->resolveIndexRelationships($request)), $related), 'meta' => $this->when(value($meta = $this->resolveIndexMeta($request)), $meta), 'pivots' => $this->when(value($pivots = $this->resolveIndexPivots($request)), $pivots), @@ -1021,7 +989,7 @@ protected function getType(RestifyRequest $request): ?string protected function getId(RestifyRequest $request): ?string { - if (! static::$id) { + if (!static::$id) { return null; } @@ -1050,7 +1018,7 @@ private function modelAttributes(Request $request = null): Collection */ protected static function fillFields(RestifyRequest $request, Model $model, Collection $fields) { - return $fields->map(fn (Field $field) => $field->fillAttribute($request, $model)); + return $fields->map(fn(Field $field) => $field->fillAttribute($request, $model)); } protected static function fillBulkFields(RestifyRequest $request, Model $model, Collection $fields, int $bulkRow = null) @@ -1062,12 +1030,12 @@ protected static function fillBulkFields(RestifyRequest $request, Model $model, public static function uriTo(Model $model) { - return Str::replaceFirst('//', '/', Restify::path().'/'.static::uriKey().'/'.$model->getKey()); + return Str::replaceFirst('//', '/', Restify::path() . '/' . static::uriKey() . '/' . $model->getKey()); } public function availableFilters(RestifyRequest $request) { - return collect($this->filter($this->filters($request)))->each(fn (Filter $filter) => $filter->authorizedToSee($request)) + return collect($this->filter($this->filters($request)))->each(fn(Filter $filter) => $filter->authorizedToSee($request)) ->values(); } @@ -1106,7 +1074,7 @@ public function restifyjsSerialize(RestifyRequest $request): array 'sort' => static::collectFilters('sortables'), 'match' => static::collectFilters('matches'), 'searchables' => static::collectFilters('searchables'), - 'actions' => $this->resolveActions($request)->filter(fn (Action $action) => $action->isShownOnIndex( + 'actions' => $this->resolveActions($request)->filter(fn(Action $action) => $action->isShownOnIndex( $request, $this ))->values(), ]; diff --git a/src/Resolvable.php b/src/Resolvable.php new file mode 100644 index 000000000..c110c95b5 --- /dev/null +++ b/src/Resolvable.php @@ -0,0 +1,12 @@ +repository = $repository; - $query = $this->prepareMatchFields($request, $this->prepareSearchFields($request, $repository::query($request), $this->fixedInput), $this->fixedInput); + $query = $this->prepareMatchFields( + $request, + $this->prepareSearchFields($request, $this->prepareRelations($request, $repository::query($request)), $this->fixedInput), + $this->fixedInput); $query = $this->applyFilters($request, $repository, $query); @@ -39,15 +42,15 @@ public function prepareMatchFields(RestifyRequest $request, $query, $extra = []) foreach ($this->repository->getMatchByFields() as $key => $type) { $negation = false; - if ($request->has('-'.$key)) { + if ($request->has('-' . $key)) { $negation = true; } - if (! $request->has($negation ? '-'.$key : $key) && ! data_get($extra, "match.$key")) { + if (!$request->has($negation ? '-' . $key : $key) && !data_get($extra, "match.$key")) { continue; } - $match = $request->input($negation ? '-'.$key : $key, data_get($extra, "match.$key")); + $match = $request->input($negation ? '-' . $key : $key, data_get($extra, "match.$key")); if ($negation) { $key = Str::after($key, '-'); @@ -118,17 +121,9 @@ public function prepareOrders(RestifyRequest $request, $query) return $query; } - public function prepareRelations(RestifyRequest $request, $query, $extra = []) + public function prepareRelations(RestifyRequest $request, $query) { - $relations = array_merge($extra, explode(',', $request->input('related'))); - - foreach ($relations as $relation) { - if (in_array($relation, $this->repository->getWiths())) { - $query->with($relation); - } - } - - return $query; + return $query->with($this->repository->getWiths()); } public function prepareSearchFields(RestifyRequest $request, $query, $extra = []) @@ -175,17 +170,17 @@ public function prepareSearchFields(RestifyRequest $request, $query, $extra = [] protected function applyIndexQuery(RestifyRequest $request, Repository $repository) { - return fn ($query) => $repository::indexQuery($request, $query); + return fn($query) => $repository::indexQuery($request, $query); } protected function applyMainQuery(RestifyRequest $request, Repository $repository) { - return fn ($query) => $repository::mainQuery($request, $query->with($repository::getWiths())); + return fn($query) => $repository::mainQuery($request, $query->with($repository::getWiths())); } protected function applyFilters(RestifyRequest $request, Repository $repository, $query) { - if (! empty($request->filters)) { + if (!empty($request->filters)) { $filters = json_decode(base64_decode($request->filters), true); collect($filters) @@ -211,7 +206,7 @@ protected function applyFilters(RestifyRequest $request, Repository $repository, return $matchingFilter; }) ->filter() - ->each(fn (Filter $filter) => $filter->filter($request, $query, $filter->value)); + ->each(fn(Filter $filter) => $filter->filter($request, $query, $filter->value)); } return $query; diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index a1f50d4a5..07738d2d3 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -141,8 +141,8 @@ public function test_repository_with_nested_relations() $response = $this->getJson(CompanyRepository::uriKey().'?related=users.posts') ->assertOk(); - $this->assertCount(1, $response->json('data.0.relationships.users')); - $this->assertCount(1, $response->json('data.0.relationships.users.0.posts')); + $this->assertCount(1, $response->json('data.0.relationships')['users.posts']); + $this->assertCount(1, $response->json('data.0.relationships')['users.posts'][0]['posts']); } public function test_paginated_repository_with_relations() diff --git a/tests/Feature/Filters/FilterDefinitionTest.php b/tests/Feature/Filters/FilterDefinitionTest.php index 32653b9f1..3d2ee16ec 100644 --- a/tests/Feature/Filters/FilterDefinitionTest.php +++ b/tests/Feature/Filters/FilterDefinitionTest.php @@ -94,7 +94,8 @@ public function test_can_filter_using_belongs_to_field() ]), ]); - $json = $this->getJson(PostRepository::uriKey().'?related=user&sort=-users.name')->json(); + $json = $this->getJson(PostRepository::uriKey().'?related=user&sort=-users.name') + ->json(); $this->assertSame( 'Zez', From 507ea645804ca1a34f5c564dfc6cc12170674691 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Sat, 16 Jan 2021 14:14:52 +0200 Subject: [PATCH 2/5] Apply fixes from StyleCI (#355) --- src/Eager/Related.php | 3 +- src/Repositories/Repository.php | 73 +++++++++---------- src/Resolvable.php | 1 - .../Search/RepositorySearchService.php | 14 ++-- 4 files changed, 43 insertions(+), 48 deletions(-) diff --git a/src/Eager/Related.php b/src/Eager/Related.php index bb03e49a7..290bd43ca 100644 --- a/src/Eager/Related.php +++ b/src/Eager/Related.php @@ -37,7 +37,7 @@ public function __construct(string $relation, EagerField $field = null) public function isEager(): bool { - return !is_null($this->field); + return ! is_null($this->field); } public function getRelation(): string @@ -96,7 +96,6 @@ public function resolve(RestifyRequest $request, Repository $repository): self return $this; } - public function jsonSerialize() { return [ diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 7a655f0d8..2e2ac961c 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -24,15 +24,12 @@ use Binaryk\LaravelRestify\Traits\InteractWithSearch; use Binaryk\LaravelRestify\Traits\PerformsQueries; use Illuminate\Contracts\Pagination\LengthAwarePaginator; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Http\Request; use Illuminate\Http\Resources\ConditionallyLoadsAttributes; use Illuminate\Http\Resources\DelegatesToResource; use Illuminate\Pagination\AbstractPaginator; use Illuminate\Routing\Router; -use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; @@ -283,7 +280,7 @@ public static function newModel(): Model public static function query(RestifyRequest $request) { - if (!$request->isViaRepository()) { + if (! $request->isViaRepository()) { return static::newModel()->query(); } @@ -450,16 +447,16 @@ public function resolveShowAttributes(RestifyRequest $request) { $fields = $this->collectFields($request) ->forShow($request, $this) - ->filter(fn(Field $field) => $field->authorize($request)) + ->filter(fn (Field $field) => $field->authorize($request)) ->when( $this->isEagerState(), function ($items) { - return $items->filter(fn(Field $field) => !$field instanceof EagerField); + return $items->filter(fn (Field $field) => ! $field instanceof EagerField); } ) - ->each(fn(Field $field) => $field->resolveForShow($this)) - ->map(fn(Field $field) => $field->serializeToValue($request)) - ->mapWithKeys(fn($value) => $value) + ->each(fn (Field $field) => $field->resolveForShow($this)) + ->map(fn (Field $field) => $field->serializeToValue($request)) + ->mapWithKeys(fn ($value) => $value) ->all(); if ($this instanceof Mergeable) { @@ -477,7 +474,7 @@ function ($items) { return false; } - if (!$field->authorize($request)) { + if (! $field->authorize($request)) { return false; } @@ -500,16 +497,16 @@ public function resolveIndexAttributes($request) $fields = $this ->collectFields($request) ->forIndex($request, $this) - ->filter(fn(Field $field) => $field->authorize($request)) + ->filter(fn (Field $field) => $field->authorize($request)) ->when( $this->eagerState, function ($items) { - return $items->filter(fn(Field $field) => !$field instanceof EagerField); + return $items->filter(fn (Field $field) => ! $field instanceof EagerField); } ) - ->each(fn(Field $field) => $field->resolveForIndex($this)) - ->map(fn(Field $field) => $field->serializeToValue($request)) - ->mapWithKeys(fn($value) => $value) + ->each(fn (Field $field) => $field->resolveForIndex($this)) + ->map(fn (Field $field) => $field->serializeToValue($request)) + ->mapWithKeys(fn ($value) => $value) ->all(); if ($this instanceof Mergeable) { @@ -527,7 +524,7 @@ function ($items) { return false; } - if (!$field->authorize($request)) { + if (! $field->authorize($request)) { return false; } @@ -555,10 +552,10 @@ public function resolveShowPivots(RestifyRequest $request): array } return $pivots - ->filter(fn(Field $field) => $field->authorize($request)) - ->each(fn(Field $field) => $field->resolve($this)) - ->map(fn(Field $field) => $field->serializeToValue($request)) - ->mapWithKeys(fn($value) => $value) + ->filter(fn (Field $field) => $field->authorize($request)) + ->each(fn (Field $field) => $field->resolve($this)) + ->map(fn (Field $field) => $field->serializeToValue($request)) + ->mapWithKeys(fn ($value) => $value) ->all(); } @@ -637,14 +634,14 @@ public function index(RestifyRequest $request) [ 'meta' => $this->when( $meta = $this->resolveIndexMainMeta( - $request, $models = $items->map(fn(self $repository) => $repository->resource), RepositoryCollection::meta($paginator->toArray()) + $request, $models = $items->map(fn (self $repository) => $repository->resource), RepositoryCollection::meta($paginator->toArray()) ), $meta ), 'links' => $this->when( $links = $this->resolveIndexLinks($request, $models, RepositoryCollection::paginationLinks($paginator->toArray())), $links ), - 'data' => $items->map(fn(self $repository) => $repository->serializeForIndex($request)), + 'data' => $items->map(fn (self $repository) => $repository->serializeForIndex($request)), ] ) ); @@ -702,7 +699,7 @@ public function store(RestifyRequest $request) ->save(); } - $fields->each(fn(Field $field) => $field->invokeAfter($request, $this->resource)); + $fields->each(fn (Field $field) => $field->invokeAfter($request, $this->resource)); }); static::stored($this->resource, $request); @@ -733,7 +730,7 @@ public function storeBulk(RepositoryStoreBulkRequest $request) $this->resource->save(); - $fields->each(fn(Field $field) => $field->invokeAfter($request, $this->resource)); + $fields->each(fn (Field $field) => $field->invokeAfter($request, $this->resource)); return $this->resource; }); @@ -766,7 +763,7 @@ public function update(RestifyRequest $request, $repositoryId) return $fields; })->each( - fn(Field $field) => $field->invokeAfter($request, $this->resource) + fn (Field $field) => $field->invokeAfter($request, $this->resource) ); return $this->response() @@ -795,7 +792,7 @@ public function attach(RestifyRequest $request, $repositoryId, Collection $pivot $eagerField = $this->authorizeBelongsToMany($request)->belongsToManyField($request); DB::transaction(function () use ($request, $pivots, $eagerField) { - $fields = $eagerField->collectPivotFields()->filter(fn($pivotField) => $request->has($pivotField->attribute))->values(); + $fields = $eagerField->collectPivotFields()->filter(fn ($pivotField) => $request->has($pivotField->attribute))->values(); $pivots->map(function ($pivot) use ($request, $fields, $eagerField) { static::validatorForAttach($request)->validate(); @@ -822,7 +819,7 @@ public function detach(RestifyRequest $request, $repositoryId, Collection $pivot $deleted = DB::transaction(function () use ($pivots, $eagerField, $request) { return $pivots - ->map(fn($pivot) => $eagerField->authorizeToDetach($request, $pivot) && $pivot->delete()); + ->map(fn ($pivot) => $eagerField->authorizeToDetach($request, $pivot) && $pivot->delete()); }); return $this->response() @@ -860,18 +857,18 @@ public function allowToUpdate(RestifyRequest $request, $payload = null): self public function allowToAttach(RestifyRequest $request, Collection $attachers): self { - $methodGuesser = 'attach' . Str::studly($request->relatedRepository); + $methodGuesser = 'attach'.Str::studly($request->relatedRepository); - $attachers->each(fn($model) => $this->authorizeToAttach($request, $methodGuesser, $model)); + $attachers->each(fn ($model) => $this->authorizeToAttach($request, $methodGuesser, $model)); return $this; } public function allowToDetach(RestifyRequest $request, Collection $attachers): self { - $methodGuesser = 'detach' . Str::studly($request->relatedRepository); + $methodGuesser = 'detach'.Str::studly($request->relatedRepository); - $attachers->each(fn($model) => $this->authorizeToDetach($request, $methodGuesser, $model)); + $attachers->each(fn ($model) => $this->authorizeToDetach($request, $methodGuesser, $model)); return $this; } @@ -961,7 +958,7 @@ public function response($content = '', $status = 200, array $headers = []): Res public function serializeForShow(RestifyRequest $request): array { return $this->filter([ - 'id' => $this->when(optional($this->resource)->id, fn() => $this->getId($request)), + 'id' => $this->when(optional($this->resource)->id, fn () => $this->getId($request)), 'type' => $this->when($type = $this->getType($request), $type), 'attributes' => $request->isShowRequest() ? $this->resolveShowAttributes($request) : $this->resolveIndexAttributes($request), 'relationships' => $this->when(value($related = $this->resolveRelationships($request)), $related), @@ -975,7 +972,7 @@ public function serializeForIndex(RestifyRequest $request): array return $this->filter([ 'id' => $this->when($id = $this->getId($request), $id), 'type' => $this->when($type = $this->getType($request), $type), - 'attributes' => $this->when((bool)$attrs = $this->resolveIndexAttributes($request), $attrs), + 'attributes' => $this->when((bool) $attrs = $this->resolveIndexAttributes($request), $attrs), 'relationships' => $this->when(value($related = $this->resolveIndexRelationships($request)), $related), 'meta' => $this->when(value($meta = $this->resolveIndexMeta($request)), $meta), 'pivots' => $this->when(value($pivots = $this->resolveIndexPivots($request)), $pivots), @@ -989,7 +986,7 @@ protected function getType(RestifyRequest $request): ?string protected function getId(RestifyRequest $request): ?string { - if (!static::$id) { + if (! static::$id) { return null; } @@ -1018,7 +1015,7 @@ private function modelAttributes(Request $request = null): Collection */ protected static function fillFields(RestifyRequest $request, Model $model, Collection $fields) { - return $fields->map(fn(Field $field) => $field->fillAttribute($request, $model)); + return $fields->map(fn (Field $field) => $field->fillAttribute($request, $model)); } protected static function fillBulkFields(RestifyRequest $request, Model $model, Collection $fields, int $bulkRow = null) @@ -1030,12 +1027,12 @@ protected static function fillBulkFields(RestifyRequest $request, Model $model, public static function uriTo(Model $model) { - return Str::replaceFirst('//', '/', Restify::path() . '/' . static::uriKey() . '/' . $model->getKey()); + return Str::replaceFirst('//', '/', Restify::path().'/'.static::uriKey().'/'.$model->getKey()); } public function availableFilters(RestifyRequest $request) { - return collect($this->filter($this->filters($request)))->each(fn(Filter $filter) => $filter->authorizedToSee($request)) + return collect($this->filter($this->filters($request)))->each(fn (Filter $filter) => $filter->authorizedToSee($request)) ->values(); } @@ -1074,7 +1071,7 @@ public function restifyjsSerialize(RestifyRequest $request): array 'sort' => static::collectFilters('sortables'), 'match' => static::collectFilters('matches'), 'searchables' => static::collectFilters('searchables'), - 'actions' => $this->resolveActions($request)->filter(fn(Action $action) => $action->isShownOnIndex( + 'actions' => $this->resolveActions($request)->filter(fn (Action $action) => $action->isShownOnIndex( $request, $this ))->values(), ]; diff --git a/src/Resolvable.php b/src/Resolvable.php index c110c95b5..5d889ba08 100644 --- a/src/Resolvable.php +++ b/src/Resolvable.php @@ -2,7 +2,6 @@ namespace Binaryk\LaravelRestify; - use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Repository; diff --git a/src/Services/Search/RepositorySearchService.php b/src/Services/Search/RepositorySearchService.php index e49b85111..4de358d84 100644 --- a/src/Services/Search/RepositorySearchService.php +++ b/src/Services/Search/RepositorySearchService.php @@ -42,15 +42,15 @@ public function prepareMatchFields(RestifyRequest $request, $query, $extra = []) foreach ($this->repository->getMatchByFields() as $key => $type) { $negation = false; - if ($request->has('-' . $key)) { + if ($request->has('-'.$key)) { $negation = true; } - if (!$request->has($negation ? '-' . $key : $key) && !data_get($extra, "match.$key")) { + if (! $request->has($negation ? '-'.$key : $key) && ! data_get($extra, "match.$key")) { continue; } - $match = $request->input($negation ? '-' . $key : $key, data_get($extra, "match.$key")); + $match = $request->input($negation ? '-'.$key : $key, data_get($extra, "match.$key")); if ($negation) { $key = Str::after($key, '-'); @@ -170,17 +170,17 @@ public function prepareSearchFields(RestifyRequest $request, $query, $extra = [] protected function applyIndexQuery(RestifyRequest $request, Repository $repository) { - return fn($query) => $repository::indexQuery($request, $query); + return fn ($query) => $repository::indexQuery($request, $query); } protected function applyMainQuery(RestifyRequest $request, Repository $repository) { - return fn($query) => $repository::mainQuery($request, $query->with($repository::getWiths())); + return fn ($query) => $repository::mainQuery($request, $query->with($repository::getWiths())); } protected function applyFilters(RestifyRequest $request, Repository $repository, $query) { - if (!empty($request->filters)) { + if (! empty($request->filters)) { $filters = json_decode(base64_decode($request->filters), true); collect($filters) @@ -206,7 +206,7 @@ protected function applyFilters(RestifyRequest $request, Repository $repository, return $matchingFilter; }) ->filter() - ->each(fn(Filter $filter) => $filter->filter($request, $query, $filter->value)); + ->each(fn (Filter $filter) => $filter->filter($request, $query, $filter->value)); } return $query; From 78ac758db14634685ed5e9cc11068714145a8c42 Mon Sep 17 00:00:00 2001 From: Eduard Lupacescu Date: Sat, 16 Jan 2021 17:43:37 +0200 Subject: [PATCH 3/5] wip --- src/Eager/Related.php | 1 - src/Fields/Concerns/Attachable.php | 47 +++++++++++++++++-- .../Concerns/InteractsWithAttachers.php | 4 -- src/Repositories/Repository.php | 2 + 4 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/Eager/Related.php b/src/Eager/Related.php index bb03e49a7..708eefaa3 100644 --- a/src/Eager/Related.php +++ b/src/Eager/Related.php @@ -89,7 +89,6 @@ public function resolve(RestifyRequest $request, Repository $repository): self $this->value = $paginator; break; default: - dd('here'); $this->value = $paginator; } diff --git a/src/Fields/Concerns/Attachable.php b/src/Fields/Concerns/Attachable.php index 52c6d7398..4486a7e9d 100644 --- a/src/Fields/Concerns/Attachable.php +++ b/src/Fields/Concerns/Attachable.php @@ -9,6 +9,8 @@ use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\ValidationException; trait Attachable { @@ -17,6 +19,11 @@ trait Attachable */ private $canAttachCallback; + /** + * @var Closure + */ + private $validationCallback; + /** * @var Closure */ @@ -61,7 +68,7 @@ public function authorizeToAttach(RestifyRequest $request) $request, $request->findModelOrFail()->{$request->viaRelationship ?? $request->relatedRepository}(), $relatedRepositoryId ); - if (! $this->authorizedToAttach($request, $pivot)) { + if (!$this->authorizedToAttach($request, $pivot)) { throw new AuthorizationException(); } }); @@ -78,7 +85,7 @@ public function authorizedToDetach(RestifyRequest $request, Pivot $pivot): bool public function authorizeToDetach(RestifyRequest $request, Pivot $pivot) { - if (! $this->authorizedToDetach($request, $pivot)) { + if (!$this->authorizedToDetach($request, $pivot)) { throw new AuthorizationException(); } @@ -112,7 +119,7 @@ public function initializePivot(RestifyRequest $request, $relationship, $related ]); } - $fields = $this->collectPivotFields()->filter(fn ($pivotField) => $request->has($pivotField->attribute))->values(); + $fields = $this->collectPivotFields()->filter(fn($pivotField) => $request->has($pivotField->attribute))->values(); $repository = $request->repository(); @@ -140,4 +147,38 @@ public function collectPivotFields(): PivotsCollection { return PivotsCollection::make($this->pivotFields); } + + public function validationCallback(Closure $validationCallback) + { + $this->validationCallback = $validationCallback; + + return $this; + } + + public function validate(RestifyRequest $request, $pivot): bool + { + if (is_callable($this->validationCallback)) { + throw_unless( + call_user_func($this->validationCallback, $request, $pivot), + ValidationException::withMessages([__('Invalid data.')]) + ); + } + + return true; + } + + public function unique(): self + { + $this->validationCallback = function (RestifyRequest $request, $pivot) { + $valid = $this->getRelation($request->repository()) + ->where($pivot->toArray()) + ->count() === 0; + + throw_unless($valid, ValidationException::withMessages([__('Invalid data. The relation must be unique.')])); + + return $valid; + }; + + return $this; + } } diff --git a/src/Repositories/Concerns/InteractsWithAttachers.php b/src/Repositories/Concerns/InteractsWithAttachers.php index 1a8863059..6467bb0d2 100644 --- a/src/Repositories/Concerns/InteractsWithAttachers.php +++ b/src/Repositories/Concerns/InteractsWithAttachers.php @@ -22,10 +22,6 @@ public function authorizeBelongsToMany(RestifyRequest $request): self abort(400, "Missing BelongsToMany or MorphToMany field for [{$request->relatedRepository}]. This field should be in the related of the [{$class}] class. Or you are not authorized to use that repository (see `allowRestify` policy method)."); } - $field->authorizeToAttach( - $request, - ); - return $this; } diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 7a655f0d8..f8c99fee8 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -798,6 +798,8 @@ public function attach(RestifyRequest $request, $repositoryId, Collection $pivot $fields = $eagerField->collectPivotFields()->filter(fn($pivotField) => $request->has($pivotField->attribute))->values(); $pivots->map(function ($pivot) use ($request, $fields, $eagerField) { + $eagerField->validate($request, $pivot); + static::validatorForAttach($request)->validate(); static::fillFields($request, $pivot, $fields); From 6f722524d4cf66f8f1b5fbb074491de4ef292446 Mon Sep 17 00:00:00 2001 From: Eduard Lupacescu Date: Sat, 16 Jan 2021 17:43:54 +0200 Subject: [PATCH 4/5] wip --- src/Fields/Concerns/Attachable.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Fields/Concerns/Attachable.php b/src/Fields/Concerns/Attachable.php index 4486a7e9d..5bdcdddaf 100644 --- a/src/Fields/Concerns/Attachable.php +++ b/src/Fields/Concerns/Attachable.php @@ -9,7 +9,6 @@ use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Support\Arr; -use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; trait Attachable From 8b24402509f33e2eb219b5f397f5d5ea673474a0 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Sat, 16 Jan 2021 17:44:12 +0200 Subject: [PATCH 5/5] Apply fixes from StyleCI (#356) --- src/Fields/Concerns/Attachable.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Fields/Concerns/Attachable.php b/src/Fields/Concerns/Attachable.php index 5bdcdddaf..10111342b 100644 --- a/src/Fields/Concerns/Attachable.php +++ b/src/Fields/Concerns/Attachable.php @@ -67,7 +67,7 @@ public function authorizeToAttach(RestifyRequest $request) $request, $request->findModelOrFail()->{$request->viaRelationship ?? $request->relatedRepository}(), $relatedRepositoryId ); - if (!$this->authorizedToAttach($request, $pivot)) { + if (! $this->authorizedToAttach($request, $pivot)) { throw new AuthorizationException(); } }); @@ -84,7 +84,7 @@ public function authorizedToDetach(RestifyRequest $request, Pivot $pivot): bool public function authorizeToDetach(RestifyRequest $request, Pivot $pivot) { - if (!$this->authorizedToDetach($request, $pivot)) { + if (! $this->authorizedToDetach($request, $pivot)) { throw new AuthorizationException(); } @@ -118,7 +118,7 @@ public function initializePivot(RestifyRequest $request, $relationship, $related ]); } - $fields = $this->collectPivotFields()->filter(fn($pivotField) => $request->has($pivotField->attribute))->values(); + $fields = $this->collectPivotFields()->filter(fn ($pivotField) => $request->has($pivotField->attribute))->values(); $repository = $request->repository();