Skip to content

Commit

Permalink
Repository related supports now a custom casts. (#302)
Browse files Browse the repository at this point in the history
* Repository related supports now a custom casts.

* Apply fixes from StyleCI (#303)

* docs
  • Loading branch information
binaryk authored Dec 12, 2020
1 parent 6bbdaec commit 053fbfc
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 35 deletions.
16 changes: 16 additions & 0 deletions config/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 39 additions & 0 deletions docs/docs/4.0/filtering/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
8 changes: 8 additions & 0 deletions src/Fields/EagerField.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down
4 changes: 1 addition & 3 deletions src/LaravelRestifyServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}
}
22 changes: 22 additions & 0 deletions src/Repositories/Casts/RelatedCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Binaryk\LaravelRestify\Repositories\Casts;

use Binaryk\LaravelRestify\Contracts\RestifySearchable;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;

class RelatedCast extends RepositoryCast
{
public static function fromBuilder(Request $request, Builder $builder): Collection
{
return $builder->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();
}
}
15 changes: 15 additions & 0 deletions src/Repositories/Casts/RepositoryCast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Binaryk\LaravelRestify\Repositories\Casts;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;

abstract class RepositoryCast
{
abstract public static function fromBuilder(Request $request, Builder $builder): Collection;

abstract public static function fromRelation(Request $request, Relation $relation): Collection;
}
7 changes: 3 additions & 4 deletions src/Repositories/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
use Binaryk\LaravelRestify\Fields\Field;
use Binaryk\LaravelRestify\Fields\FieldCollection;
use Binaryk\LaravelRestify\Filter;
use Binaryk\LaravelRestify\Http\Requests\RepositoryShowRequest;
use Binaryk\LaravelRestify\Http\Requests\RepositoryStoreBulkRequest;
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
use Binaryk\LaravelRestify\Models\CreationAware;
Expand Down Expand Up @@ -513,7 +512,7 @@ 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() && $request instanceof RepositoryShowRequest) {
if (! $this->isEagerState()) {
$this->collectFields($request)
->forEager($request, $this)
->filter(fn (EagerField $field) => $field->isShownOnShow($request, $this))
Expand All @@ -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),

Expand Down
31 changes: 31 additions & 0 deletions src/Repositories/RepositoryEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,44 @@

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.
*
* @var array
*/
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.
*
Expand All @@ -26,6 +55,8 @@ protected function bootIfNotBooted()
if (! isset(static::$booted[static::class])) {
static::$booted[static::class] = true;

static::booting();
static::boot();
static::booted();
}
}
Expand Down
36 changes: 32 additions & 4 deletions tests/Controllers/RepositoryIndexControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'));
Expand All @@ -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'];
Expand Down Expand Up @@ -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'));
Expand Down
24 changes: 0 additions & 24 deletions tests/Feature/Authentication/AuthServiceRegisterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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' => '[email protected]',
'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);
Expand Down
27 changes: 27 additions & 0 deletions tests/Fixtures/Post/RelatedCastWithAttributes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace Binaryk\LaravelRestify\Tests\Fixtures\Post;

use Binaryk\LaravelRestify\Contracts\RestifySearchable;
use Binaryk\LaravelRestify\Repositories\Casts\RepositoryCast;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;

class RelatedCastWithAttributes extends RepositoryCast
{
public static function fromBuilder(Request $request, Builder $builder): Collection
{
return $builder->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()]);
}
}
4 changes: 4 additions & 0 deletions tests/IntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 053fbfc

Please sign in to comment.