diff --git a/src/Traits/JsonResourceHelper.php b/src/Traits/JsonResourceHelper.php new file mode 100644 index 0000000..d7fefaf --- /dev/null +++ b/src/Traits/JsonResourceHelper.php @@ -0,0 +1,121 @@ +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; + } +} \ No newline at end of file diff --git a/tests/TestCase.php b/tests/TestCase.php index d996a5e..f77ff95 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -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; protected function getEnvironmentSetUp($app) { $app['config']->set('database.default', 'testing'); @@ -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; - } } \ No newline at end of file diff --git a/tests/Unit/JsonApiCollectionResourceTest.php b/tests/Unit/JsonApiCollectionResourceTest.php index 2dadce0..03d7df6 100644 --- a/tests/Unit/JsonApiCollectionResourceTest.php +++ b/tests/Unit/JsonApiCollectionResourceTest.php @@ -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']), ], ]); } @@ -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()) @@ -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']), ] ]); } diff --git a/tests/Unit/JsonApiResourceTest.php b/tests/Unit/JsonApiResourceTest.php index be4d1ab..363c160 100644 --- a/tests/Unit/JsonApiResourceTest.php +++ b/tests/Unit/JsonApiResourceTest.php @@ -39,7 +39,7 @@ public function testBasicResourceCollection() $response = $this->getJson('test-route'); $response->assertExactJson([ - 'data' => $this->createJsonResource($accounts), + 'data' => $this->createJsonResource($accounts, exceptAttributes: ['email']), ]); } @@ -54,7 +54,7 @@ public function testResourceWithEmptyRelationLoaded() $response = $this->getJson('test-route'); $response->assertExactJson([ - 'data' => $this->createJsonResource($account), + 'data' => $this->createJsonResource($account, exceptAttributes: ['email']), ]); } @@ -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() @@ -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 ]), ] ]); @@ -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']), ] ]); } @@ -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]), ] @@ -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) ]);