From 905602fb867897ee323e06c565159b61c681952a Mon Sep 17 00:00:00 2001 From: Eduard Lupacescu Date: Sat, 12 Dec 2020 17:19:45 +0200 Subject: [PATCH 1/3] Repository related supports now a custom casts. --- config/config.php | 17 +++++++++ src/Fields/EagerField.php | 8 ++++ src/LaravelRestifyServiceProvider.php | 8 ++-- src/Repositories/Casts/RelatedCast.php | 23 +++++++++++ src/Repositories/Casts/RepositoryCast.php | 15 ++++++++ src/Repositories/Repository.php | 7 ++-- src/Repositories/RepositoryEvents.php | 31 +++++++++++++++ .../RepositoryIndexControllerTest.php | 38 ++++++++++++++++--- .../AuthServiceRegisterTest.php | 21 ---------- .../Post/RelatedCastWithAttributes.php | 27 +++++++++++++ tests/IntegrationTest.php | 4 ++ 11 files changed, 164 insertions(+), 35 deletions(-) create mode 100644 src/Repositories/Casts/RelatedCast.php create mode 100644 src/Repositories/Casts/RepositoryCast.php create mode 100644 tests/Fixtures/Post/RelatedCastWithAttributes.php diff --git a/config/config.php b/config/config.php index 8833b174d..571a7dd95 100644 --- a/config/config.php +++ b/config/config.php @@ -78,6 +78,23 @@ AuthorizeRestify::class, ], + + /* + |-------------------------------------------------------------------------- + | Used to format data. + |-------------------------------------------------------------------------- + | + */ + 'casts' => [ + /* + |-------------------------------------------------------------------------- + | Casting the related entities format. + |-------------------------------------------------------------------------- + | + */ + 'related' => \Binaryk\LaravelRestify\Repositories\Casts\RelatedCast::class, + ], + /* |-------------------------------------------------------------------------- | Restify Exception Handler diff --git a/src/Fields/EagerField.php b/src/Fields/EagerField.php index 4ad017647..260eaf66e 100644 --- a/src/Fields/EagerField.php +++ b/src/Fields/EagerField.php @@ -22,6 +22,14 @@ class EagerField extends Field */ public string $repositoryClass; + public function __construct($attribute, callable $resolveCallback = null) + { + parent::__construct($attribute, $resolveCallback); + + $this->showOnShow() + ->hideFromIndex(); + } + /** * Determine if the field should be displayed for the given request. * diff --git a/src/LaravelRestifyServiceProvider.php b/src/LaravelRestifyServiceProvider.php index dfa671c8d..102ecbbf0 100644 --- a/src/LaravelRestifyServiceProvider.php +++ b/src/LaravelRestifyServiceProvider.php @@ -70,15 +70,13 @@ public function register() protected function registerPublishing() { $this->publishes([ - __DIR__.'/Commands/stubs/RestifyServiceProvider.stub' => app_path('Providers/RestifyServiceProvider.php'), + __DIR__ . '/Commands/stubs/RestifyServiceProvider.stub' => app_path('Providers/RestifyServiceProvider.php'), ], 'restify-provider'); $this->publishes([ - __DIR__.'/../config/config.php' => config_path('restify.php'), + __DIR__ . '/../config/config.php' => config_path('restify.php'), ], 'restify-config'); - if (! $this->app->configurationIsCached()) { - $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'laravel-restify'); - } + $this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'restify'); } } diff --git a/src/Repositories/Casts/RelatedCast.php b/src/Repositories/Casts/RelatedCast.php new file mode 100644 index 000000000..65634e834 --- /dev/null +++ b/src/Repositories/Casts/RelatedCast.php @@ -0,0 +1,23 @@ +take($request->input('relatablePerPage') ?? (static::$defaultRelatablePerPage ?? RestifySearchable::DEFAULT_RELATABLE_PER_PAGE))->get(); + } + + public static function fromRelation(Request $request, Relation $relation): Collection + { + return $relation->take($request->input('relatablePerPage') ?? (static::$defaultRelatablePerPage ?? RestifySearchable::DEFAULT_RELATABLE_PER_PAGE))->get(); + } +} diff --git a/src/Repositories/Casts/RepositoryCast.php b/src/Repositories/Casts/RepositoryCast.php new file mode 100644 index 000000000..278f98212 --- /dev/null +++ b/src/Repositories/Casts/RepositoryCast.php @@ -0,0 +1,15 @@ +isEagerState() && $request instanceof RepositoryShowRequest) { + if (! $this->isEagerState()) { $this->collectFields($request) ->forEager($request, $this) ->filter(fn (EagerField $field) => $field->isShownOnShow($request, $this)) @@ -534,9 +533,9 @@ public function resolveRelationships($request): array : $this->resource->{$relation}(); collect([ - Builder::class => fn () => $withs->put($relation, $paginator->take($request->input('relatablePerPage') ?? (static::$defaultRelatablePerPage ?? RestifySearchable::DEFAULT_RELATABLE_PER_PAGE))->get()), + Builder::class => fn () => $withs->put($relation, (static::$relatedCast)::fromBuilder($request, $paginator)), - Relation::class => fn () => $withs->put($relation, $paginator->take($request->input('relatablePerPage') ?? (static::$defaultRelatablePerPage ?? RestifySearchable::DEFAULT_RELATABLE_PER_PAGE))->get()), + Relation::class => fn () => $withs->put($relation, (static::$relatedCast)::fromRelation($request, $paginator)), Collection::class => fn () => $withs->put($relation, $paginator), diff --git a/src/Repositories/RepositoryEvents.php b/src/Repositories/RepositoryEvents.php index 89b60a2b3..0ebfff760 100644 --- a/src/Repositories/RepositoryEvents.php +++ b/src/Repositories/RepositoryEvents.php @@ -2,8 +2,17 @@ namespace Binaryk\LaravelRestify\Repositories; +use Binaryk\LaravelRestify\Repositories\Casts\RepositoryCast; + trait RepositoryEvents { + /** + * Used to convert collections for relations. + * + * @var RepositoryCast + */ + public static RepositoryCast $relatedCast; + /** * The array of booted repositories. * @@ -11,6 +20,26 @@ trait RepositoryEvents */ protected static $booted = []; + /** + * Perform any actions required before the repository boots. + * + * @return void + */ + protected static function booting() + { + // + } + + /** + * Boot the repository. + * + * @return void + */ + protected static function boot() + { + static::$relatedCast = app(config('restify.casts.related')); + } + /** * Perform any actions required after the repository boots. * @@ -26,6 +55,8 @@ protected function bootIfNotBooted() if (! isset(static::$booted[static::class])) { static::$booted[static::class] = true; + static::booting(); + static::boot(); static::booted(); } } diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index 6389d757f..b8da3a357 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -6,6 +6,7 @@ use Binaryk\LaravelRestify\Tests\Fixtures\Company\CompanyRepository; use Binaryk\LaravelRestify\Tests\Fixtures\Post\Post; use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository; +use Binaryk\LaravelRestify\Tests\Fixtures\Post\RelatedCastWithAttributes; use Binaryk\LaravelRestify\Tests\Fixtures\User\User; use Binaryk\LaravelRestify\Tests\IntegrationTest; use Illuminate\Foundation\Testing\RefreshDatabase; @@ -66,14 +67,14 @@ public function test_repository_order() $response = $this ->getJson('posts?sort=-title') - ->assertStatus(200); + ->assertOk(); $this->assertEquals('zzz', $response->json('data.0.attributes.title')); $this->assertEquals('aaa', $response->json('data.1.attributes.title')); $response = $this ->getJson('posts?order=-title') - ->assertStatus(200); + ->assertOk(); $this->assertEquals('zzz', $response->json('data.1.attributes.title')); $this->assertEquals('aaa', $response->json('data.0.attributes.title')); @@ -88,12 +89,39 @@ public function test_repository_with_relations() factory(Post::class)->create(['user_id' => $user->id]); $response = $this->getJson('posts?related=user') - ->assertStatus(200); + ->assertOk(); $this->assertCount(1, $response->json('data.0.relationships.user')); $this->assertArrayNotHasKey('user', $response->json('data.0.attributes')); } + public function test_using_custom_related_casts() + { + PostRepository::$related = ['user']; + + config([ + 'restify.casts.related' => RelatedCastWithAttributes::class + ]); + + $user = $this->mockUsers(1)->first(); + + factory(Post::class)->create(['user_id' => $user->id]); + + $this->getJson('posts?related=user') + ->assertOk() + ->assertJsonStructure([ + 'data' => [ + [ + 'relationships' => [ + 'user' => [ + ['attributes'] + ], + ] + ] + ] + ]); + } + public function test_repository_with_deep_relations() { CompanyRepository::$related = ['users.posts']; @@ -108,7 +136,7 @@ public function test_repository_with_deep_relations() }); }); - $response = $this->getJson(CompanyRepository::uriKey().'?related=users.posts') + $response = $this->getJson(CompanyRepository::uriKey() . '?related=users.posts') ->assertOk(); $this->assertCount(1, $response->json('data.0.relationships.users')); @@ -124,7 +152,7 @@ public function test_paginated_repository_with_relations() factory(Post::class, 20)->create(['user_id' => $user->id]); $response = $this->getJson('posts?related=user&page=2') - ->assertStatus(200); + ->assertOk(); $this->assertCount(1, $response->json('data.0.relationships.user')); $this->assertArrayNotHasKey('user', $response->json('data.0.attributes')); diff --git a/tests/Feature/Authentication/AuthServiceRegisterTest.php b/tests/Feature/Authentication/AuthServiceRegisterTest.php index 990b84268..07c6bd5b2 100644 --- a/tests/Feature/Authentication/AuthServiceRegisterTest.php +++ b/tests/Feature/Authentication/AuthServiceRegisterTest.php @@ -35,27 +35,6 @@ protected function setUp(): void $this->authService = resolve(AuthService::class); } - public function test_register_throw_user_not_authenticatable() - { - $this->app->instance(User::class, (new class extends SampleUser implements Passportable { - })); - - $user = [ - 'name' => 'Eduard Lupacescu', - 'email' => 'eduard.lupacescu@binarcode.com', - 'password' => 'password', - 'password_confirmation' => 'password', - 'remember_token' => Str::random(10), - ]; - - $request = new Request([], []); - - $request->merge($user); - - $this->expectException(AuthenticatableUserException::class); - $this->authService->register($request); - } - public function test_user_query_throw_container_does_not_have_model_reflection_exception() { $this->app['config']->set('auth.providers.users.model', null); diff --git a/tests/Fixtures/Post/RelatedCastWithAttributes.php b/tests/Fixtures/Post/RelatedCastWithAttributes.php new file mode 100644 index 000000000..1c9c9fc81 --- /dev/null +++ b/tests/Fixtures/Post/RelatedCastWithAttributes.php @@ -0,0 +1,27 @@ +take($request->input('relatablePerPage') ?? (static::$defaultRelatablePerPage ?? RestifySearchable::DEFAULT_RELATABLE_PER_PAGE)) + ->get() + ->map(fn($item) => ['attributes' => $item->toArray()]); + } + + public static function fromRelation(Request $request, Relation $relation): Collection + { + return $relation->take($request->input('relatablePerPage') ?? (static::$defaultRelatablePerPage ?? RestifySearchable::DEFAULT_RELATABLE_PER_PAGE)) + ->get() + ->map(fn($item) => ['attributes' => $item->toArray()]); + } +} diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index e30746d8f..ec3edb080 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -55,6 +55,10 @@ protected function setUp(): void $this->withFactories(__DIR__.'/Factories'); $this->injectTranslator(); $this->app->bind(ExceptionHandler::class, RestifyHandler::class); + + Restify::$authUsing = function() { + return true; + }; } protected function tearDown(): void From 37c853ae8e960c466a101cfce1503db7c23a2f7a Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Sat, 12 Dec 2020 17:20:13 +0200 Subject: [PATCH 2/3] Apply fixes from StyleCI (#303) --- config/config.php | 1 - src/LaravelRestifyServiceProvider.php | 6 +++--- src/Repositories/Casts/RelatedCast.php | 1 - tests/Controllers/RepositoryIndexControllerTest.php | 12 ++++++------ .../Authentication/AuthServiceRegisterTest.php | 3 --- tests/Fixtures/Post/RelatedCastWithAttributes.php | 4 ++-- tests/IntegrationTest.php | 2 +- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/config/config.php b/config/config.php index 571a7dd95..626510768 100644 --- a/config/config.php +++ b/config/config.php @@ -78,7 +78,6 @@ AuthorizeRestify::class, ], - /* |-------------------------------------------------------------------------- | Used to format data. diff --git a/src/LaravelRestifyServiceProvider.php b/src/LaravelRestifyServiceProvider.php index 102ecbbf0..ea6907705 100644 --- a/src/LaravelRestifyServiceProvider.php +++ b/src/LaravelRestifyServiceProvider.php @@ -70,13 +70,13 @@ public function register() protected function registerPublishing() { $this->publishes([ - __DIR__ . '/Commands/stubs/RestifyServiceProvider.stub' => app_path('Providers/RestifyServiceProvider.php'), + __DIR__.'/Commands/stubs/RestifyServiceProvider.stub' => app_path('Providers/RestifyServiceProvider.php'), ], 'restify-provider'); $this->publishes([ - __DIR__ . '/../config/config.php' => config_path('restify.php'), + __DIR__.'/../config/config.php' => config_path('restify.php'), ], 'restify-config'); - $this->mergeConfigFrom(__DIR__ . '/../config/config.php', 'restify'); + $this->mergeConfigFrom(__DIR__.'/../config/config.php', 'restify'); } } diff --git a/src/Repositories/Casts/RelatedCast.php b/src/Repositories/Casts/RelatedCast.php index 65634e834..373568e9a 100644 --- a/src/Repositories/Casts/RelatedCast.php +++ b/src/Repositories/Casts/RelatedCast.php @@ -8,7 +8,6 @@ use Illuminate\Http\Request; use Illuminate\Support\Collection; - class RelatedCast extends RepositoryCast { public static function fromBuilder(Request $request, Builder $builder): Collection diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index b8da3a357..4503630c9 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -100,7 +100,7 @@ public function test_using_custom_related_casts() PostRepository::$related = ['user']; config([ - 'restify.casts.related' => RelatedCastWithAttributes::class + 'restify.casts.related' => RelatedCastWithAttributes::class, ]); $user = $this->mockUsers(1)->first(); @@ -114,11 +114,11 @@ public function test_using_custom_related_casts() [ 'relationships' => [ 'user' => [ - ['attributes'] + ['attributes'], ], - ] - ] - ] + ], + ], + ], ]); } @@ -136,7 +136,7 @@ public function test_repository_with_deep_relations() }); }); - $response = $this->getJson(CompanyRepository::uriKey() . '?related=users.posts') + $response = $this->getJson(CompanyRepository::uriKey().'?related=users.posts') ->assertOk(); $this->assertCount(1, $response->json('data.0.relationships.users')); diff --git a/tests/Feature/Authentication/AuthServiceRegisterTest.php b/tests/Feature/Authentication/AuthServiceRegisterTest.php index 07c6bd5b2..3496cc284 100644 --- a/tests/Feature/Authentication/AuthServiceRegisterTest.php +++ b/tests/Feature/Authentication/AuthServiceRegisterTest.php @@ -2,12 +2,9 @@ namespace Binaryk\LaravelRestify\Tests\Feature\Authentication; -use Binaryk\LaravelRestify\Contracts\Passportable; -use Binaryk\LaravelRestify\Exceptions\AuthenticatableUserException; use Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException; use Binaryk\LaravelRestify\Models\LaravelRestifyModel; use Binaryk\LaravelRestify\Services\AuthService; -use Binaryk\LaravelRestify\Tests\Fixtures\User\SampleUser; use Binaryk\LaravelRestify\Tests\Fixtures\User\User; use Binaryk\LaravelRestify\Tests\IntegrationTest; use Illuminate\Auth\Access\AuthorizationException; diff --git a/tests/Fixtures/Post/RelatedCastWithAttributes.php b/tests/Fixtures/Post/RelatedCastWithAttributes.php index 1c9c9fc81..3c24fe20f 100644 --- a/tests/Fixtures/Post/RelatedCastWithAttributes.php +++ b/tests/Fixtures/Post/RelatedCastWithAttributes.php @@ -15,13 +15,13 @@ public static function fromBuilder(Request $request, Builder $builder): Collecti { return $builder->take($request->input('relatablePerPage') ?? (static::$defaultRelatablePerPage ?? RestifySearchable::DEFAULT_RELATABLE_PER_PAGE)) ->get() - ->map(fn($item) => ['attributes' => $item->toArray()]); + ->map(fn ($item) => ['attributes' => $item->toArray()]); } public static function fromRelation(Request $request, Relation $relation): Collection { return $relation->take($request->input('relatablePerPage') ?? (static::$defaultRelatablePerPage ?? RestifySearchable::DEFAULT_RELATABLE_PER_PAGE)) ->get() - ->map(fn($item) => ['attributes' => $item->toArray()]); + ->map(fn ($item) => ['attributes' => $item->toArray()]); } } diff --git a/tests/IntegrationTest.php b/tests/IntegrationTest.php index ec3edb080..dce19f7c1 100644 --- a/tests/IntegrationTest.php +++ b/tests/IntegrationTest.php @@ -56,7 +56,7 @@ protected function setUp(): void $this->injectTranslator(); $this->app->bind(ExceptionHandler::class, RestifyHandler::class); - Restify::$authUsing = function() { + Restify::$authUsing = function () { return true; }; } From c9784229f3cb1c55d3447e906d005c9db7537345 Mon Sep 17 00:00:00 2001 From: Eduard Lupacescu Date: Sat, 12 Dec 2020 17:28:17 +0200 Subject: [PATCH 3/3] docs --- docs/docs/4.0/filtering/filtering.md | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/docs/docs/4.0/filtering/filtering.md b/docs/docs/4.0/filtering/filtering.md index eb1b17de2..20d2ee04b 100644 --- a/docs/docs/4.0/filtering/filtering.md +++ b/docs/docs/4.0/filtering/filtering.md @@ -260,6 +260,45 @@ This means that we could use `posts` query for eager loading posts: GET: /api/restify/users?related=posts ``` +## Custom data + +You are not limited to add only relations under the `related` array. You can use whatever you want, for instance you can return a simple model, or a collection. Basically any serializable data could be added there. For example: + + +```php +public static $related = [ + 'foo' +]; +``` + +Then in the `Post` model we can define this method as: + +```php +public function foo() { + return collect([1, 2]); +} +``` + +### Custom data format + +You can use a custom related cast class (aka transformer). You can do so by modifying the `restify.casts.related` property. The default related cast is `Binaryk\LaravelRestify\Repositories\Casts\RelatedCast`. + +The cast class should extends the `Binaryk\LaravelRestify\Repositories\Casts\RepositoryCast` abstract class. + +This is the default cast: + +```php + 'casts' => [ + /* + |-------------------------------------------------------------------------- + | Casting the related entities format. + |-------------------------------------------------------------------------- + | + */ + 'related' => \Binaryk\LaravelRestify\Repositories\Casts\RelatedCast::class, + ], +``` + ## Pagination Laravel Restify has returns `index` items paginates. The default `perPage` is 15.