Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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' => '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);
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