From 6907c1e2d09aed60aca5ac72ae7c12b19727d617 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Fri, 18 Dec 2020 13:53:59 +0200 Subject: [PATCH] Matches/definition (#316) * Definition with related. * Apply fixes from StyleCI (#313) * wip * Apply fixes from StyleCI (#314) * wip * Apply fixes from StyleCI (#315) * Matches improvement. * Apply fixes from StyleCI (#317) --- src/Commands/StubCommand.php | 1 + src/Eager/Related.php | 37 +++++++++++++++++++ src/Eager/RelatedCollection.php | 47 +++++++++++++++++++++++++ src/Fields/EagerField.php | 6 ++-- src/Repositories/Concerns/Mockable.php | 3 +- src/Repositories/Repository.php | 27 ++++++++------ src/Traits/InteractWithSearch.php | 6 ++++ tests/Fields/BelongsToFieldTest.php | 29 ++++++++++++--- tests/Fields/BelongsToManyFieldTest.php | 17 +++++---- tests/Fields/HasManyTest.php | 21 ++++++----- tests/Fields/HasOneFieldTest.php | 17 +++++---- tests/Fields/MorphToManyFieldTest.php | 11 ++++-- 12 files changed, 181 insertions(+), 41 deletions(-) create mode 100644 src/Eager/Related.php create mode 100644 src/Eager/RelatedCollection.php diff --git a/src/Commands/StubCommand.php b/src/Commands/StubCommand.php index b20c8991..da076dca 100644 --- a/src/Commands/StubCommand.php +++ b/src/Commands/StubCommand.php @@ -93,6 +93,7 @@ protected function make($table) break; case 'bigint': case 'int': + case 'integer': if ($columnDefinition->getAutoincrement() === true) { //primary key return; diff --git a/src/Eager/Related.php b/src/Eager/Related.php new file mode 100644 index 00000000..b806fea0 --- /dev/null +++ b/src/Eager/Related.php @@ -0,0 +1,37 @@ +relation = $relation; + $this->field = $field; + } + + public function isEager(): bool + { + return ! is_null($this->field); + } + + public function getRelation(): string + { + return $this->relation; + } + + public function resolveField(Repository $repository): EagerField + { + return $this->field->resolve($repository); + } +} diff --git a/src/Eager/RelatedCollection.php b/src/Eager/RelatedCollection.php new file mode 100644 index 00000000..acd2f7bd --- /dev/null +++ b/src/Eager/RelatedCollection.php @@ -0,0 +1,47 @@ +mapWithKeys(function ($value, $key) { + return [ + is_numeric($key) ? $value : $key => $value, + ]; + }); + } + + public function forEager(RestifyRequest $request): self + { + return $this->filter(fn ($value, $key) => $value instanceof EagerField) + ->filter(fn (Field $field) => $field->authorize($request)) + ->unique('attribute'); + } + + public function inRequest(RestifyRequest $request): self + { + return $this + ->filter(fn ($field, $key) => in_array($key, str_getcsv($request->input('related')))) + ->unique(); + } + + public function mapIntoRelated(RestifyRequest $request) + { + return $this->map(function ($value, $key) { + return Related::make($key, $value instanceof EagerField ? $value : null); + }); + } + + public function authorized(RestifyRequest $request) + { + return $this->intoAssoc() + ->filter(fn ($key, $value) => $key instanceof EagerField ? $key->authorize($request) : true); + } +} diff --git a/src/Fields/EagerField.php b/src/Fields/EagerField.php index 260eaf66..43df5305 100644 --- a/src/Fields/EagerField.php +++ b/src/Fields/EagerField.php @@ -2,6 +2,7 @@ namespace Binaryk\LaravelRestify\Fields; +use Binaryk\LaravelRestify\Repositories\Repository; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Http\Request; use Illuminate\Support\Facades\Gate; @@ -18,7 +19,7 @@ class EagerField extends Field /** * The class name of the related repository. * - * @var string + * @var Repository */ public string $repositoryClass; @@ -55,9 +56,10 @@ public function resolve($repository, $attribute = null) ->eagerState(); } catch (AuthorizationException $e) { $class = get_class($relatedModel); + $field = class_basename(get_called_class()); $policy = get_class(Gate::getPolicyFor($relatedModel)); - abort(403, "You are not authorized to see the [{$class}] relationship from the BelongsTo field from the BelongsTo field. Check the [show] method from the [$policy]"); + abort(403, "You are not authorized to see the [{$class}] relationship from the {$field} field from the {$field} field. Check the [show] method from the [$policy]"); } return $this; diff --git a/src/Repositories/Concerns/Mockable.php b/src/Repositories/Concerns/Mockable.php index d47c3ca0..1eb191ca 100644 --- a/src/Repositories/Concerns/Mockable.php +++ b/src/Repositories/Concerns/Mockable.php @@ -4,6 +4,7 @@ use Mockery; use Mockery\MockInterface; +use RuntimeException; trait Mockable { @@ -151,7 +152,7 @@ public static function clearResolvedInstances() * * @return string * - * @throws \RuntimeException + * @throws RuntimeException */ public static function uriKey() { diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 54431ced..045fc79b 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -4,6 +4,7 @@ use Binaryk\LaravelRestify\Contracts\RestifySearchable; use Binaryk\LaravelRestify\Controllers\RestResponse; +use Binaryk\LaravelRestify\Eager\Related; use Binaryk\LaravelRestify\Exceptions\InstanceOfException; use Binaryk\LaravelRestify\Fields\BelongsToMany; use Binaryk\LaravelRestify\Fields\EagerField; @@ -511,23 +512,24 @@ public function resolveRelationships($request): array { $withs = collect(); - /** * To avoid circular relationships and deep stack calls, we will do not load eager fields. */ - if (! $this->isEagerState()) { - $this->collectFields($request) - ->forEager($request, $this) - ->filter(fn (EagerField $field) => $field->isShownOnShow($request, $this)) - ->each(fn (EagerField $field) => $withs->put($field->attribute, $field->resolve($this)->value)); - } + static::collectRelated() + ->authorized($request) + ->inRequest($request) + ->mapIntoRelated($request) + ->each(function (Related $related) use ($request, $withs) { + $relation = $related->getRelation(); - collect(str_getcsv($request->input('related'))) - ->filter(fn ($relation) => in_array($relation, static::getRelated())) - ->each(function ($relation) use ($request, $withs) { 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}(); @@ -861,6 +863,11 @@ public function allowToDestroy(RestifyRequest $request) return $this; } + /** + * @param $request + * @return $this + * @throws \Illuminate\Auth\Access\AuthorizationException + */ public function allowToShow($request): self { $this->authorizeToShow($request); diff --git a/src/Traits/InteractWithSearch.php b/src/Traits/InteractWithSearch.php index 5650fc53..cd925fc5 100644 --- a/src/Traits/InteractWithSearch.php +++ b/src/Traits/InteractWithSearch.php @@ -2,6 +2,7 @@ namespace Binaryk\LaravelRestify\Traits; +use Binaryk\LaravelRestify\Eager\RelatedCollection; use Binaryk\LaravelRestify\Filter; use Binaryk\LaravelRestify\Filters\MatchFilter; use Binaryk\LaravelRestify\Filters\SearchableFilter; @@ -42,6 +43,11 @@ public static function getRelated() return static::$related ?? []; } + public static function collectRelated(): RelatedCollection + { + return RelatedCollection::make(static::getRelated()); + } + /** * @return array */ diff --git a/tests/Fields/BelongsToFieldTest.php b/tests/Fields/BelongsToFieldTest.php index 24f6ffb6..44bb7588 100644 --- a/tests/Fields/BelongsToFieldTest.php +++ b/tests/Fields/BelongsToFieldTest.php @@ -36,20 +36,32 @@ protected function tearDown(): void Repository::clearResolvedInstances(); } - public function test_present_on_relations() + public function test_present_on_show_when_specified_related() { $post = factory(Post::class)->create([ 'user_id' => factory(User::class), ]); - $this->get(PostWithUserRepository::uriKey()."/$post->id") + $relationships = $this->get(PostWithUserRepository::uriKey()."/$post->id?related=user") ->assertJsonStructure([ 'data' => [ 'relationships' => [ - 'user', + 'user' => [ + 'id', + 'type', + 'attributes', + ], ], ], - ]); + ]) + ->json('data.relationships'); + + $this->assertNotNull($relationships); + + $relationships = $this->get(PostWithUserRepository::uriKey()."/$post->id") + ->json('data.relationships'); + + $this->assertNull($relationships); } public function test_unauthorized_see_relationship() @@ -61,7 +73,7 @@ public function test_unauthorized_see_relationship() tap(factory(Post::class)->create([ 'user_id' => factory(User::class), ]), function ($post) { - $this->get(PostWithUserRepository::uriKey()."/{$post->id}") + $this->get(PostWithUserRepository::uriKey()."/{$post->id}?related=user") ->assertForbidden(); }); } @@ -179,6 +191,13 @@ class PostWithUserRepository extends Repository { public static $model = Post::class; + public static function getRelated() + { + return [ + 'user' => BelongsTo::make('user', 'user', UserRepository::class), + ]; + } + public function fields(RestifyRequest $request) { return [ diff --git a/tests/Fields/BelongsToManyFieldTest.php b/tests/Fields/BelongsToManyFieldTest.php index 77a6d386..d516b0b9 100644 --- a/tests/Fields/BelongsToManyFieldTest.php +++ b/tests/Fields/BelongsToManyFieldTest.php @@ -22,7 +22,7 @@ protected function setUp(): void ]); } - public function test_displays_on_relationships_show() + public function test_belongs_to_many_displays_on_relationships_show() { $company = tap(factory(Company::class)->create(), function (Company $company) { $company->users()->attach( @@ -30,7 +30,7 @@ public function test_displays_on_relationships_show() ); }); - $this->get(CompanyWithUsersRepository::uriKey()."/{$company->id}") + $this->get(CompanyWithUsersRepository::uriKey()."/{$company->id}?related=users") ->assertJsonStructure([ 'data' => [ 'relationships' => [ @@ -86,18 +86,23 @@ class CompanyWithUsersRepository extends Repository { public static $model = Company::class; - public function fields(RestifyRequest $request) + public static function getRelated() { return [ - field('name'), - - BelongsToMany::make('users', 'users', UserRepository::class) + 'users' => BelongsToMany::make('users', 'users', UserRepository::class) ->hideFromShow(function () { return $_SERVER['hide_users_from_show'] ?? false; }), ]; } + public function fields(RestifyRequest $request) + { + return [ + field('name'), + ]; + } + public static function uriKey() { return 'companies-with-users-repository'; diff --git a/tests/Fields/HasManyTest.php b/tests/Fields/HasManyTest.php index ab7c0212..6b82bdbc 100644 --- a/tests/Fields/HasManyTest.php +++ b/tests/Fields/HasManyTest.php @@ -36,13 +36,13 @@ protected function tearDown(): void Repository::clearResolvedInstances(); } - public function test_present_on_relations() + public function test_has_many_present_on_relations() { $post = factory(Post::class)->create([ 'user_id' => factory(User::class), ]); - $this->get(UserWithPosts::uriKey()."/$post->id") + $this->get(UserWithPosts::uriKey()."/$post->id?related=posts") ->assertJsonStructure([ 'data' => [ 'relationships' => [ @@ -52,17 +52,17 @@ public function test_present_on_relations() ]); } - public function test_paginated_on_relation() + public function test_has_many_paginated_on_relation() { $user = tap($this->mockUsers()->first(), function ($user) { $this->mockPosts($user->id, 22); }); - $this->get(UserWithPosts::uriKey()."/{$user->id}?relatablePerPage=20") + $this->get(UserWithPosts::uriKey()."/{$user->id}?related=posts&relatablePerPage=20") ->assertJsonCount(20, 'data.relationships.posts'); } - public function test_unauthorized_see_relationship_posts() + public function test_has_many_unauthorized_see_relationship_posts() { $_SERVER['restify.post.show'] = false; @@ -71,7 +71,7 @@ public function test_unauthorized_see_relationship_posts() $this->mockPosts($user->id, 20); }); - $this->get(UserWithPosts::uriKey()."/$user->id") + $this->get(UserWithPosts::uriKey()."/$user->id?related=posts") ->assertForbidden(); } @@ -290,14 +290,19 @@ class UserWithPosts extends Repository { public static $model = User::class; + public static function getRelated() + { + return [ + 'posts' => HasMany::make('posts', 'posts', PostRepository::class), + ]; + } + public function fields(RestifyRequest $request) { return [ field('name'), field('email'), field('password'), - - HasMany::make('posts', 'posts', PostRepository::class), ]; } } diff --git a/tests/Fields/HasOneFieldTest.php b/tests/Fields/HasOneFieldTest.php index c41c6e21..b1733eb8 100644 --- a/tests/Fields/HasOneFieldTest.php +++ b/tests/Fields/HasOneFieldTest.php @@ -35,13 +35,13 @@ protected function tearDown(): void Repository::clearResolvedInstances(); } - public function test_present_on_relations() + public function test_has_one_present_on_relations() { $post = factory(Post::class)->create([ 'user_id' => factory(User::class), ]); - $this->get(UserWithPostRepository::uriKey()."/$post->id") + $this->get(UserWithPostRepository::uriKey()."/$post->id?related=post") ->assertJsonStructure([ 'data' => [ 'relationships' => [ @@ -51,7 +51,7 @@ public function test_present_on_relations() ]); } - public function test_unauthorized_see_relationship() + public function test_has_one_field_unauthorized_see_relationship() { $_SERVER['restify.post.show'] = false; @@ -60,7 +60,7 @@ public function test_unauthorized_see_relationship() tap(factory(Post::class)->create([ 'user_id' => factory(User::class), ]), function ($post) { - $this->get(UserWithPostRepository::uriKey()."/{$post->id}") + $this->get(UserWithPostRepository::uriKey()."/{$post->id}?related=post") ->assertForbidden(); }); } @@ -88,14 +88,19 @@ class UserWithPostRepository extends Repository { public static $model = User::class; + public static function getRelated() + { + return [ + 'post' => HasOne::make('post', 'post', PostRepository::class), + ]; + } + public function fields(RestifyRequest $request) { return [ Field::new('name'), Field::new('email'), Field::new('password'), - - HasOne::make('post', 'post', PostRepository::class), ]; } diff --git a/tests/Fields/MorphToManyFieldTest.php b/tests/Fields/MorphToManyFieldTest.php index d3f841ae..99334bd6 100644 --- a/tests/Fields/MorphToManyFieldTest.php +++ b/tests/Fields/MorphToManyFieldTest.php @@ -30,7 +30,7 @@ public function test_morph_to_many_displays_in_relationships() ); }); - $this->get(UserWithRolesRepository::uriKey()."/$user->id") + $this->get(UserWithRolesRepository::uriKey()."/$user->id?related=roles") ->assertJsonStructure([ 'data' => [ 'relationships' => [ @@ -58,14 +58,19 @@ class UserWithRolesRepository extends Repository { public static $model = User::class; + public static function getRelated() + { + return [ + 'roles' => MorphToMany::make('roles', 'roles', RoleRepository::class), + ]; + } + public function fields(RestifyRequest $request) { return [ field('name'), field('email'), field('password'), - - MorphToMany::make('roles', 'roles', RoleRepository::class), ]; }