From 053fbfc1660c5ac9baf9956753e68c5193d79b2d Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Sat, 12 Dec 2020 17:31:17 +0200 Subject: [PATCH] Repository related supports now a custom casts. (#302) * Repository related supports now a custom casts. * Apply fixes from StyleCI (#303) * docs --- config/config.php | 16 ++++++++ docs/docs/4.0/filtering/filtering.md | 39 +++++++++++++++++++ src/Fields/EagerField.php | 8 ++++ src/LaravelRestifyServiceProvider.php | 4 +- src/Repositories/Casts/RelatedCast.php | 22 +++++++++++ src/Repositories/Casts/RepositoryCast.php | 15 +++++++ src/Repositories/Repository.php | 7 ++-- src/Repositories/RepositoryEvents.php | 31 +++++++++++++++ .../RepositoryIndexControllerTest.php | 36 +++++++++++++++-- .../AuthServiceRegisterTest.php | 24 ------------ .../Post/RelatedCastWithAttributes.php | 27 +++++++++++++ tests/IntegrationTest.php | 4 ++ 12 files changed, 198 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 8833b174..62651076 100644 --- a/config/config.php +++ b/config/config.php @@ -78,6 +78,22 @@ 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/docs/docs/4.0/filtering/filtering.md b/docs/docs/4.0/filtering/filtering.md index eb1b17de..20d2ee04 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. diff --git a/src/Fields/EagerField.php b/src/Fields/EagerField.php index 4ad01764..260eaf66 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 dfa671c8..ea690770 100644 --- a/src/LaravelRestifyServiceProvider.php +++ b/src/LaravelRestifyServiceProvider.php @@ -77,8 +77,6 @@ protected function registerPublishing() __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 00000000..373568e9 --- /dev/null +++ b/src/Repositories/Casts/RelatedCast.php @@ -0,0 +1,22 @@ +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 00000000..278f9821 --- /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 89b60a2b..0ebfff76 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 6389d757..4503630c 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']; @@ -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 990b8426..3496cc28 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; @@ -35,27 +32,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 00000000..3c24fe20 --- /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 e30746d8..dce19f7c 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