Skip to content
Open
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
121 changes: 121 additions & 0 deletions src/Traits/JsonResourceHelper.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Brainstud\JsonApi\Traits;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

/**
* JsonResourceHelper
*
* Add this trait to a (test) class to add JsonResource helper to it.
*/
trait JsonResourceHelper
{
/**
* createJsonResource.
*
* Makes it easier to create an array structure of an API resource to prevent being repetitive.
*/
protected function createJsonResource(
Collection|Model $modelOrCollection,
array $relationships = null,
array $meta = null,
array $links = null,
string $type = null,
array $exceptAttributes = [],
array $onlyAttributes = [],
bool $isReferenceObject = false,
): array {
$type ??= ($modelOrCollection instanceof Collection)
? Str::snake(Str::plural(class_basename($modelOrCollection[0])))
: Str::snake(Str::plural(class_basename($modelOrCollection)));

if ($modelOrCollection instanceof Collection) {
return $modelOrCollection->map(fn ($model) => (
$this->createJsonResource(
$model,
$relationships,
type: $type,
exceptAttributes: $exceptAttributes,
isReferenceObject: $isReferenceObject,
)
))->toArray();
} elseif ($isReferenceObject) {
$data = [
'id' => $modelOrCollection->identifier,
'type' => $type,
];
} else {
$fillableAttributes = $modelOrCollection->getFillable();
$data = [
'id' => $modelOrCollection->identifier,
'type' => $type,
'attributes' => (
array_filter(
$this->getAllAttributes($modelOrCollection),
fn ($key) => (
in_array($key, $fillableAttributes)
&& (empty($onlyAttributes) || in_array($key, $onlyAttributes))
&& !in_array($key, ['identifier', ...$exceptAttributes])
),
ARRAY_FILTER_USE_KEY
)
),
];
}

if (!$isReferenceObject && $relationships) {
$data['relationships'] = collect(array_keys($relationships))->reduce(function ($rels, $relationKey) use ($relationships) {
$relation = $relationships[$relationKey];
if ($relation instanceof Model) {
$rels[$relationKey] = [
'data' => $this->createJsonResource($relation, isReferenceObject: true),
];
} else {
$rels[$relationKey] = [
'data' => collect($relation)->map(fn ($relatedModel) => (
$this->createJsonResource($relatedModel, isReferenceObject: true)
)),
];
}

return $rels;
}, []);
}

if ($meta) {
$data['meta'] = $meta;
}

if ($links) {
$data['links'] = $links;
}

return $data;
}

/**
* By default, laravel does not add columns where the value is null
* to the retrieved model. Therefore, we combine the received and
* fillable fields on the model. If a field is fillable, but not
* on the given model, it adds it to the attributes array with
* a value of null.
*
*/
private function getAllAttributes(Model $model): array
{
$columns = $model->getFillable();

$attributes = $model->getAttributes();

foreach ($columns as $column) {
if (!array_key_exists($column, $attributes)) {
$attributes[$column] = null;
}
}

return $attributes;
}
}
84 changes: 2 additions & 82 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

namespace Brainstud\JsonApi\Tests;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Brainstud\JsonApi\Traits\JsonResourceHelper;

class TestCase extends \Orchestra\Testbench\TestCase
{
use JsonResourceHelper;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
use JsonResourceHelper;
use JsonResourceHelper;

protected function getEnvironmentSetUp($app)
{
$app['config']->set('database.default', 'testing');
Expand All @@ -26,83 +25,4 @@ public function setUp(): void
'--path' => realpath(__DIR__ . '/database/migrations')
]);
}


/**
* createJsonResource.
*
* Makes it easier to create an array structure of an API resource to prevent being repetitive.
*/
protected function createJsonResource(
Collection|Model $modelOrCollection,
array $relationships = null,
array $meta = null,
array $links = null,
string $type = null,
array $exceptAttributes = [],
array $onlyAttributes = [],
bool $isReferenceObject = false,
): array {

$type ??= ($modelOrCollection instanceof Collection)
? Str::snake(Str::plural(class_basename($modelOrCollection[0])))
: Str::snake(Str::plural(class_basename($modelOrCollection)));

if( $modelOrCollection instanceof Collection ) {
return $modelOrCollection->map(fn ($model) => (
$this->createJsonResource($model, $relationships, type: $type, isReferenceObject: $isReferenceObject)
))->toArray();
} elseif ($isReferenceObject) {
$data = [
'id' => $modelOrCollection->identifier,
'type' => $type,
];
} else {
$fillableAttributes = $modelOrCollection->getFillable();
$data = [
'id' => $modelOrCollection->identifier,
'type' => $type,
'attributes' => (
array_filter(
$modelOrCollection->getAttributes(),
fn($key) => (
!!$modelOrCollection->{$key}
&& in_array($key, $fillableAttributes)
&& (empty($onlyAttributes) || in_array($key, $onlyAttributes))
&& !in_array($key, ['identifier', ...$exceptAttributes])
),
ARRAY_FILTER_USE_KEY
)
),
];
}

if(!$isReferenceObject && $relationships) {
$data['relationships'] = collect(array_keys($relationships))->reduce(function ($rels, $relationKey) use ($relationships) {
$relation = $relationships[$relationKey];
if ($relation instanceof Model){
$rels[$relationKey] = [
'data' => $this->createJsonResource($relation, isReferenceObject: true)
];
}else{
$rels[$relationKey] = [
'data' => collect($relation)->map(fn ($relatedModel) => (
$this->createJsonResource($relatedModel, isReferenceObject: true)
)),
];
}
return $rels;
}, []);
}

if($meta){
$data['meta'] = $meta;
}

if($links){
$data['links'] = $links;
}

return $data;
}
}
16 changes: 8 additions & 8 deletions tests/Unit/JsonApiCollectionResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ public function testBasicResourceCollectionResource()

