diff --git a/composer.json b/composer.json index 91bbc47..7adab71 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ ], "require": { "php": "^8.0", - "ext-json": "*" + "ext-json": "*", + "spatie/laravel-fractal": "^6.0" }, "require-dev": { "orchestra/testbench": "^8.0", diff --git a/src/Contracts/Format.php b/src/Contracts/Format.php index fad00e0..dab9f6e 100644 --- a/src/Contracts/Format.php +++ b/src/Contracts/Format.php @@ -27,7 +27,7 @@ interface Format * @param null $errors * @return array */ - public function data(?array $data, ?string $message, int $code, $errors = null): array; + public function format(?array $data, ?string $message, int $code, $errors = null): array; /** * Format paginator data. @@ -39,7 +39,7 @@ public function data(?array $data, ?string $message, int $code, $errors = null): * @param int $option * @return array */ - public function paginator(AbstractPaginator|AbstractCursorPaginator $resource, string $message = '', int $code = 200, array $headers = [], int $option = 0): array; + public function paginator(AbstractPaginator|AbstractCursorPaginator $resource); /** * Format collection resource data. @@ -51,17 +51,13 @@ public function paginator(AbstractPaginator|AbstractCursorPaginator $resource, s * @param int $option * @return array */ - public function resourceCollection(ResourceCollection $resource, string $message = '', int $code = 200, array $headers = [], int $option = 0): array; + public function resourceCollection(ResourceCollection $resource); /** * Format JsonResource Data. * * @param JsonResource $resource - * @param string $message - * @param int $code - * @param array $headers - * @param int $option * @return array */ - public function jsonResource(JsonResource $resource, string $message = '', int $code = 200, array $headers = [], int $option = 0): array; + public function jsonResource(JsonResource $resource); } diff --git a/src/Support/Format.php b/src/Support/Format.php index c03f2b0..e9217b8 100644 --- a/src/Support/Format.php +++ b/src/Support/Format.php @@ -11,14 +11,22 @@ namespace Jiannei\Response\Laravel\Support; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Http\JsonResponse; use Illuminate\Http\Resources\Json\JsonResource; use Illuminate\Http\Resources\Json\ResourceCollection; use Illuminate\Pagination\AbstractCursorPaginator; use Illuminate\Pagination\AbstractPaginator; +use Illuminate\Pagination\CursorPaginator; +use Illuminate\Pagination\Paginator; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Config; use Illuminate\Support\Traits\Macroable; +use League\Fractal\Pagination\Cursor; +use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use League\Fractal\Serializer\ArraySerializer; +use League\Fractal\Serializer\DataArraySerializer; +use Spatie\Fractal\Fractal; class Format implements \Jiannei\Response\Laravel\Contracts\Format { @@ -47,7 +55,7 @@ public function response($data = [], int $status = 200, array $headers = [], int * @param null $errors * @return array */ - public function data(?array $data, ?string $message, int $code, $errors = null): array + public function format(?array $data, ?string $message, int $code, $errors = null): array { return $this->formatDataFields([ 'status' => $this->formatStatus($code), @@ -62,61 +70,100 @@ public function data(?array $data, ?string $message, int $code, $errors = null): * Format paginator data. * * @param AbstractPaginator|AbstractCursorPaginator $resource - * @param string $message - * @param int $code - * @param array $headers - * @param int $option * @return array */ - public function paginator(AbstractPaginator|AbstractCursorPaginator $resource, string $message = '', int $code = 200, array $headers = [], int $option = 0): array + public function paginator(AbstractPaginator|AbstractCursorPaginator $resource) { - $paginated = $resource->toArray(); + $fractal = fractal()->collection($resource, function ($item) { + return $item->toArray(); + })->serializeWith(DataArraySerializer::class); + + return tap($fractal, function (Fractal $item) use ($resource) { + if ($resource instanceof CursorPaginator) { + return $item->withCursor(new Cursor( + $resource->cursor()?->encode(), + $resource->previousCursor()?->encode(), + $resource->nextCursor()?->encode(), + count($resource->items())) + ); + } - $paginationInformation = $this->formatPaginatedData($paginated); + if ($resource instanceof LengthAwarePaginator) { + return $item->paginateWith(new IlluminatePaginatorAdapter($resource)); + } - $data = array_merge_recursive(['data' => $paginated['data']], $paginationInformation); + if ($resource instanceof Paginator) { + return $item->addMeta([ + 'pagination' => [ + 'count' => count($resource->items()), + 'per_page' => $resource->perPage(), + 'current_page' => $resource->currentPage(), + 'links' => [ + 'previous' => $resource->previousPageUrl(), + 'next' => $resource->nextPageUrl() + ] + ], + ]); + } - return $this->data($data, $message, $code); + return $item; + }); } /** * Format collection resource data. * - * @param ResourceCollection $resource - * @param string $message - * @param int $code - * @param array $headers - * @param int $option + * @param ResourceCollection $collection * @return array */ - public function resourceCollection(ResourceCollection $resource, string $message = '', int $code = 200, array $headers = [], int $option = 0): array + public function resourceCollection(ResourceCollection $collection): array { - $data = array_merge_recursive(['data' => $resource->resolve(request())], $resource->with(request()), $resource->additional); - if ($resource->resource instanceof AbstractPaginator || $resource->resource instanceof AbstractCursorPaginator) { - $paginated = $resource->resource->toArray(); - $paginationInformation = $this->formatPaginatedData($paginated); + $fractal = fractal()->collection($collection->resource,function (JsonResource $resource){ + return array_merge_recursive($resource->resolve(request()), $resource->with(request()), $resource->additional); + })->serializeWith(DataArraySerializer::class); + + return tap($fractal, function (Fractal $item) use ($collection) { + if ($collection->resource instanceof CursorPaginator) { + return $item->withCursor(new Cursor( + $collection->resource->cursor()?->encode(), + $collection->resource->previousCursor()?->encode(), + $collection->resource->nextCursor()?->encode(), + count($collection->resource->items())) + ); + } - $data = array_merge_recursive($data, $paginationInformation); - } + if ($collection->resource instanceof LengthAwarePaginator) { + return $item->paginateWith(new IlluminatePaginatorAdapter($collection->resource)); + } + + if ($collection->resource instanceof Paginator) { + return $item->addMeta([ + 'pagination' => [ + 'count' => count($collection->resource->items()), + 'per_page' => $collection->resource->perPage(), + 'current_page' => $collection->resource->currentPage(), + 'links' => [ + 'previous' => $collection->resource->previousPageUrl(), + 'next' => $collection->resource->nextPageUrl() + ] + ], + ]); + } - return $this->data($data, $message, $code); + return $item; + }); } /** * Format JsonResource Data. * * @param JsonResource $resource - * @param string $message - * @param int $code - * @param array $headers - * @param int $option - * @return array */ - public function jsonResource(JsonResource $resource, string $message = '', int $code = 200, array $headers = [], int $option = 0): array + public function jsonResource(JsonResource $resource) { - $resourceData = array_merge_recursive($resource->resolve(request()), $resource->with(request()), $resource->additional); - - return $this->data($resourceData, $message, $code); + return fractal()->item($resource->resource,function (JsonResource $resource){ + return array_merge_recursive($resource->resolve(request()), $resource->with(request()), $resource->additional); + })->serializeWith(ArraySerializer::class); } /** @@ -166,35 +213,6 @@ protected function formatStatusCode($code): int return (int) substr($code, 0, 3); } - /** - * Format paginated data. - * - * @param array $paginated - * @return array - */ - protected function formatPaginatedData(array $paginated): array - { - return [ - 'meta' => [ - 'pagination' => [ - 'total' => $paginated['total'] ?? 0, - 'count' => $paginated['to'] ?? 0, - 'per_page' => $paginated['per_page'] ?? 0, - 'current_page' => $paginated['current_page'] ?? 0, - 'total_pages' => $paginated['last_page'] ?? 0, - 'links' => [ - 'previous' => $paginated['prev_page_url'] ?? '', - 'next' => $paginated['next_page_url'] ?? '', - ], - 'cursor' => [ - 'previous' => $paginated['prev_cursor'] ?? '', - 'next' => $paginated['next_cursor'] ?? '', - ], - ], - ], - ]; - } - /** * Format response data fields. * diff --git a/src/Support/Serializers/ArraySerializer.php b/src/Support/Serializers/ArraySerializer.php deleted file mode 100644 index 9602ea3..0000000 --- a/src/Support/Serializers/ArraySerializer.php +++ /dev/null @@ -1,73 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Jiannei\Response\Laravel\Support\Serializers; - -use Illuminate\Support\Facades\Config; -use League\Fractal\Pagination\PaginatorInterface; -use League\Fractal\Serializer\ArraySerializer as FractalArraySerializer; - -class ArraySerializer extends FractalArraySerializer -{ - /** - * Serialize a collection. - * - * @param string|null $resourceKey - * @param array $data - * @return array - */ - public function collection(?string $resourceKey, array $data): array - { - $paginationDataField = Config::get('response.format.fields.data.fields.data.alias', 'data'); - - return [$resourceKey ?: $paginationDataField => $data]; - } - - /** - * Serialize the paginator. - * - * @param PaginatorInterface $paginator - * @return array - */ - public function paginator(PaginatorInterface $paginator): array - { - $currentPage = (int) $paginator->getCurrentPage(); - $lastPage = (int) $paginator->getLastPage(); - $isSimplePaginator = property_exists($paginator->getPaginator(), 'hasMore'); - - $pagination = [ - 'total' => (int) $paginator->getTotal(), - 'count' => (int) $paginator->getCount(), - 'per_page' => (int) $paginator->getPerPage(), - 'current_page' => $currentPage, - 'total_pages' => $lastPage, - 'links' => [], - ]; - - if ($currentPage > 1) { - $pagination['links']['previous'] = $paginator->getUrl($currentPage - 1); - } - - if ($currentPage < $lastPage || ($isSimplePaginator && $paginator->getPaginator()->hasMore)) { - $pagination['links']['next'] = $paginator->getUrl($currentPage + 1); - } - - if (empty($pagination['links'])) { - $pagination['links'] = (object) []; - } - - if ($isSimplePaginator) { - unset($pagination['total'], $pagination['total_pages']); - } - - return ['pagination' => $pagination]; - } -} diff --git a/src/Support/Serializers/SimpleArraySerializer.php b/src/Support/Serializers/SimpleArraySerializer.php deleted file mode 100644 index 064632b..0000000 --- a/src/Support/Serializers/SimpleArraySerializer.php +++ /dev/null @@ -1,27 +0,0 @@ - - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Jiannei\Response\Laravel\Support\Serializers; - -class SimpleArraySerializer extends ArraySerializer -{ - /** - * Serialize a collection. - * - * @param string|null $resourceKey - * @param array $data - * @return array - */ - public function collection(?string $resourceKey, array $data): array - { - return $data; - } -} diff --git a/src/Support/Traits/JsonResponseTrait.php b/src/Support/Traits/JsonResponseTrait.php index 2962c7f..e1a7ba4 100644 --- a/src/Support/Traits/JsonResponseTrait.php +++ b/src/Support/Traits/JsonResponseTrait.php @@ -199,39 +199,21 @@ public function fail(string $message = '', int $code = 500, $errors = null, arra public function success($data = [], string $message = '', int $code = 200, array $headers = [], int $option = 0) { if ($data instanceof ResourceCollection) { - return tap( - $this->formatter->response($this->formatter->resourceCollection(...func_get_args()), $code, $headers, $option), - function ($response) use ($data) { - $response->original = $data->resource->map( - function ($item) { - return is_array($item) ? Arr::get($item, 'resource') : $item->resource; - } - ); - - $data->withResponse(request(), $response); - } - ); + $data = $this->formatter->resourceCollection($data); } if ($data instanceof JsonResource) { - return tap( - $this->formatter->response($this->formatter->jsonResource(...func_get_args()), $code, $headers, $option), - function ($response) use ($data) { - $response->original = $data->resource; - - $data->withResponse(request(), $response); - } - ); + $data = $this->formatter->jsonResource($data); } if ($data instanceof AbstractPaginator || $data instanceof AbstractCursorPaginator) { - return $this->formatter->response($this->formatter->paginator(...func_get_args()), $code, $headers, $option); + $data = $this->formatter->paginator($data); } - if ($data instanceof Arrayable || (is_object($data) && method_exists($data, 'toJson'))) { + if ($data instanceof Arrayable || (is_object($data) && method_exists($data, 'toArray'))) { $data = $data->toArray(); } - return $this->formatter->response($this->formatter->data(Arr::wrap($data), $message, $code), $code, $headers, $option); + return $this->formatter->response($this->formatter->format(Arr::wrap($data), $message, $code), $code, $headers, $option); } }