$response->assertExactJson([
'data' => [
$this->createJsonResource($accounts[0]),
$this->createJsonResource($accounts[1]),
$this->createJsonResource($accounts[2]),
$this->createJsonResource($accounts[0], exceptAttributes: ['email']),
$this->createJsonResource($accounts[1], exceptAttributes: ['email']),
$this->createJsonResource($accounts[2], exceptAttributes: ['email']),
],
]);
}
Expand All @@ -38,8 +38,8 @@ public function testCollectionResourceWithRelations()

$response->assertExactJson([
'data' => [
...$this->createJsonResource($others),
$this->createJsonResource($author, [ 'posts' => $author->posts ]),
...$this->createJsonResource($others, exceptAttributes: ['email']),
$this->createJsonResource($author, [ 'posts' => $author->posts ], exceptAttributes: ['email']),
],
'included' => [
$this->createJsonResource($author->posts->first())
Expand Down Expand Up @@ -89,15 +89,15 @@ public function testCollectionResourceEnlargeResourceDepth1()

$response->assertExactJson([
'data' => [
$this->createJsonResource($authorClaire, [ 'posts' => $postsClaire ]),
$this->createJsonResource($authorClaire, [ 'posts' => $postsClaire ], exceptAttributes: ['email']),
],
'included' => [
$this->createJsonResource($postClaire, [ 'comments' => $postClaire->comments ]),
$this->createJsonResource($postClaire->comments[0], [ 'commenter' => $postClaire->comments[0]->commenter ]),
$this->createJsonResource($postClaire->comments[1], [ 'commenter' => $postClaire->comments[1]->commenter ]),
$this->createJsonResource($postClaire->comments[2], [ 'commenter' => $postClaire->comments[2]->commenter ]),
$this->createJsonResource($postClaire->comments[0]->commenter),
$this->createJsonResource($postClaire->comments[2]->commenter),
$this->createJsonResource($postClaire->comments[0]->commenter, exceptAttributes: ['email']),
$this->createJsonResource($postClaire->comments[2]->commenter, exceptAttributes: ['email']),
]
]);
}
Expand Down
22 changes: 11 additions & 11 deletions tests/Unit/JsonApiResourceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function testBasicResourceCollection()
$response = $this->getJson('test-route');

$response->assertExactJson([
'data' => $this->createJsonResource($accounts),
'data' => $this->createJsonResource($accounts, exceptAttributes: ['email']),
]);
}

Expand All @@ -54,7 +54,7 @@ public function testResourceWithEmptyRelationLoaded()
$response = $this->getJson('test-route');

$response->assertExactJson([
'data' => $this->createJsonResource($account),
'data' => $this->createJsonResource($account, exceptAttributes: ['email']),
]);
}

Expand All @@ -70,11 +70,10 @@ public function testRelatedResource()

$response->assertExactJson([
'data' => $this->createJsonResource($post, [ 'author' => $author]),
'included' => [$this->createJsonResource($author)],
'included' => [$this->createJsonResource($author, exceptAttributes: ['email'])],
]);
}


public function testRelatedResources()
{
$post = Post::factory()
Expand Down Expand Up @@ -111,7 +110,7 @@ public function testDuplicatedRelatedResources()
$response->assertExactJson([
'data' => $this->createJsonResource($post, ['author' => $author, 'comments' => [ $comment ]]),
'included' => [
$this->createJsonResource($author),
$this->createJsonResource($author, exceptAttributes: ['email']),
$this->createJsonResource($comment, [ 'commenter' => $author ]),
]
]);
Expand Down Expand Up @@ -140,13 +139,13 @@ public function testDeepRelatedResource()


$response->assertExactJson([
'data' => $this->createJsonResource($author, [ 'posts' => [ $post ]]),
'data' => $this->createJsonResource($author, [ 'posts' => [ $post ]], exceptAttributes: ['email']),
'included' => [
$this->createJsonResource($post, [ 'author' => $author, 'comments' => $comments ]),
$this->createJsonResource($author),
$this->createJsonResource($author, exceptAttributes: ['email']),
$this->createJsonResource($comments[0], [ 'commenter' => $commenter ]),
$this->createJsonResource($comments[1], [ 'commenter' => $authorAsCommenter ]),
$this->createJsonResource($commenter),
$this->createJsonResource($commenter, exceptAttributes: ['email']),
]
]);
}
Expand All @@ -171,10 +170,10 @@ public function testTooDeepRelatedResource()
$response = $this->getJson('test-route?includes=posts,posts.author,posts.comments,posts.comments.commenter');

$response->assertExactJson([
'data' => $this->createJsonResource($postAuthor, [ 'posts' => [ $post ]]),
'data' => $this->createJsonResource($postAuthor, [ 'posts' => [ $post ]], exceptAttributes: ['email']),
'included' => [
$this->createJsonResource($post, [ 'author' => $postAuthor, 'comments' => $comments ]),
$this->createJsonResource($postAuthor),
$this->createJsonResource($postAuthor, exceptAttributes: ['email']),
$this->createJsonResource($comments[0]),
$this->createJsonResource($comments[1]),
]
Expand All @@ -196,7 +195,8 @@ public function testResourceWithMetaData()
'data' => $this->createJsonResource(
modelOrCollection: $account,
relationships: [ 'posts' => $account->posts ],
meta: [ 'experienced_author' => true ]
meta: [ 'experienced_author' => true ],
exceptAttributes: ['email'],
),
'included' => $this->createJsonResource($account->posts)
]);
Expand Down