From b2ab8f2ee10d2e45190490630528e6b20ec6beed Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 15:48:22 -0500 Subject: [PATCH 01/45] initial pass at resources --- .../Contracts/Database/CastsToResource.php | 22 + .../Database/Eloquent/DetectsResource.php | 55 +++ .../Http/Middleware/CastToResource.php | 89 ++++ src/Illuminate/Http/Resource.php | 422 ++++++++++++++++++ .../Http/Resources/CssResourceResponse.php | 34 ++ .../Http/Resources/HtmlResourceResponse.php | 34 ++ .../Http/Resources/JsonResourceResponse.php | 113 +++++ .../PaginatedJsonResourceResponse.php | 83 ++++ .../Http/Resources/ResourceResponse.php | 164 +++++++ .../Pagination/LengthAwarePaginator.php | 2 + src/Illuminate/Pagination/Paginator.php | 1 + 11 files changed, 1019 insertions(+) create mode 100644 src/Illuminate/Contracts/Database/CastsToResource.php create mode 100644 src/Illuminate/Database/Eloquent/DetectsResource.php create mode 100644 src/Illuminate/Http/Middleware/CastToResource.php create mode 100644 src/Illuminate/Http/Resource.php create mode 100644 src/Illuminate/Http/Resources/CssResourceResponse.php create mode 100644 src/Illuminate/Http/Resources/HtmlResourceResponse.php create mode 100644 src/Illuminate/Http/Resources/JsonResourceResponse.php create mode 100644 src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php create mode 100644 src/Illuminate/Http/Resources/ResourceResponse.php diff --git a/src/Illuminate/Contracts/Database/CastsToResource.php b/src/Illuminate/Contracts/Database/CastsToResource.php new file mode 100644 index 000000000000..49eac6be3cee --- /dev/null +++ b/src/Illuminate/Contracts/Database/CastsToResource.php @@ -0,0 +1,22 @@ +detectResourceName(); + + return new $class($model); + } + + /** + * Cast the given collection into a resource. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Support\Collection $collection + */ + public static function castCollectionToResource($request, $collection) + { + $class = $collection->first()->detectResourceName('Collection'); + + return new $class($collection); + } + + /** + * Detect the resource name for the model. + * + * @param string $suffix + * @return string + */ + public function detectResourceName($suffix = '') + { + $segments = explode('\\', get_class($this)); + + $base = array_pop($segments); + + if (class_exists($class = implode('\\', $segments).'\\Http\\Resources\\'.$base.$suffix)) { + return $class; + } + + throw new Exception( + "Unable to detect the resource for the [".get_class($this)."] model." + ); + } +} diff --git a/src/Illuminate/Http/Middleware/CastToResource.php b/src/Illuminate/Http/Middleware/CastToResource.php new file mode 100644 index 000000000000..cf187d4c7138 --- /dev/null +++ b/src/Illuminate/Http/Middleware/CastToResource.php @@ -0,0 +1,89 @@ +original)) { + return $response; + } + + if ($response->original instanceof Model && + $response->original instanceof CastsToResource) { + return $this->castModelToResource($request, $response); + } + + if ($response->original instanceof Collection && + $response->original->first() instanceof CastsToResource) { + return $this->castCollectionToResource($request, $response); + } + + if ($response->original instanceof AbstractPaginator && + $response->original->getCollection()->first() instanceof CastsToResource) { + return $this->castPaginatorToResource($request, $response); + } + + return $response; + } + + /** + * Cast the model sent within the response to a resource response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return \Illuminate\Http\Response + */ + protected function castModelToResource($request, $response) + { + return $response->original->castToResource( + $request, $response->original + )->toResponse($request); + } + + /** + * Cast the collection sent within the response to a resource response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return \Illuminate\Http\Response + */ + protected function castCollectionToResource($request, $response) + { + return $response->original->first()->castCollectionToResource( + $request, $response->original + )->toResponse($request); + } + + /** + * Cast the collection sent within the response to a resource response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return \Illuminate\Http\Response + */ + protected function castPaginatorToResource($request, $response) + { + $collection = $response->original->getCollection(); + + return $collection->first()->castCollectionToResource( + $request, $collection + )->toResponse($request); + } +} diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php new file mode 100644 index 000000000000..fd1469db7e73 --- /dev/null +++ b/src/Illuminate/Http/Resource.php @@ -0,0 +1,422 @@ +resource = $resource; + } + + /** + * Create a resource response based on the incoming request. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function response($request) + { + if (static::hasMacro($format = $request->format())) { + return call_user_func_array(static::$macros[$format], $request); + } + + if ($request->expectsJson()) { + return $this->json(); + } + + switch ($format) { + case 'html': + return $this->html(); + + case 'css': + return $this->css(); + + default: + return $this->json(); + } + } + + /** + * Create a new HTML resource response for the given resource. + * + * @return \App\ResourceResponse + */ + public function html() + { + return new Resources\HtmlResourceResponse(get_class($this), $this->resource); + } + + /** + * Create a new CSS resource response for the given resource. + * + * @return \App\ResourceResponse + */ + public function css() + { + return new Resources\CssResourceResponse(get_class($this), $this->resource); + } + + /** + * Create a new JSON resource response for the given resource. + * + * @return \App\ResourceResponse + */ + public function json() + { + return $this->resource instanceof AbstractPaginator + ? new Resources\PaginatedJsonResourceResponse(get_class($this), $this->resource) + : new Resources\JsonResourceResponse(get_class($this), $this->resource); + } + + /** + * Transform the resource into a JSON array. + * + * @param \Illuminate\Http\Request + * @return array + */ + public function toJson($request) + { + return $this->isCollectionResource() && $this->singularResource() + ? $this->collectionToJson($request) + : $this->resourceToJson($request); + } + + /** + * Determine if this resource is a collection resource. + * + * @return bool + */ + protected function isCollectionResource() + { + return $this->resource instanceof Collection && + Str::endsWith(get_class($this), 'Collection'); + } + + /** + * Convert the collection into a JSON array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function collectionToJson($request) + { + $data = $this->mapInto( + $this->singularResource() + )->map->toJson($request)->all(); + + return static::$wrap ? [static::$wrap => $data] : $data; + } + + /** + * Get the singular version of this resource class, if applicable. + * + * @return string|null + */ + protected function singularResource() + { + if (class_exists($class = Str::replaceLast('Collection', '', get_class($this)))) { + return $class; + } + } + + /** + * Convert the resource into a JSON array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function resourceToJson($request) + { + $values = $this->resource->toArray(); + + if (count($this->visible) > 0) { + $values = array_intersect_key($values, array_flip($this->visible)); + } + + if (count($this->hidden) > 0) { + $values = array_diff_key($values, array_flip($this->hidden)); + } + + return $values; + } + + /** + * Customize the response for a request. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return void + */ + public function withResponse($request, $response) + { + // + } + + /** + * Customize the response for a HTML request. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return void + */ + public function withHtmlResponse($request, $response) + { + // + } + + /** + * Customize the response for a CSS request. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return void + */ + public function withCssResponse($request, $response) + { + // + } + + /** + * Customize the response for a JSON request. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return void + */ + public function withJsonResponse($request, $response) + { + // + } + + /** + * Get the value of the resource's route key. + * + * @return mixed + */ + public function getRouteKey() + { + return $this->resource->getRouteKey(); + } + + /** + * Get the route key for the resource. + * + * @return string + */ + public function getRouteKeyName() + { + return $this->resource->getRouteKeyName(); + } + + /** + * Retrieve the model for a bound value. + * + * @param mixed $value + * @return static + */ + public function resolveRouteBinding($value) + { + throw new Exception("Resources may not be implicitly resolved from route bindings."); + } + + /** + * Set the string that should wrap the outer-most JSON array. + * + * @param string $value + * @return void + */ + public static function wrap($value) + { + static::$wrap = $value; + } + + /** + * Disable wrapping of the outer-most JSON array. + * + * @param string $value + * @return void + */ + public static function withoutWrapping() + { + static::$wrap = null; + } + + /** + * Create an HTTP response that represents the object. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function toResponse($request) + { + return $this->response($request)->toResponse($request); + } + + /** + * Get an iterator for the resource. + * + * @return \ArrayIterator + */ + public function getIterator() + { + if (is_array($this->resource)) { + return new ArrayIterator($this->resource); + } elseif ($this->resource instanceof IteratorAggregate) { + return $this->resource->getIterator(); + } + + throw new Exception( + "Unable to generate an iterator for this resource." + ); + } + + /** + * Prepare the resource for JSON serialization. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toJson(Container::getInstance()->make('request')); + } + + /** + * Determine if the given attribute exists. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($this->resource[$offset]); + } + + /** + * Get the value for a given offset. + * + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->resource[$offset]; + } + + /** + * Set the value for a given offset. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->resource[$offset] = $value; + } + + /** + * Unset the value for a given offset. + * + * @param mixed $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->resource[$offset]); + } + + /** + * Determine if an attribute exists on the resource. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->resource->{$key}); + } + + /** + * Unset an attribute on the resource. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->resource->{$key}); + } + + /** + * Dynamically get properties from the underlying resource. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->resource->{$key}; + } + + /** + * Dynamically pass method calls to the underlying resource. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->resource->{$method}(...$parameters); + } +} diff --git a/src/Illuminate/Http/Resources/CssResourceResponse.php b/src/Illuminate/Http/Resources/CssResourceResponse.php new file mode 100644 index 000000000000..6565da2a31b3 --- /dev/null +++ b/src/Illuminate/Http/Resources/CssResourceResponse.php @@ -0,0 +1,34 @@ +build($request, response( + $this->instance()->toCss($request), + 200, ['Content-Type' => 'text/css'] + )); + } + + /** + * Build the finished HTTP response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return \Illuminate\Http\Response + */ + protected function build($request, $response) + { + return tap(parent::build($request, $response), function ($response) use ($request) { + $this->instance()->withCssResponse($request, $response); + }); + } +} diff --git a/src/Illuminate/Http/Resources/HtmlResourceResponse.php b/src/Illuminate/Http/Resources/HtmlResourceResponse.php new file mode 100644 index 000000000000..99379a229b2f --- /dev/null +++ b/src/Illuminate/Http/Resources/HtmlResourceResponse.php @@ -0,0 +1,34 @@ +build($request, response( + $this->instance()->toHtml($request), + $this->calculateStatus(), $this->headers + )); + } + + /** + * Build the finished HTTP response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return \Illuminate\Http\Response + */ + protected function build($request, $response) + { + return tap(parent::build($request, $response), function ($response) use ($request) { + $this->instance()->withHtmlResponse($request, $response); + }); + } +} diff --git a/src/Illuminate/Http/Resources/JsonResourceResponse.php b/src/Illuminate/Http/Resources/JsonResourceResponse.php new file mode 100644 index 000000000000..0b2f1dece246 --- /dev/null +++ b/src/Illuminate/Http/Resources/JsonResourceResponse.php @@ -0,0 +1,113 @@ +build($request, response()->json( + array_merge($this->wrap($this->instance()->toJson($request)), $this->with), + $this->calculateStatus(), $this->headers + )); + } + + /** + * Wrap the given data if necessary. + * + * @param array $data + * @return array + */ + protected function wrap($data) + { + if ($data instanceof Collection) { + $data = $data->all(); + } + + if ($this->haveDefaultWrapperAndDataIsUnwrapped($data)) { + $data = [$this->wrapper() => $data]; + } elseif ($this->haveAdditionalInformationAndDataIsUnwrapped($data)) { + $data = [($this->wrapper() ?? 'data') => $data]; + } + + return $data; + } + + /** + * Determine if we have a default wrapper and the given data is unwrapped. + * + * @param array $data + * @return bool + */ + protected function haveDefaultWrapperAndDataIsUnwrapped($data) + { + return $this->wrapper() && ! array_key_exists($this->wrapper(), $data); + } + + /** + * Determine if "with" data has been added and our data is unwrapped. + * + * @param array $data + * @return bool + */ + protected function haveAdditionalInformationAndDataIsUnwrapped($data) + { + return ! empty($this->with) && + (! $this->wrapper() || + ! array_key_exists($this->wrapper(), $data)); + } + + /** + * Add the given array to the response body. + * + * @param array $values + * @return $this + */ + public function with(array $values) + { + $this->with = array_merge($this->with, $values); + + return $this; + } + + /** + * Get the default data wrapper for the resource. + * + * @return string + */ + protected function wrapper() + { + $class = $this->class; + + return $class::$wrap; + } + + /** + * Build the finished HTTP response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return \Illuminate\Http\Response + */ + protected function build($request, $response) + { + return tap(parent::build($request, $response), function ($response) use ($request) { + $this->instance()->withJsonResponse($request, $response); + }); + } +} diff --git a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php new file mode 100644 index 000000000000..cbfe7bc963b5 --- /dev/null +++ b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php @@ -0,0 +1,83 @@ +addPaginationInformation($request); + + return $this->build($request, response()->json( + array_merge($this->wrap($this->instance()->toJson($request)), $this->with), + $this->calculateStatus(), $this->headers + )); + } + + /** + * Add the pagination information to the response. + * + * @param \Illuminate\Http\Request $request + * @return void + */ + protected function addPaginationInformation($request) + { + return $this->with([ + 'links' => $this->paginationLinks($paginated = $this->resource->toArray($request)), + 'meta' => $this->meta($paginated), + ]); + } + + /** + * Get the pagination links for the response. + * + * @param array $paginated + * @return array + */ + protected function paginationLinks($paginated) + { + return array_merge($this->with['link'] ?? [], [ + 'first' => $paginated['first_page_url'] ?? null, + 'last' => $paginated['last_page_url'] ?? null, + 'prev' => $paginated['prev_page_url'] ?? null, + 'next' => $paginated['next_page_url'] ?? null, + ]); + } + + /** + * Gather the meta data for the response. + * + * @param array $paginated + * @return array + */ + protected function meta($paginated) + { + return array_merge($this->with['meta'] ?? [], Arr::except($paginated, [ + 'data', + 'first_page_url', + 'last_page_url', + 'prev_page_url', + 'next_page_url', + ])); + } + + /** + * Get an instance of the resource class. + * + * @return mixed + */ + protected function instance() + { + $class = $this->class; + + return new $class($this->resource->getCollection()); + } +} diff --git a/src/Illuminate/Http/Resources/ResourceResponse.php b/src/Illuminate/Http/Resources/ResourceResponse.php new file mode 100644 index 000000000000..070601f28ed8 --- /dev/null +++ b/src/Illuminate/Http/Resources/ResourceResponse.php @@ -0,0 +1,164 @@ +class = $class; + $this->resource = $resource; + + $this->withResponse(function ($request, $response) { + return $response; + }); + } + + /** + * Set the HTTP status code on the response. + * + * @param int $status + * @return $this + */ + public function status($status) + { + $this->status = $status; + + return $this; + } + + /** + * Set a header on the response. + * + * @param string $key + * @param string $value + * @return $this + */ + public function header($key, $value) + { + $this->headers[$key] = $value; + + return $this; + } + + /** + * Add an array of headers to the response. + * + * @param \Symfony\Component\HttpFoundation\HeaderBag|array $headers + * @return $this + */ + public function withHeaders($headers) + { + if ($headers instanceof HeaderBag) { + $headers = $headers->all(); + } + + foreach ($headers as $key => $value) { + $this->headers[$key] = $value; + } + + return $this; + } + + /** + * Build the finished HTTP response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return \Illuminate\Http\Response + */ + protected function build($request, $response) + { + return tap($response, function ($response) use ($request) { + call_user_func($this->callback, $request, $response); + + $this->instance()->withResponse($request, $response); + }); + } + + /** + * Register a callback that will be used to customize the response. + * + * @param \Closure $callback + * @return $this + */ + public function withResponse($callback) + { + $this->callback = $callback; + + return $this; + } + + /** + * Calculate the appropriate status code for the response. + * + * @return int + */ + protected function calculateStatus() + { + if ($this->status) { + return $this->status; + } + + return $this->resource instanceof Model && + $this->resource->wasRecentlyCreated ? 201 : 200; + } + + /** + * Get an instance of the resource class. + * + * @return mixed + */ + protected function instance() + { + $class = $this->class; + + return new $class($this->resource); + } +} diff --git a/src/Illuminate/Pagination/LengthAwarePaginator.php b/src/Illuminate/Pagination/LengthAwarePaginator.php index 353c8c00d2ef..3faf65e9b611 100644 --- a/src/Illuminate/Pagination/LengthAwarePaginator.php +++ b/src/Illuminate/Pagination/LengthAwarePaginator.php @@ -163,8 +163,10 @@ public function toArray() return [ 'current_page' => $this->currentPage(), 'data' => $this->items->toArray(), + 'first_page_url' => $this->url(1), 'from' => $this->firstItem(), 'last_page' => $this->lastPage(), + 'last_page_url' => $this->url($this->lastPage()), 'next_page_url' => $this->nextPageUrl(), 'path' => $this->path, 'per_page' => $this->perPage(), diff --git a/src/Illuminate/Pagination/Paginator.php b/src/Illuminate/Pagination/Paginator.php index 71e4c9ff6c44..9fd5e1c9b0ac 100644 --- a/src/Illuminate/Pagination/Paginator.php +++ b/src/Illuminate/Pagination/Paginator.php @@ -144,6 +144,7 @@ public function toArray() return [ 'current_page' => $this->currentPage(), 'data' => $this->items->toArray(), + 'first_page_url' => $this->url(1), 'from' => $this->firstItem(), 'next_page_url' => $this->nextPageUrl(), 'path' => $this->path, From ff521c93908ddf13a0b592db1ea51db5b8c854f8 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 16:11:20 -0500 Subject: [PATCH 02/45] tweak how resources are passed --- src/Illuminate/Http/Resource.php | 8 +++--- .../Http/Resources/CssResourceResponse.php | 4 +-- .../Http/Resources/HtmlResourceResponse.php | 4 +-- .../Http/Resources/JsonResourceResponse.php | 6 ++--- .../PaginatedJsonResourceResponse.php | 16 ++---------- .../Http/Resources/ResourceResponse.php | 25 ++----------------- 6 files changed, 15 insertions(+), 48 deletions(-) diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php index fd1469db7e73..4374b16ae58a 100644 --- a/src/Illuminate/Http/Resource.php +++ b/src/Illuminate/Http/Resource.php @@ -93,7 +93,7 @@ public function response($request) */ public function html() { - return new Resources\HtmlResourceResponse(get_class($this), $this->resource); + return new Resources\HtmlResourceResponse($this); } /** @@ -103,7 +103,7 @@ public function html() */ public function css() { - return new Resources\CssResourceResponse(get_class($this), $this->resource); + return new Resources\CssResourceResponse($this); } /** @@ -114,8 +114,8 @@ public function css() public function json() { return $this->resource instanceof AbstractPaginator - ? new Resources\PaginatedJsonResourceResponse(get_class($this), $this->resource) - : new Resources\JsonResourceResponse(get_class($this), $this->resource); + ? new Resources\PaginatedJsonResourceResponse($this) + : new Resources\JsonResourceResponse($this); } /** diff --git a/src/Illuminate/Http/Resources/CssResourceResponse.php b/src/Illuminate/Http/Resources/CssResourceResponse.php index 6565da2a31b3..135ffe54f878 100644 --- a/src/Illuminate/Http/Resources/CssResourceResponse.php +++ b/src/Illuminate/Http/Resources/CssResourceResponse.php @@ -13,7 +13,7 @@ class CssResourceResponse extends ResourceResponse public function toResponse($request) { return $this->build($request, response( - $this->instance()->toCss($request), + $this->resource->toCss($request), 200, ['Content-Type' => 'text/css'] )); } @@ -28,7 +28,7 @@ public function toResponse($request) protected function build($request, $response) { return tap(parent::build($request, $response), function ($response) use ($request) { - $this->instance()->withCssResponse($request, $response); + $this->resource->withCssResponse($request, $response); }); } } diff --git a/src/Illuminate/Http/Resources/HtmlResourceResponse.php b/src/Illuminate/Http/Resources/HtmlResourceResponse.php index 99379a229b2f..e7c9cb99de64 100644 --- a/src/Illuminate/Http/Resources/HtmlResourceResponse.php +++ b/src/Illuminate/Http/Resources/HtmlResourceResponse.php @@ -13,7 +13,7 @@ class HtmlResourceResponse extends ResourceResponse public function toResponse($request) { return $this->build($request, response( - $this->instance()->toHtml($request), + $this->resource->toHtml($request), $this->calculateStatus(), $this->headers )); } @@ -28,7 +28,7 @@ public function toResponse($request) protected function build($request, $response) { return tap(parent::build($request, $response), function ($response) use ($request) { - $this->instance()->withHtmlResponse($request, $response); + $this->resource->withHtmlResponse($request, $response); }); } } diff --git a/src/Illuminate/Http/Resources/JsonResourceResponse.php b/src/Illuminate/Http/Resources/JsonResourceResponse.php index 0b2f1dece246..4aa401d3a71f 100644 --- a/src/Illuminate/Http/Resources/JsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/JsonResourceResponse.php @@ -22,7 +22,7 @@ class JsonResourceResponse extends ResourceResponse public function toResponse($request) { return $this->build($request, response()->json( - array_merge($this->wrap($this->instance()->toJson($request)), $this->with), + array_merge($this->wrap($this->resource->toJson($request)), $this->with), $this->calculateStatus(), $this->headers )); } @@ -92,7 +92,7 @@ public function with(array $values) */ protected function wrapper() { - $class = $this->class; + $class = get_class($this->resource); return $class::$wrap; } @@ -107,7 +107,7 @@ protected function wrapper() protected function build($request, $response) { return tap(parent::build($request, $response), function ($response) use ($request) { - $this->instance()->withJsonResponse($request, $response); + $this->resource->withJsonResponse($request, $response); }); } } diff --git a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php index cbfe7bc963b5..12fb7c20c159 100644 --- a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php @@ -17,7 +17,7 @@ public function toResponse($request) $this->addPaginationInformation($request); return $this->build($request, response()->json( - array_merge($this->wrap($this->instance()->toJson($request)), $this->with), + array_merge($this->wrap($this->resource->toJson($request)), $this->with), $this->calculateStatus(), $this->headers )); } @@ -31,7 +31,7 @@ public function toResponse($request) protected function addPaginationInformation($request) { return $this->with([ - 'links' => $this->paginationLinks($paginated = $this->resource->toArray($request)), + 'links' => $this->paginationLinks($paginated = $this->resource->resource->toArray($request)), 'meta' => $this->meta($paginated), ]); } @@ -68,16 +68,4 @@ protected function meta($paginated) 'next_page_url', ])); } - - /** - * Get an instance of the resource class. - * - * @return mixed - */ - protected function instance() - { - $class = $this->class; - - return new $class($this->resource->getCollection()); - } } diff --git a/src/Illuminate/Http/Resources/ResourceResponse.php b/src/Illuminate/Http/Resources/ResourceResponse.php index 070601f28ed8..a2ff7e7e15d3 100644 --- a/src/Illuminate/Http/Resources/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/ResourceResponse.php @@ -8,13 +8,6 @@ abstract class ResourceResponse implements Responsable { - /** - * The class name of the resource. - * - * @var string - */ - public $class; - /** * The underlying resource. * @@ -46,13 +39,11 @@ abstract class ResourceResponse implements Responsable /** * Create a new resource repsonse. * - * @param string $class * @param mixed $resource * @return void */ - public function __construct($class, $resource) + public function __construct($resource) { - $this->class = $class; $this->resource = $resource; $this->withResponse(function ($request, $response) { @@ -118,7 +109,7 @@ protected function build($request, $response) return tap($response, function ($response) use ($request) { call_user_func($this->callback, $request, $response); - $this->instance()->withResponse($request, $response); + $this->resource->withResponse($request, $response); }); } @@ -149,16 +140,4 @@ protected function calculateStatus() return $this->resource instanceof Model && $this->resource->wasRecentlyCreated ? 201 : 200; } - - /** - * Get an instance of the resource class. - * - * @return mixed - */ - protected function instance() - { - $class = $this->class; - - return new $class($this->resource); - } } From c6ccc2080a9dd84787d1ab1b2b834b2de1adbaa5 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 16:14:46 -0500 Subject: [PATCH 03/45] formatting --- .../Http/Resources/PaginatedJsonResourceResponse.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php index 12fb7c20c159..8d1c17a4843d 100644 --- a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php @@ -30,8 +30,10 @@ public function toResponse($request) */ protected function addPaginationInformation($request) { + $paginated = $this->resource->resource->toArray(); + return $this->with([ - 'links' => $this->paginationLinks($paginated = $this->resource->resource->toArray($request)), + 'links' => $this->paginationLinks($paginated), 'meta' => $this->meta($paginated), ]); } From 47912ef1f48378f223f4a548e248433a4c75943c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 16:57:53 -0500 Subject: [PATCH 04/45] tests for resources --- .../Http/Resources/ResourceResponse.php | 4 +- tests/Integration/Http/ResourceTest.php | 275 ++++++++++++++++++ 2 files changed, 277 insertions(+), 2 deletions(-) create mode 100644 tests/Integration/Http/ResourceTest.php diff --git a/src/Illuminate/Http/Resources/ResourceResponse.php b/src/Illuminate/Http/Resources/ResourceResponse.php index a2ff7e7e15d3..1cc8d12822f1 100644 --- a/src/Illuminate/Http/Resources/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/ResourceResponse.php @@ -137,7 +137,7 @@ protected function calculateStatus() return $this->status; } - return $this->resource instanceof Model && - $this->resource->wasRecentlyCreated ? 201 : 200; + return $this->resource->resource instanceof Model && + $this->resource->resource->wasRecentlyCreated ? 201 : 200; } } diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php new file mode 100644 index 000000000000..bfc59a4519d9 --- /dev/null +++ b/tests/Integration/Http/ResourceTest.php @@ -0,0 +1,275 @@ + 5, + 'title' => 'Test Title', + ])); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + 'id' => 5, + 'title' => 'Test Title', + ] + ]); + } + + public function test_resources_may_be_serializable() + { + Route::get('/', function () { + return new SerializablePostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + 'id' => 5, + 'title' => 'Test Title', + ] + ]); + } + + public function test_resources_may_customize_responses() + { + Route::get('/', function () { + return new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + $response->assertHeader('X-Resource', 'True'); + } + + public function test_resources_may_be_converted_to_html() + { + Route::get('/', function () { + return new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'text/html'] + ); + + $response->assertStatus(200); + + $this->assertEquals('html', $response->original); + } + + public function test_resources_may_be_converted_to_css() + { + Route::get('/', function () { + return new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'text/css'] + ); + + $response->assertStatus(200); + + $this->assertEquals('css', $response->original); + } + + public function test_resources_may_receive_proper_status_code_for_fresh_models() + { + Route::get('/', function () { + $post = new Post([ + 'id' => 5, + 'title' => 'Test Title', + ]); + + $post->wasRecentlyCreated = true; + + return new PostResource($post); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(201); + } + + public function test_collections_are_not_doubled_wrapped() + { + Route::get('/', function () { + return new PostCollectionResource(collect([new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])])); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + [ + 'id' => 5, + 'title' => 'Test Title', + ], + ], + ]); + } + + public function test_paginators_receive_links() + { + Route::get('/', function () { + $paginator = new LengthAwarePaginator( + collect([new Post(['id' => 5, 'title' => 'Test Title'])]), + 10, 15, 1 + ); + + return new PostCollectionResource($paginator); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + [ + 'id' => 5, + 'title' => 'Test Title', + ], + ], + 'links' => [ + 'first' => '/?page=1', + 'last' => '/?page=1', + 'prev' => null, + 'next' => null, + ], + 'meta' => [ + 'current_page' => 1, + 'from' => 1, + 'last_page' => 1, + 'path' => '/', + 'per_page' => 15, + 'to' => 1, + 'total' => 10, + ], + ]); + } +} + +class Post extends Model +{ + /** + * The attributes that aren't mass assignable. + * + * @var array + */ + protected $guarded = []; +} + + +class PostResource extends Resource +{ + public function toHtml($request) + { + return 'html'; + } + + public function toCss($request) + { + return 'css'; + } + + public function toJson($request) + { + return ['id' => $this->id, 'title' => $this->title]; + } + + public function withJsonResponse($request, $response) + { + $response->header('X-Resource', 'True'); + } +} + + +class SerializablePostResource extends Resource +{ + public function toJson($request) + { + return new JsonSerializableResource($this); + } +} + + +class PostCollectionResource extends Resource +{ + public function toJson($request) + { + return ['data' => $this->mapInto(PostResource::class)]; + } +} + + +class JsonSerializableResource implements JsonSerializable +{ + public $resource; + + public function __construct($resource) + { + $this->resource = $resource; + } + + public function jsonSerialize() + { + return $this->resource->toArray(); + } +} From 178473c93f9b2d8581cc0d181010f382b4dfd9e2 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 17:11:56 -0500 Subject: [PATCH 05/45] work on tests --- .../Contracts/Database/CastsToResource.php | 6 +- .../Database/Eloquent/DetectsResource.php | 4 +- .../Http/Middleware/CastToResource.php | 2 +- tests/Integration/Http/ResourceTest.php | 158 +++++++++++++++++- 4 files changed, 164 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Contracts/Database/CastsToResource.php b/src/Illuminate/Contracts/Database/CastsToResource.php index 49eac6be3cee..86393f627272 100644 --- a/src/Illuminate/Contracts/Database/CastsToResource.php +++ b/src/Illuminate/Contracts/Database/CastsToResource.php @@ -9,14 +9,16 @@ interface CastsToResource * * @param \Illuminate\Http\Request $request * @param mixed $model + * @return mixed */ public static function castToResource($request, $model); /** - * Cast the given collection into a resource. + * Cast the given paginator or collection into a resource. * * @param \Illuminate\Http\Request $request - * @param \Illuminate\Support\Collection $collection + * @param mixed $collection + * @return mixed */ public static function castCollectionToResource($request, $collection); } diff --git a/src/Illuminate/Database/Eloquent/DetectsResource.php b/src/Illuminate/Database/Eloquent/DetectsResource.php index 03d8f0c91490..44547078e95e 100644 --- a/src/Illuminate/Database/Eloquent/DetectsResource.php +++ b/src/Illuminate/Database/Eloquent/DetectsResource.php @@ -11,6 +11,7 @@ trait DetectsResource * * @param \Illuminate\Http\Request $request * @param mixed $model + * @return mixed */ public static function castToResource($request, $model) { @@ -20,10 +21,11 @@ public static function castToResource($request, $model) } /** - * Cast the given collection into a resource. + * Cast the given paginator or collection into a resource. * * @param \Illuminate\Http\Request $request * @param \Illuminate\Support\Collection $collection + * @return mixed */ public static function castCollectionToResource($request, $collection) { diff --git a/src/Illuminate/Http/Middleware/CastToResource.php b/src/Illuminate/Http/Middleware/CastToResource.php index cf187d4c7138..30a5145e0459 100644 --- a/src/Illuminate/Http/Middleware/CastToResource.php +++ b/src/Illuminate/Http/Middleware/CastToResource.php @@ -83,7 +83,7 @@ protected function castPaginatorToResource($request, $response) $collection = $response->original->getCollection(); return $collection->first()->castCollectionToResource( - $request, $collection + $request, $response->original )->toResponse($request); } } diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index bfc59a4519d9..026c24af98cd 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -11,8 +11,10 @@ use Illuminate\Support\Facades\Schema; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Http\Middleware\CastToResource; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Relations\Pivot; +use Illuminate\Contracts\Database\CastsToResource; /** * @group integration @@ -42,6 +44,56 @@ public function test_resources_may_be_converted_to_json() ]); } + public function test_models_may_be_cast_by_middleware() + { + Route::get('/', function () { + return new Post([ + 'id' => 5, + 'title' => 'Test Title', + ]); + })->middleware(CastToResource::class); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + 'id' => 5, + 'title' => 'Test Title', + 'custom' => true, + ] + ]); + } + + public function test_collections_are_cast_by_middleware() + { + Route::get('/', function () { + return collect([new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])]); + })->middleware(CastToResource::class); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + [ + 'id' => 5, + 'title' => 'Test Title', + 'custom' => true, + ], + ], + ]); + } + public function test_resources_may_be_serializable() { Route::get('/', function () { @@ -82,6 +134,45 @@ public function test_resources_may_customize_responses() $response->assertHeader('X-Resource', 'True'); } + public function test_resources_may_customize_extra_data() + { + Route::get('/', function () { + return (new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])))->json()->with(['foo' => 'bar']); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertJson([ + 'data' => [ + 'id' => 5, + 'title' => 'Test Title', + ], + 'foo' => 'bar', + ]); + } + + public function test_custom_headers_may_be_set_on_responses() + { + Route::get('/', function () { + return (new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])))->json()->status(202)->header('X-Custom', 'True'); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(202); + $response->assertHeader('X-Custom', 'True'); + } + public function test_resources_may_be_converted_to_html() { Route::get('/', function () { @@ -204,9 +295,50 @@ public function test_paginators_receive_links() ], ]); } + + public function test_paginators_are_cast_by_middleware() + { + Route::get('/', function () { + return new LengthAwarePaginator( + collect([new Post(['id' => 5, 'title' => 'Test Title'])]), + 10, 15, 1 + ); + })->middleware(CastToResource::class); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + [ + 'id' => 5, + 'title' => 'Test Title', + 'custom' => true, + ], + ], + 'links' => [ + 'first' => '/?page=1', + 'last' => '/?page=1', + 'prev' => null, + 'next' => null, + ], + 'meta' => [ + 'current_page' => 1, + 'from' => 1, + 'last_page' => 1, + 'path' => '/', + 'per_page' => 15, + 'to' => 1, + 'total' => 10, + ], + ]); + } } -class Post extends Model +class Post extends Model implements CastsToResource { /** * The attributes that aren't mass assignable. @@ -214,6 +346,28 @@ class Post extends Model * @var array */ protected $guarded = []; + + /** + * Cast the given model into a resource. + * + * @param \Illuminate\Http\Request $request + * @param mixed $model + */ + public static function castToResource($request, $model) + { + return new PostResource($model); + } + + /** + * Cast the given collection into a resource. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Support\Collection $collection + */ + public static function castCollectionToResource($request, $collection) + { + return new PostCollectionResource($collection); + } } @@ -231,7 +385,7 @@ public function toCss($request) public function toJson($request) { - return ['id' => $this->id, 'title' => $this->title]; + return ['id' => $this->id, 'title' => $this->title, 'custom' => true]; } public function withJsonResponse($request, $response) From 51b171c540cae727261f4bc262df98124937d66a Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 17:15:26 -0500 Subject: [PATCH 06/45] fix test --- tests/Integration/Http/ResourceTest.php | 4 ---- tests/Pagination/PaginatorTest.php | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index 026c24af98cd..185304befb40 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -4,16 +4,12 @@ use JsonSerializable; use Illuminate\Http\Resource; -use Illuminate\Support\Carbon; use Orchestra\Testbench\TestCase; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Route; -use Illuminate\Support\Facades\Schema; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Collection; use Illuminate\Http\Middleware\CastToResource; use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Contracts\Database\CastsToResource; /** diff --git a/tests/Pagination/PaginatorTest.php b/tests/Pagination/PaginatorTest.php index 73eadeac83d6..d563f97422c7 100644 --- a/tests/Pagination/PaginatorTest.php +++ b/tests/Pagination/PaginatorTest.php @@ -17,15 +17,16 @@ public function testSimplePaginatorReturnsRelevantContextInformation() $this->assertEquals(['item3', 'item4'], $p->items()); $pageInfo = [ - 'per_page' => 2, - 'current_page' => 2, - 'next_page_url' => '/?page=3', - 'prev_page_url' => '/?page=1', - 'from' => 3, - 'to' => 4, - 'data' => ['item3', 'item4'], - 'path' => '/', - ]; + 'per_page' => 2, + 'current_page' => 2, + 'first_page_url' => '/?page=1', + 'next_page_url' => '/?page=3', + 'prev_page_url' => '/?page=1', + 'from' => 3, + 'to' => 4, + 'data' => ['item3', 'item4'], + 'path' => '/', + ]; $this->assertEquals($pageInfo, $p->toArray()); } From 8e3d9fe9f24afe1ae5dc4ad971619671c9342eba Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 17:38:30 -0500 Subject: [PATCH 07/45] more tests --- tests/Integration/Http/ResourceTest.php | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index 185304befb40..cc37f6db3469 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -40,6 +40,34 @@ public function test_resources_may_be_converted_to_json() ]); } + public function test_resource_is_url_routable() + { + $post = new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])); + + $this->assertEquals('http://localhost/post/5', url('/post', $post)); + } + + public function test_named_routes_are_url_routable() + { + $post = new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])); + + $route = Route::get('/post/{id}', function () use ($post) { + return route('post.show', $post); + })->name('post.show'); + + $this->app['router']->getRoutes()->refreshNameLookups(); + + $response = $this->withoutExceptionHandling()->get('/post/1'); + + $this->assertEquals('http://localhost/post/5', $response->original); + } + public function test_models_may_be_cast_by_middleware() { Route::get('/', function () { From 1ef2b4c71e2d0455442b5848c0f82a89b2b75131 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 17:50:25 -0500 Subject: [PATCH 08/45] fix macro call --- src/Illuminate/Http/Resource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php index 4374b16ae58a..d5bba27029ed 100644 --- a/src/Illuminate/Http/Resource.php +++ b/src/Illuminate/Http/Resource.php @@ -67,7 +67,7 @@ public function __construct($resource) public function response($request) { if (static::hasMacro($format = $request->format())) { - return call_user_func_array(static::$macros[$format], $request); + return call_user_func(static::$macros[$format]); } if ($request->expectsJson()) { From e81c47a05fa9c30741963f9898cd4c17ac35454f Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 18:07:32 -0500 Subject: [PATCH 09/45] no macroable setup for resource --- src/Illuminate/Http/Resource.php | 31 +++++-- tests/Integration/Http/ResourceTest.php | 108 ++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 7 deletions(-) diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php index d5bba27029ed..daa31d5002cd 100644 --- a/src/Illuminate/Http/Resource.php +++ b/src/Illuminate/Http/Resource.php @@ -10,15 +10,12 @@ use Illuminate\Support\Str; use Illuminate\Support\Collection; use Illuminate\Container\Container; -use Illuminate\Support\Traits\Macroable; use Illuminate\Pagination\AbstractPaginator; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; class Resource implements ArrayAccess, IteratorAggregate, JsonSerializable, Responsable, UrlRoutable { - use Macroable; - /** * The resource instance. * @@ -47,6 +44,13 @@ class Resource implements ArrayAccess, IteratorAggregate, JsonSerializable, Resp */ public static $wrap = 'data'; + /** + * The custom format extensions. + * + * @var array + */ + public static $extensions = []; + /** * Create a new resource instance. * @@ -66,8 +70,8 @@ public function __construct($resource) */ public function response($request) { - if (static::hasMacro($format = $request->format())) { - return call_user_func(static::$macros[$format]); + if (isset(static::$extensions[$format = $request->format()])) { + return call_user_func(static::$extensions[$format], $this); } if ($request->expectsJson()) { @@ -138,8 +142,9 @@ public function toJson($request) */ protected function isCollectionResource() { - return $this->resource instanceof Collection && - Str::endsWith(get_class($this), 'Collection'); + return ($this->resource instanceof Collection || + $this->resource instanceof AbstractPaginator) && + Str::contains(get_class($this), 'Collection'); } /** @@ -302,6 +307,18 @@ public function toResponse($request) return $this->response($request)->toResponse($request); } + /** + * Extend the resource with a new format. + * + * @param string $format + * @param \Closure $callback + * @return void + */ + public static function extend($format, $callback) + { + static::$extensions[$format] = $callback; + } + /** * Get an iterator for the resource. * diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index cc37f6db3469..92ebfa39b07d 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Route; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\Middleware\CastToResource; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Database\CastsToResource; @@ -360,6 +361,99 @@ public function test_paginators_are_cast_by_middleware() ], ]); } + + public function test_to_json_may_be_left_off_of_collection() + { + Route::get('/', function () { + return new EmptyPostCollectionResource(new LengthAwarePaginator( + collect([new Post(['id' => 5, 'title' => 'Test Title'])]), + 10, 15, 1 + )); + })->middleware(CastToResource::class); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + [ + 'id' => 5, + 'title' => 'Test Title', + 'custom' => true, + ], + ], + 'links' => [ + 'first' => '/?page=1', + 'last' => '/?page=1', + 'prev' => null, + 'next' => null, + ], + 'meta' => [ + 'current_page' => 1, + 'from' => 1, + 'last_page' => 1, + 'path' => '/', + 'per_page' => 15, + 'to' => 1, + 'total' => 10, + ], + ]); + } + + public function test_to_json_may_be_left_off_of_single_resource() + { + Route::get('/', function () { + return new ReallyEmptyPostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertStatus(200); + + $response->assertJson([ + 'data' => [ + 'id' => 5, + 'title' => 'Test Title', + ] + ]); + } + + public function test_resource_types_may_be_macroed() + { + Resource::extend('xml', function ($resource) { + $this->assertInstanceOf(PostResource::class, $resource); + + return new class implements Responsable { + public function toResponse($request) + { + return 'xml response'; + } + }; + }); + + Route::get('/', function () { + return new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/xml'] + ); + + $response->assertStatus(200); + + $this->assertEquals('xml response', $response->original); + } } class Post extends Model implements CastsToResource @@ -436,6 +530,20 @@ public function toJson($request) } } +class EmptyPostCollectionResource extends Resource +{ + // +} + +class EmptyPostResource extends PostResource +{ + // +} + +class ReallyEmptyPostResource extends Resource +{ + // +} class JsonSerializableResource implements JsonSerializable { From 2c20b84eb73cad2cc1df13241b7afc5604cb58e5 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 21 Aug 2017 18:08:41 -0500 Subject: [PATCH 10/45] wip --- src/Illuminate/Http/Resource.php | 2 +- tests/Integration/Http/ResourceTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php index daa31d5002cd..a42a8b5524ee 100644 --- a/src/Illuminate/Http/Resource.php +++ b/src/Illuminate/Http/Resource.php @@ -314,7 +314,7 @@ public function toResponse($request) * @param \Closure $callback * @return void */ - public static function extend($format, $callback) + public static function format($format, $callback) { static::$extensions[$format] = $callback; } diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index 92ebfa39b07d..701b9354b54d 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -428,7 +428,7 @@ public function test_to_json_may_be_left_off_of_single_resource() public function test_resource_types_may_be_macroed() { - Resource::extend('xml', function ($resource) { + Resource::format('xml', function ($resource) { $this->assertInstanceOf(PostResource::class, $resource); return new class implements Responsable { From 3014d931dea16b907afcdca1eeb040de28c4243d Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 07:18:48 -0500 Subject: [PATCH 11/45] no longer needed after orchestra fix --- tests/Integration/Http/ResourceTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index 701b9354b54d..3c7b83fb4e36 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -62,8 +62,6 @@ public function test_named_routes_are_url_routable() return route('post.show', $post); })->name('post.show'); - $this->app['router']->getRoutes()->refreshNameLookups(); - $response = $this->withoutExceptionHandling()->get('/post/1'); $this->assertEquals('http://localhost/post/5', $response->original); From 18b2039484106f7fe2def264db93d21084586f87 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 08:31:31 -0500 Subject: [PATCH 12/45] auto pass resource if not set --- .../Http/Resources/HtmlResourceResponse.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Resources/HtmlResourceResponse.php b/src/Illuminate/Http/Resources/HtmlResourceResponse.php index e7c9cb99de64..b09916b8100d 100644 --- a/src/Illuminate/Http/Resources/HtmlResourceResponse.php +++ b/src/Illuminate/Http/Resources/HtmlResourceResponse.php @@ -2,6 +2,8 @@ namespace Illuminate\Http\Resources; +use Illuminate\Contracts\View\View; + class HtmlResourceResponse extends ResourceResponse { /** @@ -12,9 +14,14 @@ class HtmlResourceResponse extends ResourceResponse */ public function toResponse($request) { + $view = $this->resource->toHtml($request); + + if ($view instanceof View && ! isset($view->resource)) { + $view->with('resource', $this->resource); + } + return $this->build($request, response( - $this->resource->toHtml($request), - $this->calculateStatus(), $this->headers + $view, $this->calculateStatus(), $this->headers )); } From a604b6b47b0e740bfa14ed79115f52ae4b592744 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 08:58:53 -0500 Subject: [PATCH 13/45] working on resourceS --- src/Illuminate/Http/Resource.php | 62 +++++++++++++++++-------- tests/Integration/Http/ResourceTest.php | 6 ++- 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php index a42a8b5524ee..b20c7f76a14e 100644 --- a/src/Illuminate/Http/Resource.php +++ b/src/Illuminate/Http/Resource.php @@ -37,6 +37,20 @@ class Resource implements ArrayAccess, IteratorAggregate, JsonSerializable, Resp */ public $visible = []; + /** + * The resource that this resource collects. + * + * @var string + */ + public $collects; + + /** + * The mapped collection instance. + * + * @var \Illuminate\Support\Collection + */ + public $collection; + /** * The "data" wrapper that should be applied. * @@ -60,6 +74,29 @@ class Resource implements ArrayAccess, IteratorAggregate, JsonSerializable, Resp public function __construct($resource) { $this->resource = $resource; + + if ($this->isCollectionResource()) { + $this->resource = $this->collectResource($resource); + } + } + + /** + * Map the given collection resource into its individual resources. + * + * @param mixed $resource + * @return mixed + */ + protected function collectResource($resource) + { + if (! $this->collects) { + throw new Exception('The ['.get_class($this).'] resource must specify the models it collects.'); + } + + $this->collection = $resource->mapInto($this->collects); + + return $resource instanceof Collection + ? $this->collection + : $resource->setCollection($this->collection); } /** @@ -130,7 +167,7 @@ public function json() */ public function toJson($request) { - return $this->isCollectionResource() && $this->singularResource() + return $this->isCollectionResource() ? $this->collectionToJson($request) : $this->resourceToJson($request); } @@ -142,9 +179,8 @@ public function toJson($request) */ protected function isCollectionResource() { - return ($this->resource instanceof Collection || - $this->resource instanceof AbstractPaginator) && - Str::contains(get_class($this), 'Collection'); + return $this->resource instanceof Collection || + $this->resource instanceof AbstractPaginator; } /** @@ -155,25 +191,13 @@ protected function isCollectionResource() */ public function collectionToJson($request) { - $data = $this->mapInto( - $this->singularResource() - )->map->toJson($request)->all(); + $data = $this->resource->map(function ($item) use ($request) { + return $item->toJson($request); + })->all(); return static::$wrap ? [static::$wrap => $data] : $data; } - /** - * Get the singular version of this resource class, if applicable. - * - * @return string|null - */ - protected function singularResource() - { - if (class_exists($class = Str::replaceLast('Collection', '', get_class($this)))) { - return $class; - } - } - /** * Convert the resource into a JSON array. * diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index 3c7b83fb4e36..6118e76fa34f 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -522,15 +522,17 @@ public function toJson($request) class PostCollectionResource extends Resource { + public $collects = PostResource::class; + public function toJson($request) { - return ['data' => $this->mapInto(PostResource::class)]; + return ['data' => $this->collection]; } } class EmptyPostCollectionResource extends Resource { - // + public $collects = PostResource::class; } class EmptyPostResource extends PostResource From 5f10a52a9e5a273a5e22f4fab981f23dfd487bc6 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 09:09:21 -0500 Subject: [PATCH 14/45] extract resource collection --- src/Illuminate/Http/Resource.php | 58 +-------------- src/Illuminate/Http/ResourceCollection.php | 83 ++++++++++++++++++++++ tests/Integration/Http/ResourceTest.php | 5 +- 3 files changed, 88 insertions(+), 58 deletions(-) create mode 100644 src/Illuminate/Http/ResourceCollection.php diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php index b20c7f76a14e..4f9d8bc3fdec 100644 --- a/src/Illuminate/Http/Resource.php +++ b/src/Illuminate/Http/Resource.php @@ -7,7 +7,6 @@ use ArrayIterator; use JsonSerializable; use IteratorAggregate; -use Illuminate\Support\Str; use Illuminate\Support\Collection; use Illuminate\Container\Container; use Illuminate\Pagination\AbstractPaginator; @@ -74,29 +73,6 @@ class Resource implements ArrayAccess, IteratorAggregate, JsonSerializable, Resp public function __construct($resource) { $this->resource = $resource; - - if ($this->isCollectionResource()) { - $this->resource = $this->collectResource($resource); - } - } - - /** - * Map the given collection resource into its individual resources. - * - * @param mixed $resource - * @return mixed - */ - protected function collectResource($resource) - { - if (! $this->collects) { - throw new Exception('The ['.get_class($this).'] resource must specify the models it collects.'); - } - - $this->collection = $resource->mapInto($this->collects); - - return $resource instanceof Collection - ? $this->collection - : $resource->setCollection($this->collection); } /** @@ -154,9 +130,7 @@ public function css() */ public function json() { - return $this->resource instanceof AbstractPaginator - ? new Resources\PaginatedJsonResourceResponse($this) - : new Resources\JsonResourceResponse($this); + return new Resources\JsonResourceResponse($this); } /** @@ -167,35 +141,7 @@ public function json() */ public function toJson($request) { - return $this->isCollectionResource() - ? $this->collectionToJson($request) - : $this->resourceToJson($request); - } - - /** - * Determine if this resource is a collection resource. - * - * @return bool - */ - protected function isCollectionResource() - { - return $this->resource instanceof Collection || - $this->resource instanceof AbstractPaginator; - } - - /** - * Convert the collection into a JSON array. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - public function collectionToJson($request) - { - $data = $this->resource->map(function ($item) use ($request) { - return $item->toJson($request); - })->all(); - - return static::$wrap ? [static::$wrap => $data] : $data; + return $this->resourceToJson($request); } /** diff --git a/src/Illuminate/Http/ResourceCollection.php b/src/Illuminate/Http/ResourceCollection.php new file mode 100644 index 000000000000..3986be387680 --- /dev/null +++ b/src/Illuminate/Http/ResourceCollection.php @@ -0,0 +1,83 @@ +resource = $this->collectResource($resource); + } + + /** + * Map the given collection resource into its individual resources. + * + * @param mixed $resource + * @return mixed + */ + protected function collectResource($resource) + { + if (! $this->collects) { + throw new Exception('The ['.get_class($this).'] resource must specify the models it collects.'); + } + + $this->collection = $resource->mapInto($this->collects); + + return $resource instanceof Collection + ? $this->collection + : $resource->setCollection($this->collection); + } + + /** + * Create a new JSON resource response for the given resource. + * + * @return \App\ResourceResponse + */ + public function json() + { + return $this->resource instanceof AbstractPaginator + ? new Resources\PaginatedJsonResourceResponse($this) + : parent::json(); + } + + /** + * Transform the resource into a JSON array. + * + * @param \Illuminate\Http\Request + * @return array + */ + public function toJson($request) + { + $data = $this->resource->map(function ($item) use ($request) { + return $item->toJson($request); + })->all(); + + return static::$wrap ? [static::$wrap => $data] : $data; + } +} diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index 6118e76fa34f..e56640cfba77 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -6,6 +6,7 @@ use Illuminate\Http\Resource; use Orchestra\Testbench\TestCase; use Illuminate\Support\Facades\Route; +use Illuminate\Http\ResourceCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Collection; use Illuminate\Contracts\Support\Responsable; @@ -520,7 +521,7 @@ public function toJson($request) } -class PostCollectionResource extends Resource +class PostCollectionResource extends ResourceCollection { public $collects = PostResource::class; @@ -530,7 +531,7 @@ public function toJson($request) } } -class EmptyPostCollectionResource extends Resource +class EmptyPostCollectionResource extends ResourceCollection { public $collects = PostResource::class; } From 12262fbcd74c88da20524e35a1a2bcba6ee79c01 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 09:16:09 -0500 Subject: [PATCH 15/45] extract method --- src/Illuminate/Http/ResourceCollection.php | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Http/ResourceCollection.php b/src/Illuminate/Http/ResourceCollection.php index 3986be387680..b081b31630ce 100644 --- a/src/Illuminate/Http/ResourceCollection.php +++ b/src/Illuminate/Http/ResourceCollection.php @@ -43,17 +43,29 @@ public function __construct($resource) */ protected function collectResource($resource) { - if (! $this->collects) { - throw new Exception('The ['.get_class($this).'] resource must specify the models it collects.'); - } - - $this->collection = $resource->mapInto($this->collects); + $this->collection = $resource->mapInto($this->collects()); return $resource instanceof Collection ? $this->collection : $resource->setCollection($this->collection); } + /** + * Get the resource that this resource collects. + * + * @return string + */ + protected function collects() + { + if ($this->collects) { + return $this->collects; + } + + throw new Exception( + 'The ['.get_class($this).'] resource must specify the models it collects.' + ); + } + /** * Create a new JSON resource response for the given resource. * From b8bdce1a456ffc276624188968d17ff1189b7bfe Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 09:16:47 -0500 Subject: [PATCH 16/45] reverse check --- src/Illuminate/Http/ResourceCollection.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Http/ResourceCollection.php b/src/Illuminate/Http/ResourceCollection.php index b081b31630ce..4369a3c913af 100644 --- a/src/Illuminate/Http/ResourceCollection.php +++ b/src/Illuminate/Http/ResourceCollection.php @@ -45,9 +45,9 @@ protected function collectResource($resource) { $this->collection = $resource->mapInto($this->collects()); - return $resource instanceof Collection - ? $this->collection - : $resource->setCollection($this->collection); + return $resource instanceof AbstractPaginator + ? $resource->setCollection($this->collection) + : $this->collection; } /** From cc06b533a3d29d1df930e007d826e3338985ca13 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 09:24:50 -0500 Subject: [PATCH 17/45] try to guess collect object --- src/Illuminate/Http/ResourceCollection.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Illuminate/Http/ResourceCollection.php b/src/Illuminate/Http/ResourceCollection.php index 4369a3c913af..c61cc0ca3bac 100644 --- a/src/Illuminate/Http/ResourceCollection.php +++ b/src/Illuminate/Http/ResourceCollection.php @@ -3,6 +3,7 @@ namespace Illuminate\Http; use Exception; +use Illuminate\Support\Str; use Illuminate\Support\Collection; use Illuminate\Pagination\AbstractPaginator; @@ -61,6 +62,11 @@ protected function collects() return $this->collects; } + if (Str::endsWith(class_basename($this), 'Collection') && + class_exists($class = Str::replaceLast('Collection', '', get_class($this)))) { + return $class; + } + throw new Exception( 'The ['.get_class($this).'] resource must specify the models it collects.' ); From a0b97c5162d6fe03897fa771a38a776425ab781e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 10:01:02 -0500 Subject: [PATCH 18/45] cli commands --- .../Console/ResourceMakeCommand.php | 86 +++++++++++++++++++ .../Console/stubs/resource-collection.stub | 19 ++++ .../Foundation/Console/stubs/resource.stub | 26 ++++++ .../Providers/ArtisanServiceProvider.php | 14 +++ src/Illuminate/Http/ResourceCollection.php | 4 +- 5 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 src/Illuminate/Foundation/Console/ResourceMakeCommand.php create mode 100644 src/Illuminate/Foundation/Console/stubs/resource-collection.stub create mode 100644 src/Illuminate/Foundation/Console/stubs/resource.stub diff --git a/src/Illuminate/Foundation/Console/ResourceMakeCommand.php b/src/Illuminate/Foundation/Console/ResourceMakeCommand.php new file mode 100644 index 000000000000..22c4ebc7d186 --- /dev/null +++ b/src/Illuminate/Foundation/Console/ResourceMakeCommand.php @@ -0,0 +1,86 @@ +option('collection')) { + $this->type = 'Resource collection'; + } + + parent::handle(); + + if (! $this->option('collection')) { + $this->call('make:resource', [ + 'name' => $this->argument('name').'Collection', + '--collection' => true, + ]); + } + } + + /** + * Get the stub file for the generator. + * + * @return string + */ + protected function getStub() + { + return $this->option('collection') + ? __DIR__.'/stubs/resource-collection.stub' + : __DIR__.'/stubs/resource.stub'; + } + + /** + * Get the default namespace for the class. + * + * @param string $rootNamespace + * @return string + */ + protected function getDefaultNamespace($rootNamespace) + { + return $rootNamespace.'\Http\Resources'; + } + + /** + * Get the console command options. + * + * @return array + */ + protected function getOptions() + { + return [ + ['collection', 'c', InputOption::VALUE_NONE, 'Create a resource collection.'], + ]; + } +} diff --git a/src/Illuminate/Foundation/Console/stubs/resource-collection.stub b/src/Illuminate/Foundation/Console/stubs/resource-collection.stub new file mode 100644 index 000000000000..7365bfc767dd --- /dev/null +++ b/src/Illuminate/Foundation/Console/stubs/resource-collection.stub @@ -0,0 +1,19 @@ + 'command.queue.failed-table', 'QueueTable' => 'command.queue.table', 'RequestMake' => 'command.request.make', + 'ResourceMake' => 'command.resource.make', 'RuleMake' => 'command.rule.make', 'SeederMake' => 'command.seeder.make', 'SessionTable' => 'command.session.table', @@ -727,6 +729,18 @@ protected function registerRequestMakeCommand() }); } + /** + * Register the command. + * + * @return void + */ + protected function registerResourceMakeCommand() + { + $this->app->singleton('command.resource.make', function ($app) { + return new ResourceMakeCommand($app['files']); + }); + } + /** * Register the command. * diff --git a/src/Illuminate/Http/ResourceCollection.php b/src/Illuminate/Http/ResourceCollection.php index c61cc0ca3bac..1999d2b62b2b 100644 --- a/src/Illuminate/Http/ResourceCollection.php +++ b/src/Illuminate/Http/ResourceCollection.php @@ -92,10 +92,10 @@ public function json() */ public function toJson($request) { - $data = $this->resource->map(function ($item) use ($request) { + return $this->resource->map(function ($item) use ($request) { return $item->toJson($request); })->all(); - return static::$wrap ? [static::$wrap => $data] : $data; + // return static::$wrap ? [static::$wrap => $data] : $data; } } From acd5a813775dd8d7bcc64c7e364ddb56db2432ce Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 10:12:06 -0500 Subject: [PATCH 19/45] tweak iterator implementation --- src/Illuminate/Http/Resource.php | 23 +--------------------- src/Illuminate/Http/ResourceCollection.php | 19 ++++++++++++++++-- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php index 4f9d8bc3fdec..2e7df23d3097 100644 --- a/src/Illuminate/Http/Resource.php +++ b/src/Illuminate/Http/Resource.php @@ -4,16 +4,13 @@ use Exception; use ArrayAccess; -use ArrayIterator; use JsonSerializable; -use IteratorAggregate; use Illuminate\Support\Collection; use Illuminate\Container\Container; -use Illuminate\Pagination\AbstractPaginator; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; -class Resource implements ArrayAccess, IteratorAggregate, JsonSerializable, Responsable, UrlRoutable +class Resource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable { /** * The resource instance. @@ -289,24 +286,6 @@ public static function format($format, $callback) static::$extensions[$format] = $callback; } - /** - * Get an iterator for the resource. - * - * @return \ArrayIterator - */ - public function getIterator() - { - if (is_array($this->resource)) { - return new ArrayIterator($this->resource); - } elseif ($this->resource instanceof IteratorAggregate) { - return $this->resource->getIterator(); - } - - throw new Exception( - "Unable to generate an iterator for this resource." - ); - } - /** * Prepare the resource for JSON serialization. * diff --git a/src/Illuminate/Http/ResourceCollection.php b/src/Illuminate/Http/ResourceCollection.php index 1999d2b62b2b..bf17f0e63b63 100644 --- a/src/Illuminate/Http/ResourceCollection.php +++ b/src/Illuminate/Http/ResourceCollection.php @@ -3,11 +3,12 @@ namespace Illuminate\Http; use Exception; +use IteratorAggregate; use Illuminate\Support\Str; use Illuminate\Support\Collection; use Illuminate\Pagination\AbstractPaginator; -class ResourceCollection extends Resource +class ResourceCollection extends Resource implements IteratorAggregate { /** * The resource that this resource collects. @@ -95,7 +96,21 @@ public function toJson($request) return $this->resource->map(function ($item) use ($request) { return $item->toJson($request); })->all(); + } - // return static::$wrap ? [static::$wrap => $data] : $data; + /** + * Get an iterator for the resource collection. + * + * @return \ArrayIterator + */ + public function getIterator() + { + if ($this->collection instanceof IteratorAggregate) { + return $this->collection->getIterator(); + } + + throw new Exception( + "Unable to generate an iterator for this resource collection." + ); } } From c793726e4214b273bc8109ef46294e0b2e644747 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 10:24:49 -0500 Subject: [PATCH 20/45] 404 for not found resource types --- src/Illuminate/Http/Resources/CssResourceResponse.php | 6 ++++++ src/Illuminate/Http/Resources/HtmlResourceResponse.php | 5 +++++ src/Illuminate/Http/Resources/JsonResourceResponse.php | 5 +++++ .../Http/Resources/PaginatedJsonResourceResponse.php | 5 +++++ 4 files changed, 21 insertions(+) diff --git a/src/Illuminate/Http/Resources/CssResourceResponse.php b/src/Illuminate/Http/Resources/CssResourceResponse.php index 135ffe54f878..7bb0d3f9c5e7 100644 --- a/src/Illuminate/Http/Resources/CssResourceResponse.php +++ b/src/Illuminate/Http/Resources/CssResourceResponse.php @@ -2,6 +2,8 @@ namespace Illuminate\Http\Resources; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; + class CssResourceResponse extends ResourceResponse { /** @@ -12,6 +14,10 @@ class CssResourceResponse extends ResourceResponse */ public function toResponse($request) { + if (! method_exists($this->resource, 'toCss')) { + throw NotFoundHttpException; + } + return $this->build($request, response( $this->resource->toCss($request), 200, ['Content-Type' => 'text/css'] diff --git a/src/Illuminate/Http/Resources/HtmlResourceResponse.php b/src/Illuminate/Http/Resources/HtmlResourceResponse.php index b09916b8100d..d9ddf0946487 100644 --- a/src/Illuminate/Http/Resources/HtmlResourceResponse.php +++ b/src/Illuminate/Http/Resources/HtmlResourceResponse.php @@ -3,6 +3,7 @@ namespace Illuminate\Http\Resources; use Illuminate\Contracts\View\View; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class HtmlResourceResponse extends ResourceResponse { @@ -14,6 +15,10 @@ class HtmlResourceResponse extends ResourceResponse */ public function toResponse($request) { + if (! method_exists($this->resource, 'toHtml')) { + throw new NotFoundHttpException; + } + $view = $this->resource->toHtml($request); if ($view instanceof View && ! isset($view->resource)) { diff --git a/src/Illuminate/Http/Resources/JsonResourceResponse.php b/src/Illuminate/Http/Resources/JsonResourceResponse.php index 4aa401d3a71f..4acf0d370d82 100644 --- a/src/Illuminate/Http/Resources/JsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/JsonResourceResponse.php @@ -3,6 +3,7 @@ namespace Illuminate\Http\Resources; use Illuminate\Support\Collection; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class JsonResourceResponse extends ResourceResponse { @@ -21,6 +22,10 @@ class JsonResourceResponse extends ResourceResponse */ public function toResponse($request) { + if (! method_exists($this->resource, 'toJson')) { + throw new NotFoundHttpException; + } + return $this->build($request, response()->json( array_merge($this->wrap($this->resource->toJson($request)), $this->with), $this->calculateStatus(), $this->headers diff --git a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php index 8d1c17a4843d..511cc8d6a661 100644 --- a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php @@ -3,6 +3,7 @@ namespace Illuminate\Http\Resources; use Illuminate\Support\Arr; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class PaginatedJsonResourceResponse extends JsonResourceResponse { @@ -14,6 +15,10 @@ class PaginatedJsonResourceResponse extends JsonResourceResponse */ public function toResponse($request) { + if (! method_exists($this->resource, 'toJson')) { + throw new NotFoundHttpException; + } + $this->addPaginationInformation($request); return $this->build($request, response()->json( From 8cf4fef4ad0516a4c2e1c721e023031ad325fbb3 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 10:25:10 -0500 Subject: [PATCH 21/45] fix --- src/Illuminate/Http/Resources/CssResourceResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Resources/CssResourceResponse.php b/src/Illuminate/Http/Resources/CssResourceResponse.php index 7bb0d3f9c5e7..25dc79f1f7d1 100644 --- a/src/Illuminate/Http/Resources/CssResourceResponse.php +++ b/src/Illuminate/Http/Resources/CssResourceResponse.php @@ -15,7 +15,7 @@ class CssResourceResponse extends ResourceResponse public function toResponse($request) { if (! method_exists($this->resource, 'toCss')) { - throw NotFoundHttpException; + throw new NotFoundHttpException; } return $this->build($request, response( From 5771ee71ffb96d3919afafbb87d9cb9b8a07548c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 10:28:25 -0500 Subject: [PATCH 22/45] use 406 status code --- src/Illuminate/Http/Resources/CssResourceResponse.php | 4 ++-- src/Illuminate/Http/Resources/HtmlResourceResponse.php | 4 ++-- src/Illuminate/Http/Resources/JsonResourceResponse.php | 4 ++-- .../Http/Resources/PaginatedJsonResourceResponse.php | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Http/Resources/CssResourceResponse.php b/src/Illuminate/Http/Resources/CssResourceResponse.php index 25dc79f1f7d1..7f7ed526da47 100644 --- a/src/Illuminate/Http/Resources/CssResourceResponse.php +++ b/src/Illuminate/Http/Resources/CssResourceResponse.php @@ -2,7 +2,7 @@ namespace Illuminate\Http\Resources; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; class CssResourceResponse extends ResourceResponse { @@ -15,7 +15,7 @@ class CssResourceResponse extends ResourceResponse public function toResponse($request) { if (! method_exists($this->resource, 'toCss')) { - throw new NotFoundHttpException; + throw new HttpException(406); } return $this->build($request, response( diff --git a/src/Illuminate/Http/Resources/HtmlResourceResponse.php b/src/Illuminate/Http/Resources/HtmlResourceResponse.php index d9ddf0946487..7a6dc244d8ff 100644 --- a/src/Illuminate/Http/Resources/HtmlResourceResponse.php +++ b/src/Illuminate/Http/Resources/HtmlResourceResponse.php @@ -3,7 +3,7 @@ namespace Illuminate\Http\Resources; use Illuminate\Contracts\View\View; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; class HtmlResourceResponse extends ResourceResponse { @@ -16,7 +16,7 @@ class HtmlResourceResponse extends ResourceResponse public function toResponse($request) { if (! method_exists($this->resource, 'toHtml')) { - throw new NotFoundHttpException; + throw new HttpException(406); } $view = $this->resource->toHtml($request); diff --git a/src/Illuminate/Http/Resources/JsonResourceResponse.php b/src/Illuminate/Http/Resources/JsonResourceResponse.php index 4acf0d370d82..e7a445179665 100644 --- a/src/Illuminate/Http/Resources/JsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/JsonResourceResponse.php @@ -3,7 +3,7 @@ namespace Illuminate\Http\Resources; use Illuminate\Support\Collection; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; class JsonResourceResponse extends ResourceResponse { @@ -23,7 +23,7 @@ class JsonResourceResponse extends ResourceResponse public function toResponse($request) { if (! method_exists($this->resource, 'toJson')) { - throw new NotFoundHttpException; + throw new HttpException(406); } return $this->build($request, response()->json( diff --git a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php index 511cc8d6a661..475934d8ab63 100644 --- a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php @@ -3,7 +3,7 @@ namespace Illuminate\Http\Resources; use Illuminate\Support\Arr; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Symfony\Component\HttpKernel\Exception\HttpException; class PaginatedJsonResourceResponse extends JsonResourceResponse { @@ -16,7 +16,7 @@ class PaginatedJsonResourceResponse extends JsonResourceResponse public function toResponse($request) { if (! method_exists($this->resource, 'toJson')) { - throw new NotFoundHttpException; + throw new HttpException(406); } $this->addPaginationInformation($request); From 26949d7e6cd443121cf3f258ae979e713d3eba3d Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 10:37:41 -0500 Subject: [PATCH 23/45] tweak cli --- .../Console/ResourceMakeCommand.php | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Foundation/Console/ResourceMakeCommand.php b/src/Illuminate/Foundation/Console/ResourceMakeCommand.php index 22c4ebc7d186..fd74a8ec74bf 100644 --- a/src/Illuminate/Foundation/Console/ResourceMakeCommand.php +++ b/src/Illuminate/Foundation/Console/ResourceMakeCommand.php @@ -2,6 +2,7 @@ namespace Illuminate\Foundation\Console; +use Illuminate\Support\Str; use Illuminate\Console\GeneratorCommand; use Symfony\Component\Console\Input\InputOption; @@ -35,18 +36,11 @@ class ResourceMakeCommand extends GeneratorCommand */ public function handle() { - if ($this->option('collection')) { + if ($this->collection()) { $this->type = 'Resource collection'; } parent::handle(); - - if (! $this->option('collection')) { - $this->call('make:resource', [ - 'name' => $this->argument('name').'Collection', - '--collection' => true, - ]); - } } /** @@ -56,11 +50,22 @@ public function handle() */ protected function getStub() { - return $this->option('collection') + return $this->collection() ? __DIR__.'/stubs/resource-collection.stub' : __DIR__.'/stubs/resource.stub'; } + /** + * Determine if the command is generating a resource collection. + * + * @return bool + */ + protected function collection() + { + return $this->option('collection') || + Str::endsWith($this->argument('name'), 'Collection'); + } + /** * Get the default namespace for the class. * From 53e1f4faaa8b6b8a3e5a720972bfb430ca678723 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 10:41:09 -0500 Subject: [PATCH 24/45] allow appending to already generated additions --- src/Illuminate/Http/Resources/JsonResourceResponse.php | 2 +- src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Resources/JsonResourceResponse.php b/src/Illuminate/Http/Resources/JsonResourceResponse.php index e7a445179665..36c4c4e53053 100644 --- a/src/Illuminate/Http/Resources/JsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/JsonResourceResponse.php @@ -27,7 +27,7 @@ public function toResponse($request) } return $this->build($request, response()->json( - array_merge($this->wrap($this->resource->toJson($request)), $this->with), + array_merge_recursive($this->wrap($this->resource->toJson($request)), $this->with), $this->calculateStatus(), $this->headers )); } diff --git a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php index 475934d8ab63..f75c73172099 100644 --- a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php @@ -22,7 +22,7 @@ public function toResponse($request) $this->addPaginationInformation($request); return $this->build($request, response()->json( - array_merge($this->wrap($this->resource->toJson($request)), $this->with), + array_merge_recursive($this->wrap($this->resource->toJson($request)), $this->with), $this->calculateStatus(), $this->headers )); } From 61899496fce296e0b5ce8640a4a6c877ac052c6e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 17:37:47 -0500 Subject: [PATCH 25/45] refactor response handling --- src/Illuminate/Http/Resource.php | 85 ++++++++++++++++++ .../Http/Resources/HtmlResourceResponse.php | 2 +- .../Http/Resources/JsonResourceResponse.php | 2 +- .../PaginatedJsonResourceResponse.php | 2 +- .../Http/Resources/ResourceResponse.php | 90 +------------------ tests/Integration/Http/ResourceTest.php | 20 ++++- 6 files changed, 110 insertions(+), 91 deletions(-) diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php index 2e7df23d3097..3869e6a2635c 100644 --- a/src/Illuminate/Http/Resource.php +++ b/src/Illuminate/Http/Resource.php @@ -9,6 +9,7 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; +use Symfony\Component\HttpFoundation\HeaderBag; class Resource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable { @@ -47,6 +48,27 @@ class Resource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutabl */ public $collection; + /** + * The status code that should be used for the response. + * + * @var int + */ + public $status; + + /** + * The headers that should be added to the response. + * + * @var array + */ + public $headers = []; + + /** + * The callback that should customize the response. + * + * @var \Closure + */ + public $callback; + /** * The "data" wrapper that should be applied. * @@ -70,6 +92,10 @@ class Resource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutabl public function __construct($resource) { $this->resource = $resource; + + $this->callback = function () { + // + }; } /** @@ -296,6 +322,65 @@ public function jsonSerialize() return $this->toJson(Container::getInstance()->make('request')); } + /** + * Set the HTTP status code that should be added to the response. + * + * @param int $status + * @return $this + */ + public function status($status) + { + $this->status = $status; + + return $this; + } + + /** + * Define a header that should be added to the response. + * + * @param string $key + * @param string $value + * @return $this + */ + public function header($key, $value) + { + $this->headers[$key] = $value; + + return $this; + } + + /** + * Add an array of headers that should be added to the response. + * + * @param \Symfony\Component\HttpFoundation\HeaderBag|array $headers + * @return $this + */ + public function withHeaders($headers) + { + if ($headers instanceof HeaderBag) { + $headers = $headers->all(); + } + + foreach ($headers as $key => $value) { + $this->headers[$key] = $value; + } + + return $this; + } + + /** + * Define a custom callback that should customize the response. + * + * @param \Closure $callback + * @return $this + */ + public function using($callback) + { + $this->callback = $callback; + + return $this; + } + /** * Determine if the given attribute exists. * diff --git a/src/Illuminate/Http/Resources/HtmlResourceResponse.php b/src/Illuminate/Http/Resources/HtmlResourceResponse.php index 7a6dc244d8ff..40b5e80e5f44 100644 --- a/src/Illuminate/Http/Resources/HtmlResourceResponse.php +++ b/src/Illuminate/Http/Resources/HtmlResourceResponse.php @@ -26,7 +26,7 @@ public function toResponse($request) } return $this->build($request, response( - $view, $this->calculateStatus(), $this->headers + $view, $this->calculateStatus(), $this->resource->headers )); } diff --git a/src/Illuminate/Http/Resources/JsonResourceResponse.php b/src/Illuminate/Http/Resources/JsonResourceResponse.php index 36c4c4e53053..c52c8c164dfd 100644 --- a/src/Illuminate/Http/Resources/JsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/JsonResourceResponse.php @@ -28,7 +28,7 @@ public function toResponse($request) return $this->build($request, response()->json( array_merge_recursive($this->wrap($this->resource->toJson($request)), $this->with), - $this->calculateStatus(), $this->headers + $this->calculateStatus(), $this->resource->headers )); } diff --git a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php index f75c73172099..dcf818fd9f00 100644 --- a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php @@ -23,7 +23,7 @@ public function toResponse($request) return $this->build($request, response()->json( array_merge_recursive($this->wrap($this->resource->toJson($request)), $this->with), - $this->calculateStatus(), $this->headers + $this->calculateStatus(), $this->resource->headers )); } diff --git a/src/Illuminate/Http/Resources/ResourceResponse.php b/src/Illuminate/Http/Resources/ResourceResponse.php index 1cc8d12822f1..7b44871840e6 100644 --- a/src/Illuminate/Http/Resources/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/ResourceResponse.php @@ -15,27 +15,6 @@ abstract class ResourceResponse implements Responsable */ public $resource; - /** - * The HTTP status code of the response. - * - * @var int - */ - public $status; - - /** - * The headers that should be present on the response. - * - * @var array - */ - public $headers = []; - - /** - * The callback that will customize the response. - * - * @var \Closure - */ - public $callback; - /** * Create a new resource repsonse. * @@ -45,56 +24,6 @@ abstract class ResourceResponse implements Responsable public function __construct($resource) { $this->resource = $resource; - - $this->withResponse(function ($request, $response) { - return $response; - }); - } - - /** - * Set the HTTP status code on the response. - * - * @param int $status - * @return $this - */ - public function status($status) - { - $this->status = $status; - - return $this; - } - - /** - * Set a header on the response. - * - * @param string $key - * @param string $value - * @return $this - */ - public function header($key, $value) - { - $this->headers[$key] = $value; - - return $this; - } - - /** - * Add an array of headers to the response. - * - * @param \Symfony\Component\HttpFoundation\HeaderBag|array $headers - * @return $this - */ - public function withHeaders($headers) - { - if ($headers instanceof HeaderBag) { - $headers = $headers->all(); - } - - foreach ($headers as $key => $value) { - $this->headers[$key] = $value; - } - - return $this; } /** @@ -107,25 +36,12 @@ public function withHeaders($headers) protected function build($request, $response) { return tap($response, function ($response) use ($request) { - call_user_func($this->callback, $request, $response); + call_user_func($this->resource->callback, $request, $response); $this->resource->withResponse($request, $response); }); } - /** - * Register a callback that will be used to customize the response. - * - * @param \Closure $callback - * @return $this - */ - public function withResponse($callback) - { - $this->callback = $callback; - - return $this; - } - /** * Calculate the appropriate status code for the response. * @@ -133,8 +49,8 @@ public function withResponse($callback) */ protected function calculateStatus() { - if ($this->status) { - return $this->status; + if ($this->resource->status) { + return $this->resource->status; } return $this->resource->resource instanceof Model && diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index e56640cfba77..c25459a15cdc 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -186,7 +186,7 @@ public function test_custom_headers_may_be_set_on_responses() return (new PostResource(new Post([ 'id' => 5, 'title' => 'Test Title', - ])))->json()->status(202)->header('X-Custom', 'True'); + ])))->status(202)->header('X-Custom', 'True')->json(); }); $response = $this->withoutExceptionHandling()->get( @@ -197,6 +197,24 @@ public function test_custom_headers_may_be_set_on_responses() $response->assertHeader('X-Custom', 'True'); } + public function test_custom_headers_may_be_set_on_responses_using_callback() + { + Route::get('/', function () { + return (new PostResource(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ])))->using(function ($request, $response) { + $response->header('X-Custom', 'True'); + })->json(); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertHeader('X-Custom', 'True'); + } + public function test_resources_may_be_converted_to_html() { Route::get('/', function () { From 35baf7aaa6e7a907d5c687fcc6f12f51867b907e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 21:04:09 -0500 Subject: [PATCH 26/45] wip --- src/Illuminate/Http/Resource.php | 473 ------------------ src/Illuminate/Http/ResourceCollection.php | 116 ----- .../Http/Resources/CollectsResources.php | 55 ++ .../Http/Resources/CssResourceResponse.php | 40 -- .../Http/Resources/HtmlResourceResponse.php | 46 -- .../PaginatedResourceResponse.php} | 24 +- .../Http/Resources/Json/Resource.php | 157 ++++++ .../Resources/Json/ResourceCollection.php | 69 +++ .../ResourceResponse.php} | 45 +- src/Illuminate/Http/Resources/Resource.php | 157 ++++++ .../Http/Resources/ResourceCollection.php | 41 ++ .../Resources/UnknownCollectionException.php | 21 + 12 files changed, 514 insertions(+), 730 deletions(-) delete mode 100644 src/Illuminate/Http/Resource.php delete mode 100644 src/Illuminate/Http/ResourceCollection.php create mode 100644 src/Illuminate/Http/Resources/CollectsResources.php delete mode 100644 src/Illuminate/Http/Resources/CssResourceResponse.php delete mode 100644 src/Illuminate/Http/Resources/HtmlResourceResponse.php rename src/Illuminate/Http/Resources/{PaginatedJsonResourceResponse.php => Json/PaginatedResourceResponse.php} (74%) create mode 100644 src/Illuminate/Http/Resources/Json/Resource.php create mode 100644 src/Illuminate/Http/Resources/Json/ResourceCollection.php rename src/Illuminate/Http/Resources/{JsonResourceResponse.php => Json/ResourceResponse.php} (62%) create mode 100644 src/Illuminate/Http/Resources/Resource.php create mode 100644 src/Illuminate/Http/Resources/ResourceCollection.php create mode 100644 src/Illuminate/Http/Resources/UnknownCollectionException.php diff --git a/src/Illuminate/Http/Resource.php b/src/Illuminate/Http/Resource.php deleted file mode 100644 index 3869e6a2635c..000000000000 --- a/src/Illuminate/Http/Resource.php +++ /dev/null @@ -1,473 +0,0 @@ -resource = $resource; - - $this->callback = function () { - // - }; - } - - /** - * Create a resource response based on the incoming request. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function response($request) - { - if (isset(static::$extensions[$format = $request->format()])) { - return call_user_func(static::$extensions[$format], $this); - } - - if ($request->expectsJson()) { - return $this->json(); - } - - switch ($format) { - case 'html': - return $this->html(); - - case 'css': - return $this->css(); - - default: - return $this->json(); - } - } - - /** - * Create a new HTML resource response for the given resource. - * - * @return \App\ResourceResponse - */ - public function html() - { - return new Resources\HtmlResourceResponse($this); - } - - /** - * Create a new CSS resource response for the given resource. - * - * @return \App\ResourceResponse - */ - public function css() - { - return new Resources\CssResourceResponse($this); - } - - /** - * Create a new JSON resource response for the given resource. - * - * @return \App\ResourceResponse - */ - public function json() - { - return new Resources\JsonResourceResponse($this); - } - - /** - * Transform the resource into a JSON array. - * - * @param \Illuminate\Http\Request - * @return array - */ - public function toJson($request) - { - return $this->resourceToJson($request); - } - - /** - * Convert the resource into a JSON array. - * - * @param \Illuminate\Http\Request $request - * @return array - */ - protected function resourceToJson($request) - { - $values = $this->resource->toArray(); - - if (count($this->visible) > 0) { - $values = array_intersect_key($values, array_flip($this->visible)); - } - - if (count($this->hidden) > 0) { - $values = array_diff_key($values, array_flip($this->hidden)); - } - - return $values; - } - - /** - * Customize the response for a request. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Http\Response $response - * @return void - */ - public function withResponse($request, $response) - { - // - } - - /** - * Customize the response for a HTML request. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Http\Response $response - * @return void - */ - public function withHtmlResponse($request, $response) - { - // - } - - /** - * Customize the response for a CSS request. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Http\Response $response - * @return void - */ - public function withCssResponse($request, $response) - { - // - } - - /** - * Customize the response for a JSON request. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Http\Response $response - * @return void - */ - public function withJsonResponse($request, $response) - { - // - } - - /** - * Get the value of the resource's route key. - * - * @return mixed - */ - public function getRouteKey() - { - return $this->resource->getRouteKey(); - } - - /** - * Get the route key for the resource. - * - * @return string - */ - public function getRouteKeyName() - { - return $this->resource->getRouteKeyName(); - } - - /** - * Retrieve the model for a bound value. - * - * @param mixed $value - * @return static - */ - public function resolveRouteBinding($value) - { - throw new Exception("Resources may not be implicitly resolved from route bindings."); - } - - /** - * Set the string that should wrap the outer-most JSON array. - * - * @param string $value - * @return void - */ - public static function wrap($value) - { - static::$wrap = $value; - } - - /** - * Disable wrapping of the outer-most JSON array. - * - * @param string $value - * @return void - */ - public static function withoutWrapping() - { - static::$wrap = null; - } - - /** - * Create an HTTP response that represents the object. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function toResponse($request) - { - return $this->response($request)->toResponse($request); - } - - /** - * Extend the resource with a new format. - * - * @param string $format - * @param \Closure $callback - * @return void - */ - public static function format($format, $callback) - { - static::$extensions[$format] = $callback; - } - - /** - * Prepare the resource for JSON serialization. - * - * @return array - */ - public function jsonSerialize() - { - return $this->toJson(Container::getInstance()->make('request')); - } - - /** - * Set the HTTP status code that should be added to the response. - * - * @param int $status - * @return $this - */ - public function status($status) - { - $this->status = $status; - - return $this; - } - - /** - * Define a header that should be added to the response. - * - * @param string $key - * @param string $value - * @return $this - */ - public function header($key, $value) - { - $this->headers[$key] = $value; - - return $this; - } - - /** - * Add an array of headers that should be added to the response. - * - * @param \Symfony\Component\HttpFoundation\HeaderBag|array $headers - * @return $this - */ - public function withHeaders($headers) - { - if ($headers instanceof HeaderBag) { - $headers = $headers->all(); - } - - foreach ($headers as $key => $value) { - $this->headers[$key] = $value; - } - - return $this; - } - - /** - * Define a custom callback that should customize the response. - * - * @param \Closure $callback - * @return $this - */ - public function using($callback) - { - $this->callback = $callback; - - return $this; - } - - /** - * Determine if the given attribute exists. - * - * @param mixed $offset - * @return bool - */ - public function offsetExists($offset) - { - return array_key_exists($this->resource[$offset]); - } - - /** - * Get the value for a given offset. - * - * @param mixed $offset - * @return mixed - */ - public function offsetGet($offset) - { - return $this->resource[$offset]; - } - - /** - * Set the value for a given offset. - * - * @param mixed $offset - * @param mixed $value - * @return void - */ - public function offsetSet($offset, $value) - { - $this->resource[$offset] = $value; - } - - /** - * Unset the value for a given offset. - * - * @param mixed $offset - * @return void - */ - public function offsetUnset($offset) - { - unset($this->resource[$offset]); - } - - /** - * Determine if an attribute exists on the resource. - * - * @param string $key - * @return bool - */ - public function __isset($key) - { - return isset($this->resource->{$key}); - } - - /** - * Unset an attribute on the resource. - * - * @param string $key - * @return void - */ - public function __unset($key) - { - unset($this->resource->{$key}); - } - - /** - * Dynamically get properties from the underlying resource. - * - * @param string $key - * @return mixed - */ - public function __get($key) - { - return $this->resource->{$key}; - } - - /** - * Dynamically pass method calls to the underlying resource. - * - * @param string $method - * @param array $parameters - * @return mixed - */ - public function __call($method, $parameters) - { - return $this->resource->{$method}(...$parameters); - } -} diff --git a/src/Illuminate/Http/ResourceCollection.php b/src/Illuminate/Http/ResourceCollection.php deleted file mode 100644 index bf17f0e63b63..000000000000 --- a/src/Illuminate/Http/ResourceCollection.php +++ /dev/null @@ -1,116 +0,0 @@ -resource = $this->collectResource($resource); - } - - /** - * Map the given collection resource into its individual resources. - * - * @param mixed $resource - * @return mixed - */ - protected function collectResource($resource) - { - $this->collection = $resource->mapInto($this->collects()); - - return $resource instanceof AbstractPaginator - ? $resource->setCollection($this->collection) - : $this->collection; - } - - /** - * Get the resource that this resource collects. - * - * @return string - */ - protected function collects() - { - if ($this->collects) { - return $this->collects; - } - - if (Str::endsWith(class_basename($this), 'Collection') && - class_exists($class = Str::replaceLast('Collection', '', get_class($this)))) { - return $class; - } - - throw new Exception( - 'The ['.get_class($this).'] resource must specify the models it collects.' - ); - } - - /** - * Create a new JSON resource response for the given resource. - * - * @return \App\ResourceResponse - */ - public function json() - { - return $this->resource instanceof AbstractPaginator - ? new Resources\PaginatedJsonResourceResponse($this) - : parent::json(); - } - - /** - * Transform the resource into a JSON array. - * - * @param \Illuminate\Http\Request - * @return array - */ - public function toJson($request) - { - return $this->resource->map(function ($item) use ($request) { - return $item->toJson($request); - })->all(); - } - - /** - * Get an iterator for the resource collection. - * - * @return \ArrayIterator - */ - public function getIterator() - { - if ($this->collection instanceof IteratorAggregate) { - return $this->collection->getIterator(); - } - - throw new Exception( - "Unable to generate an iterator for this resource collection." - ); - } -} diff --git a/src/Illuminate/Http/Resources/CollectsResources.php b/src/Illuminate/Http/Resources/CollectsResources.php new file mode 100644 index 000000000000..2dbee7bda537 --- /dev/null +++ b/src/Illuminate/Http/Resources/CollectsResources.php @@ -0,0 +1,55 @@ +collection = $resource->mapInto($this->collects()); + + return $resource instanceof AbstractPaginator + ? $resource->setCollection($this->collection) + : $this->collection; + } + + /** + * Get the resource that this resource collects. + * + * @return string + */ + protected function collects() + { + if ($this->collects) { + return $this->collects; + } + + if (Str::endsWith(class_basename($this), 'Collection') && + class_exists($class = Str::replaceLast('Collection', '', get_class($this)))) { + return $class; + } + + throw new UnknownCollectionException($this); + } + + /** + * Get an iterator for the resource collection. + * + * @return \ArrayIterator + */ + public function getIterator() + { + return $this->collection->getIterator(); + } +} diff --git a/src/Illuminate/Http/Resources/CssResourceResponse.php b/src/Illuminate/Http/Resources/CssResourceResponse.php deleted file mode 100644 index 7f7ed526da47..000000000000 --- a/src/Illuminate/Http/Resources/CssResourceResponse.php +++ /dev/null @@ -1,40 +0,0 @@ -resource, 'toCss')) { - throw new HttpException(406); - } - - return $this->build($request, response( - $this->resource->toCss($request), - 200, ['Content-Type' => 'text/css'] - )); - } - - /** - * Build the finished HTTP response. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Http\Response $response - * @return \Illuminate\Http\Response - */ - protected function build($request, $response) - { - return tap(parent::build($request, $response), function ($response) use ($request) { - $this->resource->withCssResponse($request, $response); - }); - } -} diff --git a/src/Illuminate/Http/Resources/HtmlResourceResponse.php b/src/Illuminate/Http/Resources/HtmlResourceResponse.php deleted file mode 100644 index 40b5e80e5f44..000000000000 --- a/src/Illuminate/Http/Resources/HtmlResourceResponse.php +++ /dev/null @@ -1,46 +0,0 @@ -resource, 'toHtml')) { - throw new HttpException(406); - } - - $view = $this->resource->toHtml($request); - - if ($view instanceof View && ! isset($view->resource)) { - $view->with('resource', $this->resource); - } - - return $this->build($request, response( - $view, $this->calculateStatus(), $this->resource->headers - )); - } - - /** - * Build the finished HTTP response. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Http\Response $response - * @return \Illuminate\Http\Response - */ - protected function build($request, $response) - { - return tap(parent::build($request, $response), function ($response) use ($request) { - $this->resource->withHtmlResponse($request, $response); - }); - } -} diff --git a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php similarity index 74% rename from src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php rename to src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php index dcf818fd9f00..8b291d604566 100644 --- a/src/Illuminate/Http/Resources/PaginatedJsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php @@ -1,11 +1,10 @@ resource, 'toJson')) { - throw new HttpException(406); - } - - $this->addPaginationInformation($request); - return $this->build($request, response()->json( - array_merge_recursive($this->wrap($this->resource->toJson($request)), $this->with), + array_merge_recursive( + $this->wrap($this->resource->toJson($request)), + $this->paginationInformation($request) + ), $this->calculateStatus(), $this->resource->headers )); } @@ -31,16 +27,16 @@ public function toResponse($request) * Add the pagination information to the response. * * @param \Illuminate\Http\Request $request - * @return void + * @return array */ - protected function addPaginationInformation($request) + protected function paginationInformation($request) { $paginated = $this->resource->resource->toArray(); - return $this->with([ + return [ 'links' => $this->paginationLinks($paginated), 'meta' => $this->meta($paginated), - ]); + ]; } /** diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php new file mode 100644 index 000000000000..ca92041d821a --- /dev/null +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -0,0 +1,157 @@ +resourceToJson($request); + } + + /** + * Convert the resource into a JSON array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + protected function resourceToJson($request) + { + $values = $this->resource->toArray(); + + if (count($this->visible) > 0) { + $values = array_intersect_key($values, array_flip($this->visible)); + } + + if (count($this->hidden) > 0) { + $values = array_diff_key($values, array_flip($this->hidden)); + } + + return $values; + } + + /** + * Customize the response for a request. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Response $response + * @return void + */ + public function withResponse($request, $response) + { + // + } + + /** + * Define a custom callback that should customize the response. + * + * @param \Closure $callback + * @return $this + */ + public function using($callback) + { + $this->callback = $callback; + + return $this; + } + + /** + * Set the string that should wrap the outer-most JSON array. + * + * @param string $value + * @return void + */ + public static function wrap($value) + { + static::$wrap = $value; + } + + /** + * Disable wrapping of the outer-most JSON array. + * + * @param string $value + * @return void + */ + public static function withoutWrapping() + { + static::$wrap = null; + } + + /** + * Create an HTTP response that represents the object. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function toResponse($request) + { + return (new ResourceResponse($this))->toResponse($request); + } + + /** + * Prepare the resource for JSON serialization. + * + * @return array + */ + public function jsonSerialize() + { + return $this->toJson(Container::getInstance()->make('request')); + } +} diff --git a/src/Illuminate/Http/Resources/Json/ResourceCollection.php b/src/Illuminate/Http/Resources/Json/ResourceCollection.php new file mode 100644 index 000000000000..fe4e372f5b45 --- /dev/null +++ b/src/Illuminate/Http/Resources/Json/ResourceCollection.php @@ -0,0 +1,69 @@ +resource = $this->collectResource($resource); + } + + /** + * Transform the resource into a JSON array. + * + * @param \Illuminate\Http\Request + * @return array + */ + public function toJson($request) + { + return $this->resource->map(function ($item) use ($request) { + return $item->toJson($request); + })->all(); + } + + /** + * Create an HTTP response that represents the object. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function toResponse($request) + { + return $this->resource instanceof AbstractPaginator + ? (new PaginatedResourceResponse($this))->toResponse($request) + : parent::toResponse($request); + } +} diff --git a/src/Illuminate/Http/Resources/JsonResourceResponse.php b/src/Illuminate/Http/Resources/Json/ResourceResponse.php similarity index 62% rename from src/Illuminate/Http/Resources/JsonResourceResponse.php rename to src/Illuminate/Http/Resources/Json/ResourceResponse.php index c52c8c164dfd..989aa8b22d54 100644 --- a/src/Illuminate/Http/Resources/JsonResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/ResourceResponse.php @@ -1,19 +1,13 @@ resource, 'toJson')) { - throw new HttpException(406); - } - return $this->build($request, response()->json( - array_merge_recursive($this->wrap($this->resource->toJson($request)), $this->with), + $this->wrap($this->resource->toJson($request)), $this->calculateStatus(), $this->resource->headers )); } @@ -77,19 +67,6 @@ protected function haveAdditionalInformationAndDataIsUnwrapped($data) ! array_key_exists($this->wrapper(), $data)); } - /** - * Add the given array to the response body. - * - * @param array $values - * @return $this - */ - public function with(array $values) - { - $this->with = array_merge($this->with, $values); - - return $this; - } - /** * Get the default data wrapper for the resource. * @@ -101,18 +78,4 @@ protected function wrapper() return $class::$wrap; } - - /** - * Build the finished HTTP response. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Http\Response $response - * @return \Illuminate\Http\Response - */ - protected function build($request, $response) - { - return tap(parent::build($request, $response), function ($response) use ($request) { - $this->resource->withJsonResponse($request, $response); - }); - } } diff --git a/src/Illuminate/Http/Resources/Resource.php b/src/Illuminate/Http/Resources/Resource.php new file mode 100644 index 000000000000..30a9880ecbab --- /dev/null +++ b/src/Illuminate/Http/Resources/Resource.php @@ -0,0 +1,157 @@ +resource = $resource; + + $this->callback = function () { + // + }; + } + + /** + * Get the value of the resource's route key. + * + * @return mixed + */ + public function getRouteKey() + { + return $this->resource->getRouteKey(); + } + + /** + * Get the route key for the resource. + * + * @return string + */ + public function getRouteKeyName() + { + return $this->resource->getRouteKeyName(); + } + + /** + * Retrieve the model for a bound value. + * + * @param mixed $value + * @return static + */ + public function resolveRouteBinding($value) + { + throw new Exception("Resources may not be implicitly resolved from route bindings."); + } + + /** + * Determine if the given attribute exists. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($this->resource[$offset]); + } + + /** + * Get the value for a given offset. + * + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->resource[$offset]; + } + + /** + * Set the value for a given offset. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->resource[$offset] = $value; + } + + /** + * Unset the value for a given offset. + * + * @param mixed $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->resource[$offset]); + } + + /** + * Determine if an attribute exists on the resource. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->resource->{$key}); + } + + /** + * Unset an attribute on the resource. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->resource->{$key}); + } + + /** + * Dynamically get properties from the underlying resource. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->resource->{$key}; + } + + /** + * Dynamically pass method calls to the underlying resource. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->resource->{$method}(...$parameters); + } +} diff --git a/src/Illuminate/Http/Resources/ResourceCollection.php b/src/Illuminate/Http/Resources/ResourceCollection.php new file mode 100644 index 000000000000..b105bb0b9701 --- /dev/null +++ b/src/Illuminate/Http/Resources/ResourceCollection.php @@ -0,0 +1,41 @@ +resource = $this->collectResource($resource); + } +} diff --git a/src/Illuminate/Http/Resources/UnknownCollectionException.php b/src/Illuminate/Http/Resources/UnknownCollectionException.php new file mode 100644 index 000000000000..126146a59a90 --- /dev/null +++ b/src/Illuminate/Http/Resources/UnknownCollectionException.php @@ -0,0 +1,21 @@ + Date: Tue, 22 Aug 2017 21:04:47 -0500 Subject: [PATCH 27/45] wip --- src/Illuminate/Http/Resources/CollectsResources.php | 1 - src/Illuminate/Http/Resources/Json/Resource.php | 3 --- src/Illuminate/Http/Resources/Json/ResourceCollection.php | 3 --- src/Illuminate/Http/Resources/Json/ResourceResponse.php | 1 - src/Illuminate/Http/Resources/Resource.php | 3 --- src/Illuminate/Http/Resources/ResourceCollection.php | 3 --- src/Illuminate/Http/Resources/ResourceResponse.php | 1 - 7 files changed, 15 deletions(-) diff --git a/src/Illuminate/Http/Resources/CollectsResources.php b/src/Illuminate/Http/Resources/CollectsResources.php index 2dbee7bda537..a394948b0772 100644 --- a/src/Illuminate/Http/Resources/CollectsResources.php +++ b/src/Illuminate/Http/Resources/CollectsResources.php @@ -2,7 +2,6 @@ namespace Illuminate\Http\Resources; -use Exception; use Illuminate\Support\Str; use Illuminate\Support\Collection; use Illuminate\Pagination\AbstractPaginator; diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index ca92041d821a..c0f6924715ac 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -2,14 +2,11 @@ namespace Illuminate\Http\Resources\Json; -use Exception; use ArrayAccess; use JsonSerializable; -use Illuminate\Support\Collection; use Illuminate\Container\Container; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; -use Symfony\Component\HttpFoundation\HeaderBag; use Illuminate\Http\Resources\Resource as BaseResource; class Resource extends BaseResource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable diff --git a/src/Illuminate/Http/Resources/Json/ResourceCollection.php b/src/Illuminate/Http/Resources/Json/ResourceCollection.php index fe4e372f5b45..21ccc6a52a72 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceCollection.php +++ b/src/Illuminate/Http/Resources/Json/ResourceCollection.php @@ -2,13 +2,10 @@ namespace Illuminate\Http\Resources\Json; -use Exception; use IteratorAggregate; -use Illuminate\Support\Str; use Illuminate\Support\Collection; use Illuminate\Pagination\AbstractPaginator; use Illuminate\Http\Resources\CollectsResources; -use Illuminate\Http\Resources\UnknownExceptionCollection; class ResourceCollection extends Resource implements IteratorAggregate { diff --git a/src/Illuminate/Http/Resources/Json/ResourceResponse.php b/src/Illuminate/Http/Resources/Json/ResourceResponse.php index 989aa8b22d54..287fab9b0d60 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/ResourceResponse.php @@ -3,7 +3,6 @@ namespace Illuminate\Http\Resources\Json; use Illuminate\Support\Collection; -use Symfony\Component\HttpKernel\Exception\HttpException; use Illuminate\Http\Resources\ResourceResponse as BaseResourceResponse; class ResourceResponse extends BaseResourceResponse diff --git a/src/Illuminate/Http/Resources/Resource.php b/src/Illuminate/Http/Resources/Resource.php index 30a9880ecbab..51365969ab8f 100644 --- a/src/Illuminate/Http/Resources/Resource.php +++ b/src/Illuminate/Http/Resources/Resource.php @@ -4,11 +4,8 @@ use Exception; use ArrayAccess; -use Illuminate\Support\Collection; -use Illuminate\Container\Container; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; -use Symfony\Component\HttpFoundation\HeaderBag; abstract class Resource implements ArrayAccess, Responsable, UrlRoutable { diff --git a/src/Illuminate/Http/Resources/ResourceCollection.php b/src/Illuminate/Http/Resources/ResourceCollection.php index b105bb0b9701..c8110d59a8ec 100644 --- a/src/Illuminate/Http/Resources/ResourceCollection.php +++ b/src/Illuminate/Http/Resources/ResourceCollection.php @@ -2,11 +2,8 @@ namespace Illuminate\Http\Resources; -use Exception; use IteratorAggregate; -use Illuminate\Support\Str; use Illuminate\Support\Collection; -use Illuminate\Pagination\AbstractPaginator; abstract class ResourceCollection extends Resource implements IteratorAggregate { diff --git a/src/Illuminate/Http/Resources/ResourceResponse.php b/src/Illuminate/Http/Resources/ResourceResponse.php index 7b44871840e6..4df956451218 100644 --- a/src/Illuminate/Http/Resources/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/ResourceResponse.php @@ -4,7 +4,6 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Contracts\Support\Responsable; -use Symfony\Component\HttpFoundation\HeaderBag; abstract class ResourceResponse implements Responsable { From 4f095ab5f033fc72e3eefef19073300d31552f3c Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 21:16:57 -0500 Subject: [PATCH 28/45] wip --- src/Illuminate/Http/Resources/Resource.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Illuminate/Http/Resources/Resource.php b/src/Illuminate/Http/Resources/Resource.php index 51365969ab8f..4d5aa00c69eb 100644 --- a/src/Illuminate/Http/Resources/Resource.php +++ b/src/Illuminate/Http/Resources/Resource.php @@ -25,10 +25,6 @@ abstract class Resource implements ArrayAccess, Responsable, UrlRoutable public function __construct($resource) { $this->resource = $resource; - - $this->callback = function () { - // - }; } /** From 4dad05d4d4670242335a76f0c635cfe0211ce975 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 21:28:39 -0500 Subject: [PATCH 29/45] wip --- src/Illuminate/Http/Resources/Json/Resource.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index c0f6924715ac..7da0a053c5fd 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -52,6 +52,21 @@ class Resource extends BaseResource implements ArrayAccess, JsonSerializable, Re */ public static $wrap = 'data'; + /** + * Create a new resource instance. + * + * @param mixed $resource + * @return void + */ + public function __construct($resource) + { + parent::__construct($resource); + + $this->callback = function () { + // + }; + } + /** * Transform the resource into a JSON array. * From 48eeb7ac81feaa7afb932307d8b6b34f6d48a5ab Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 21:33:33 -0500 Subject: [PATCH 30/45] wip --- .../Resources/Json/PaginatedResourceResponse.php | 11 ++++------- src/Illuminate/Http/Resources/Json/Resource.php | 13 ------------- .../Http/Resources/Json/ResourceResponse.php | 2 +- src/Illuminate/Http/Resources/ResourceResponse.php | 4 ---- 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php index 8b291d604566..91c0cf536b8e 100644 --- a/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php @@ -14,13 +14,10 @@ class PaginatedResourceResponse extends ResourceResponse */ public function toResponse($request) { - return $this->build($request, response()->json( - array_merge_recursive( - $this->wrap($this->resource->toJson($request)), - $this->paginationInformation($request) - ), - $this->calculateStatus(), $this->resource->headers - )); + return $this->build($request, response()->json(array_merge_recursive( + $this->wrap($this->resource->toJson($request)), + $this->paginationInformation($request) + ), $this->calculateStatus())); } /** diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 7da0a053c5fd..4309dc821448 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -25,19 +25,6 @@ class Resource extends BaseResource implements ArrayAccess, JsonSerializable, Re */ public $visible = []; - /** - * The status code that should be used for the response. - * - * @var int - */ - public $status; - /** - * The headers that should be added to the response. - * - * @var array - */ - public $headers = []; - /** * The callback that should customize the response. * diff --git a/src/Illuminate/Http/Resources/Json/ResourceResponse.php b/src/Illuminate/Http/Resources/Json/ResourceResponse.php index 287fab9b0d60..79cd9d53f850 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/ResourceResponse.php @@ -17,7 +17,7 @@ public function toResponse($request) { return $this->build($request, response()->json( $this->wrap($this->resource->toJson($request)), - $this->calculateStatus(), $this->resource->headers + $this->calculateStatus() )); } diff --git a/src/Illuminate/Http/Resources/ResourceResponse.php b/src/Illuminate/Http/Resources/ResourceResponse.php index 4df956451218..29bddc97c321 100644 --- a/src/Illuminate/Http/Resources/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/ResourceResponse.php @@ -48,10 +48,6 @@ protected function build($request, $response) */ protected function calculateStatus() { - if ($this->resource->status) { - return $this->resource->status; - } - return $this->resource->resource instanceof Model && $this->resource->resource->wasRecentlyCreated ? 201 : 200; } From 7f51eb74d3ca8606625719acbddbb0ffc6aa2bb0 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 22 Aug 2017 21:35:24 -0500 Subject: [PATCH 31/45] wip --- .../Http/Resources/Json/Resource.php | 20 ------------------- .../Http/Resources/ResourceResponse.php | 2 -- 2 files changed, 22 deletions(-) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 4309dc821448..9970c8dbc0d2 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -25,13 +25,6 @@ class Resource extends BaseResource implements ArrayAccess, JsonSerializable, Re */ public $visible = []; - /** - * The callback that should customize the response. - * - * @var \Closure - */ - public $callback; - /** * The "data" wrapper that should be applied. * @@ -98,19 +91,6 @@ public function withResponse($request, $response) // } - /** - * Define a custom callback that should customize the response. - * - * @param \Closure $callback - * @return $this - */ - public function using($callback) - { - $this->callback = $callback; - - return $this; - } - /** * Set the string that should wrap the outer-most JSON array. * diff --git a/src/Illuminate/Http/Resources/ResourceResponse.php b/src/Illuminate/Http/Resources/ResourceResponse.php index 29bddc97c321..b46b12b111d3 100644 --- a/src/Illuminate/Http/Resources/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/ResourceResponse.php @@ -35,8 +35,6 @@ public function __construct($resource) protected function build($request, $response) { return tap($response, function ($response) use ($request) { - call_user_func($this->resource->callback, $request, $response); - $this->resource->withResponse($request, $response); }); } From 2a833acf382e94c334e372eff7c3dc580030d608 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 11:01:56 -0500 Subject: [PATCH 32/45] wip --- .../Http/Resources/DelegatesToResource.php | 129 ++++++++++++++++++ .../Json/PaginatedResourceResponse.php | 6 +- .../Http/Resources/Json/Resource.php | 14 +- .../Http/Resources/Json/ResourceResponse.php | 73 +++++----- src/Illuminate/Http/Resources/Resource.php | 123 +---------------- .../Http/Resources/ResourceResponse.php | 52 ------- 6 files changed, 184 insertions(+), 213 deletions(-) create mode 100644 src/Illuminate/Http/Resources/DelegatesToResource.php delete mode 100644 src/Illuminate/Http/Resources/ResourceResponse.php diff --git a/src/Illuminate/Http/Resources/DelegatesToResource.php b/src/Illuminate/Http/Resources/DelegatesToResource.php new file mode 100644 index 000000000000..1d4a4dea6331 --- /dev/null +++ b/src/Illuminate/Http/Resources/DelegatesToResource.php @@ -0,0 +1,129 @@ +resource->getRouteKey(); + } + + /** + * Get the route key for the resource. + * + * @return string + */ + public function getRouteKeyName() + { + return $this->resource->getRouteKeyName(); + } + + /** + * Retrieve the model for a bound value. + * + * @param mixed $value + * @return static + */ + public function resolveRouteBinding($value) + { + throw new Exception("Resources may not be implicitly resolved from route bindings."); + } + + /** + * Determine if the given attribute exists. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($this->resource[$offset]); + } + + /** + * Get the value for a given offset. + * + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->resource[$offset]; + } + + /** + * Set the value for a given offset. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet($offset, $value) + { + $this->resource[$offset] = $value; + } + + /** + * Unset the value for a given offset. + * + * @param mixed $offset + * @return void + */ + public function offsetUnset($offset) + { + unset($this->resource[$offset]); + } + + /** + * Determine if an attribute exists on the resource. + * + * @param string $key + * @return bool + */ + public function __isset($key) + { + return isset($this->resource->{$key}); + } + + /** + * Unset an attribute on the resource. + * + * @param string $key + * @return void + */ + public function __unset($key) + { + unset($this->resource->{$key}); + } + + /** + * Dynamically get properties from the underlying resource. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->resource->{$key}; + } + + /** + * Dynamically pass method calls to the underlying resource. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->resource->{$method}(...$parameters); + } +} diff --git a/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php index 91c0cf536b8e..55885f3c3b33 100644 --- a/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php @@ -14,10 +14,12 @@ class PaginatedResourceResponse extends ResourceResponse */ public function toResponse($request) { - return $this->build($request, response()->json(array_merge_recursive( + return tap(response()->json(array_merge_recursive( $this->wrap($this->resource->toJson($request)), $this->paginationInformation($request) - ), $this->calculateStatus())); + ), $this->calculateStatus()), function ($response) use ($request) { + $this->resource->withResponse($request, $response); + }); } /** diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 9970c8dbc0d2..02ca03df7f35 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -7,10 +7,20 @@ use Illuminate\Container\Container; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; +use Illuminate\Http\Resources\DelegatesToResource; use Illuminate\Http\Resources\Resource as BaseResource; -class Resource extends BaseResource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable +class Resource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable { + use DelegatesToResource; + + /** + * The resource instance. + * + * @var mixed + */ + public $resource; + /** * The attributes that should be hidden when serialized. * @@ -40,7 +50,7 @@ class Resource extends BaseResource implements ArrayAccess, JsonSerializable, Re */ public function __construct($resource) { - parent::__construct($resource); + $this->resource = $resource; $this->callback = function () { // diff --git a/src/Illuminate/Http/Resources/Json/ResourceResponse.php b/src/Illuminate/Http/Resources/Json/ResourceResponse.php index 79cd9d53f850..bd4c668ede74 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/ResourceResponse.php @@ -3,10 +3,29 @@ namespace Illuminate\Http\Resources\Json; use Illuminate\Support\Collection; +use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\Resources\ResourceResponse as BaseResourceResponse; -class ResourceResponse extends BaseResourceResponse +class ResourceResponse implements Responsable { + /** + * The underlying resource. + * + * @var mixed + */ + public $resource; + + /** + * Create a new resource repsonse. + * + * @param mixed $resource + * @return void + */ + public function __construct($resource) + { + $this->resource = $resource; + } + /** * Create an HTTP response that represents the object. * @@ -15,10 +34,11 @@ class ResourceResponse extends BaseResourceResponse */ public function toResponse($request) { - return $this->build($request, response()->json( - $this->wrap($this->resource->toJson($request)), - $this->calculateStatus() - )); + return tap(response()->json( + $this->wrap($this->resource->toJson($request)), $this->calculateStatus() + ), function ($response) use ($request) { + $this->resource->withResponse($request, $response); + }); } /** @@ -33,48 +53,29 @@ protected function wrap($data) $data = $data->all(); } - if ($this->haveDefaultWrapperAndDataIsUnwrapped($data)) { - $data = [$this->wrapper() => $data]; - } elseif ($this->haveAdditionalInformationAndDataIsUnwrapped($data)) { - $data = [($this->wrapper() ?? 'data') => $data]; - } - - return $data; + return $this->wrapper() && ! array_key_exists($this->wrapper(), $data) + ? [$this->wrapper() => $data] + : $data; } /** - * Determine if we have a default wrapper and the given data is unwrapped. - * - * @param array $data - * @return bool - */ - protected function haveDefaultWrapperAndDataIsUnwrapped($data) - { - return $this->wrapper() && ! array_key_exists($this->wrapper(), $data); - } - - /** - * Determine if "with" data has been added and our data is unwrapped. + * Get the default data wrapper for the resource. * - * @param array $data - * @return bool + * @return string */ - protected function haveAdditionalInformationAndDataIsUnwrapped($data) + protected function wrapper() { - return ! empty($this->with) && - (! $this->wrapper() || - ! array_key_exists($this->wrapper(), $data)); + return get_class($this->resource)::$wrap; } /** - * Get the default data wrapper for the resource. + * Calculate the appropriate status code for the response. * - * @return string + * @return int */ - protected function wrapper() + protected function calculateStatus() { - $class = get_class($this->resource); - - return $class::$wrap; + return $this->resource->resource instanceof Model && + $this->resource->resource->wasRecentlyCreated ? 201 : 200; } } diff --git a/src/Illuminate/Http/Resources/Resource.php b/src/Illuminate/Http/Resources/Resource.php index 4d5aa00c69eb..c4647eaec962 100644 --- a/src/Illuminate/Http/Resources/Resource.php +++ b/src/Illuminate/Http/Resources/Resource.php @@ -9,6 +9,8 @@ abstract class Resource implements ArrayAccess, Responsable, UrlRoutable { + use DelegatesToResource; + /** * The resource instance. * @@ -26,125 +28,4 @@ public function __construct($resource) { $this->resource = $resource; } - - /** - * Get the value of the resource's route key. - * - * @return mixed - */ - public function getRouteKey() - { - return $this->resource->getRouteKey(); - } - - /** - * Get the route key for the resource. - * - * @return string - */ - public function getRouteKeyName() - { - return $this->resource->getRouteKeyName(); - } - - /** - * Retrieve the model for a bound value. - * - * @param mixed $value - * @return static - */ - public function resolveRouteBinding($value) - { - throw new Exception("Resources may not be implicitly resolved from route bindings."); - } - - /** - * Determine if the given attribute exists. - * - * @param mixed $offset - * @return bool - */ - public function offsetExists($offset) - { - return array_key_exists($this->resource[$offset]); - } - - /** - * Get the value for a given offset. - * - * @param mixed $offset - * @return mixed - */ - public function offsetGet($offset) - { - return $this->resource[$offset]; - } - - /** - * Set the value for a given offset. - * - * @param mixed $offset - * @param mixed $value - * @return void - */ - public function offsetSet($offset, $value) - { - $this->resource[$offset] = $value; - } - - /** - * Unset the value for a given offset. - * - * @param mixed $offset - * @return void - */ - public function offsetUnset($offset) - { - unset($this->resource[$offset]); - } - - /** - * Determine if an attribute exists on the resource. - * - * @param string $key - * @return bool - */ - public function __isset($key) - { - return isset($this->resource->{$key}); - } - - /** - * Unset an attribute on the resource. - * - * @param string $key - * @return void - */ - public function __unset($key) - { - unset($this->resource->{$key}); - } - - /** - * Dynamically get properties from the underlying resource. - * - * @param string $key - * @return mixed - */ - public function __get($key) - { - return $this->resource->{$key}; - } - - /** - * Dynamically pass method calls to the underlying resource. - * - * @param string $method - * @param array $parameters - * @return mixed - */ - public function __call($method, $parameters) - { - return $this->resource->{$method}(...$parameters); - } } diff --git a/src/Illuminate/Http/Resources/ResourceResponse.php b/src/Illuminate/Http/Resources/ResourceResponse.php deleted file mode 100644 index b46b12b111d3..000000000000 --- a/src/Illuminate/Http/Resources/ResourceResponse.php +++ /dev/null @@ -1,52 +0,0 @@ -resource = $resource; - } - - /** - * Build the finished HTTP response. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Http\Response $response - * @return \Illuminate\Http\Response - */ - protected function build($request, $response) - { - return tap($response, function ($response) use ($request) { - $this->resource->withResponse($request, $response); - }); - } - - /** - * Calculate the appropriate status code for the response. - * - * @return int - */ - protected function calculateStatus() - { - return $this->resource->resource instanceof Model && - $this->resource->resource->wasRecentlyCreated ? 201 : 200; - } -} From d027bcc81a62b7f5f04f4f7fb134a34897761d82 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 11:03:09 -0500 Subject: [PATCH 33/45] wip --- src/Illuminate/Http/Resources/Json/Resource.php | 1 - src/Illuminate/Http/Resources/Json/ResourceResponse.php | 1 - src/Illuminate/Http/Resources/Resource.php | 1 - 3 files changed, 3 deletions(-) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 02ca03df7f35..83149a8f7c33 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -8,7 +8,6 @@ use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\Resources\DelegatesToResource; -use Illuminate\Http\Resources\Resource as BaseResource; class Resource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable { diff --git a/src/Illuminate/Http/Resources/Json/ResourceResponse.php b/src/Illuminate/Http/Resources/Json/ResourceResponse.php index bd4c668ede74..3931c721f431 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/ResourceResponse.php @@ -4,7 +4,6 @@ use Illuminate\Support\Collection; use Illuminate\Contracts\Support\Responsable; -use Illuminate\Http\Resources\ResourceResponse as BaseResourceResponse; class ResourceResponse implements Responsable { diff --git a/src/Illuminate/Http/Resources/Resource.php b/src/Illuminate/Http/Resources/Resource.php index c4647eaec962..1ed46c84fe9f 100644 --- a/src/Illuminate/Http/Resources/Resource.php +++ b/src/Illuminate/Http/Resources/Resource.php @@ -2,7 +2,6 @@ namespace Illuminate\Http\Resources; -use Exception; use ArrayAccess; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; From 0a8dcc5b361d87c39f997ba89804d66aaaa99667 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 11:04:57 -0500 Subject: [PATCH 34/45] wip --- .../Http/Resources/Json/PaginatedResourceResponse.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php index 55885f3c3b33..1e378a83ad39 100644 --- a/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php @@ -46,12 +46,12 @@ protected function paginationInformation($request) */ protected function paginationLinks($paginated) { - return array_merge($this->with['link'] ?? [], [ + return [ 'first' => $paginated['first_page_url'] ?? null, 'last' => $paginated['last_page_url'] ?? null, 'prev' => $paginated['prev_page_url'] ?? null, 'next' => $paginated['next_page_url'] ?? null, - ]); + ]; } /** @@ -62,12 +62,12 @@ protected function paginationLinks($paginated) */ protected function meta($paginated) { - return array_merge($this->with['meta'] ?? [], Arr::except($paginated, [ + return Arr::except($paginated, [ 'data', 'first_page_url', 'last_page_url', 'prev_page_url', 'next_page_url', - ])); + ]); } } From 7bbc6983c34459457d25851733a354bbfed47f16 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 11:18:12 -0500 Subject: [PATCH 35/45] wip --- .../Http/Resources/Json/Resource.php | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 83149a8f7c33..532fb54cc141 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -56,6 +56,31 @@ public function __construct($resource) }; } + /** + * Create new anonymous resource collection. + * + * @param mixed $resource + * @return mixed + */ + public static function collection($resource) + { + return new class($resource, get_called_class()) extends ResourceCollection { + /** + * Create a new anonymous resource collection. + * + * @param mixed $resource + * @param string $collects + * @return void + */ + public function __construct($resource, $collects) + { + $this->collects = $collects; + + parent::__construct($resource); + } + }; + } + /** * Transform the resource into a JSON array. * From 4c39b1a0619dc43027edb6fe1216ade8dec2c611 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 13:15:00 -0500 Subject: [PATCH 36/45] wip --- .../Json/PaginatedResourceResponse.php | 14 +- .../Http/Resources/Json/Resource.php | 67 ++++- .../Resources/Json/ResourceCollection.php | 6 +- .../Http/Resources/Json/ResourceResponse.php | 45 +++- src/Illuminate/Http/Resources/Resource.php | 30 --- .../Http/Resources/ResourceCollection.php | 38 --- tests/Integration/Http/ResourceTest.php | 242 ++---------------- 7 files changed, 130 insertions(+), 312 deletions(-) delete mode 100644 src/Illuminate/Http/Resources/Resource.php delete mode 100644 src/Illuminate/Http/Resources/ResourceCollection.php diff --git a/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php index 1e378a83ad39..d0ae99a06946 100644 --- a/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/PaginatedResourceResponse.php @@ -14,10 +14,16 @@ class PaginatedResourceResponse extends ResourceResponse */ public function toResponse($request) { - return tap(response()->json(array_merge_recursive( - $this->wrap($this->resource->toJson($request)), - $this->paginationInformation($request) - ), $this->calculateStatus()), function ($response) use ($request) { + return tap(response()->json( + $this->wrap( + $this->resource->resolve($request), + array_merge_recursive( + $this->paginationInformation($request), + $this->resource->with($request) + ) + ), + $this->calculateStatus() + ), function ($response) use ($request) { $this->resource->withResponse($request, $response); }); } diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 532fb54cc141..22c36ed8716c 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -4,7 +4,9 @@ use ArrayAccess; use JsonSerializable; +use Illuminate\Support\Collection; use Illuminate\Container\Container; +use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Routing\UrlRoutable; use Illuminate\Contracts\Support\Responsable; use Illuminate\Http\Resources\DelegatesToResource; @@ -56,6 +58,17 @@ public function __construct($resource) }; } + /** + * Create a new resource instance. + * + * @param dynamic $parameters + * @return static + */ + public static function make(...$parameters) + { + return new static(...$parameters); + } + /** * Create new anonymous resource collection. * @@ -82,23 +95,33 @@ public function __construct($resource, $collects) } /** - * Transform the resource into a JSON array. + * Resolve the resource to an array. * - * @param \Illuminate\Http\Request + * @param \Illuminate\Http\Request $request * @return array */ - public function toJson($request) + public function resolve($request) { - return $this->resourceToJson($request); + $data = $this->toArray($request); + + if (is_array($data)) { + return $data; + } elseif ($data instanceof Arrayable || $data instanceof Collection) { + return $data->toArray(); + } elseif ($data instanceof JsonSerializable) { + return $data->jsonSerialize(); + } + + return (array) $data; } /** - * Convert the resource into a JSON array. + * Transform the resource into an array. * - * @param \Illuminate\Http\Request $request + * @param \Illuminate\Http\Request * @return array */ - protected function resourceToJson($request) + public function toArray($request) { $values = $this->resource->toArray(); @@ -113,6 +136,17 @@ protected function resourceToJson($request) return $values; } + /** + * Get any additional data that should be returned with the resource array. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function with($request) + { + return []; + } + /** * Customize the response for a request. * @@ -126,7 +160,7 @@ public function withResponse($request, $response) } /** - * Set the string that should wrap the outer-most JSON array. + * Set the string that should wrap the outer-most resource array. * * @param string $value * @return void @@ -137,7 +171,7 @@ public static function wrap($value) } /** - * Disable wrapping of the outer-most JSON array. + * Disable wrapping of the outer-most resource array. * * @param string $value * @return void @@ -147,6 +181,19 @@ public static function withoutWrapping() static::$wrap = null; } + /** + * Transform the resource into an HTTP response. + * + * @param \Illuminate\Http\Request|null $request + * @return \Illuminate\Http\Response + */ + public function response($request = null) + { + return $this->toResponse( + $request ?: Container::getInstance()->make('request') + ); + } + /** * Create an HTTP response that represents the object. * @@ -165,6 +212,6 @@ public function toResponse($request) */ public function jsonSerialize() { - return $this->toJson(Container::getInstance()->make('request')); + return $this->toArray(Container::getInstance()->make('request')); } } diff --git a/src/Illuminate/Http/Resources/Json/ResourceCollection.php b/src/Illuminate/Http/Resources/Json/ResourceCollection.php index 21ccc6a52a72..e3f1a9b5f7fb 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceCollection.php +++ b/src/Illuminate/Http/Resources/Json/ResourceCollection.php @@ -44,11 +44,9 @@ public function __construct($resource) * @param \Illuminate\Http\Request * @return array */ - public function toJson($request) + public function toArray($request) { - return $this->resource->map(function ($item) use ($request) { - return $item->toJson($request); - })->all(); + return $this->collection->map->toArray($request)->all(); } /** diff --git a/src/Illuminate/Http/Resources/Json/ResourceResponse.php b/src/Illuminate/Http/Resources/Json/ResourceResponse.php index 3931c721f431..12b05c184601 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/ResourceResponse.php @@ -3,6 +3,7 @@ namespace Illuminate\Http\Resources\Json; use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Model; use Illuminate\Contracts\Support\Responsable; class ResourceResponse implements Responsable @@ -34,7 +35,11 @@ public function __construct($resource) public function toResponse($request) { return tap(response()->json( - $this->wrap($this->resource->toJson($request)), $this->calculateStatus() + $this->wrap( + $this->resource->resolve($request), + $this->resource->with($request) + ), + $this->calculateStatus() ), function ($response) use ($request) { $this->resource->withResponse($request, $response); }); @@ -44,17 +49,47 @@ public function toResponse($request) * Wrap the given data if necessary. * * @param array $data + * @param array $with * @return array */ - protected function wrap($data) + protected function wrap($data, $with = []) { if ($data instanceof Collection) { $data = $data->all(); } - return $this->wrapper() && ! array_key_exists($this->wrapper(), $data) - ? [$this->wrapper() => $data] - : $data; + if ($this->haveDefaultWrapperAndDataIsUnwrapped($data)) { + $data = [$this->wrapper() => $data]; + } elseif ($this->haveAdditionalInformationAndDataIsUnwrapped($data, $with)) { + $data = [($this->wrapper() ?? 'data') => $data]; + } + + return array_merge_recursive($data, $with); + } + + /** + * Determine if we have a default wrapper and the given data is unwrapped. + * + * @param array $data + * @return bool + */ + protected function haveDefaultWrapperAndDataIsUnwrapped($data) + { + return $this->wrapper() && ! array_key_exists($this->wrapper(), $data); + } + + /** + * Determine if "with" data has been added and our data is unwrapped. + * + * @param array $data + * @param array $with + * @return bool + */ + protected function haveAdditionalInformationAndDataIsUnwrapped($data, $with) + { + return ! empty($this->with) && + (! $this->wrapper() || + ! array_key_exists($this->wrapper(), $data)); } /** diff --git a/src/Illuminate/Http/Resources/Resource.php b/src/Illuminate/Http/Resources/Resource.php deleted file mode 100644 index 1ed46c84fe9f..000000000000 --- a/src/Illuminate/Http/Resources/Resource.php +++ /dev/null @@ -1,30 +0,0 @@ -resource = $resource; - } -} diff --git a/src/Illuminate/Http/Resources/ResourceCollection.php b/src/Illuminate/Http/Resources/ResourceCollection.php deleted file mode 100644 index c8110d59a8ec..000000000000 --- a/src/Illuminate/Http/Resources/ResourceCollection.php +++ /dev/null @@ -1,38 +0,0 @@ -resource = $this->collectResource($resource); - } -} diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index c25459a15cdc..3977eb3fd329 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -3,16 +3,14 @@ namespace Illuminate\Tests\Integration\Http; use JsonSerializable; -use Illuminate\Http\Resource; use Orchestra\Testbench\TestCase; use Illuminate\Support\Facades\Route; -use Illuminate\Http\ResourceCollection; use Illuminate\Database\Eloquent\Model; +use Illuminate\Http\Resources\Json\Resource; use Illuminate\Database\Eloquent\Collection; use Illuminate\Contracts\Support\Responsable; -use Illuminate\Http\Middleware\CastToResource; use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Contracts\Database\CastsToResource; +use Illuminate\Http\Resources\Json\ResourceCollection; /** * @group integration @@ -68,56 +66,6 @@ public function test_named_routes_are_url_routable() $this->assertEquals('http://localhost/post/5', $response->original); } - public function test_models_may_be_cast_by_middleware() - { - Route::get('/', function () { - return new Post([ - 'id' => 5, - 'title' => 'Test Title', - ]); - })->middleware(CastToResource::class); - - $response = $this->withoutExceptionHandling()->get( - '/', ['Accept' => 'application/json'] - ); - - $response->assertStatus(200); - - $response->assertJson([ - 'data' => [ - 'id' => 5, - 'title' => 'Test Title', - 'custom' => true, - ] - ]); - } - - public function test_collections_are_cast_by_middleware() - { - Route::get('/', function () { - return collect([new Post([ - 'id' => 5, - 'title' => 'Test Title', - ])]); - })->middleware(CastToResource::class); - - $response = $this->withoutExceptionHandling()->get( - '/', ['Accept' => 'application/json'] - ); - - $response->assertStatus(200); - - $response->assertJson([ - 'data' => [ - [ - 'id' => 5, - 'title' => 'Test Title', - 'custom' => true, - ], - ], - ]); - } - public function test_resources_may_be_serializable() { Route::get('/', function () { @@ -136,7 +84,6 @@ public function test_resources_may_be_serializable() $response->assertJson([ 'data' => [ 'id' => 5, - 'title' => 'Test Title', ] ]); } @@ -161,10 +108,10 @@ public function test_resources_may_customize_responses() public function test_resources_may_customize_extra_data() { Route::get('/', function () { - return (new PostResource(new Post([ + return new PostResourceWithExtraData(new Post([ 'id' => 5, 'title' => 'Test Title', - ])))->json()->with(['foo' => 'bar']); + ])); }); $response = $this->withoutExceptionHandling()->get( @@ -186,7 +133,7 @@ public function test_custom_headers_may_be_set_on_responses() return (new PostResource(new Post([ 'id' => 5, 'title' => 'Test Title', - ])))->status(202)->header('X-Custom', 'True')->json(); + ])))->response()->setStatusCode(202)->header('X-Custom', 'True'); }); $response = $this->withoutExceptionHandling()->get( @@ -197,60 +144,6 @@ public function test_custom_headers_may_be_set_on_responses() $response->assertHeader('X-Custom', 'True'); } - public function test_custom_headers_may_be_set_on_responses_using_callback() - { - Route::get('/', function () { - return (new PostResource(new Post([ - 'id' => 5, - 'title' => 'Test Title', - ])))->using(function ($request, $response) { - $response->header('X-Custom', 'True'); - })->json(); - }); - - $response = $this->withoutExceptionHandling()->get( - '/', ['Accept' => 'application/json'] - ); - - $response->assertHeader('X-Custom', 'True'); - } - - public function test_resources_may_be_converted_to_html() - { - Route::get('/', function () { - return new PostResource(new Post([ - 'id' => 5, - 'title' => 'Test Title', - ])); - }); - - $response = $this->withoutExceptionHandling()->get( - '/', ['Accept' => 'text/html'] - ); - - $response->assertStatus(200); - - $this->assertEquals('html', $response->original); - } - - public function test_resources_may_be_converted_to_css() - { - Route::get('/', function () { - return new PostResource(new Post([ - 'id' => 5, - 'title' => 'Test Title', - ])); - }); - - $response = $this->withoutExceptionHandling()->get( - '/', ['Accept' => 'text/css'] - ); - - $response->assertStatus(200); - - $this->assertEquals('css', $response->original); - } - public function test_resources_may_receive_proper_status_code_for_fresh_models() { Route::get('/', function () { @@ -338,47 +231,6 @@ public function test_paginators_receive_links() ]); } - public function test_paginators_are_cast_by_middleware() - { - Route::get('/', function () { - return new LengthAwarePaginator( - collect([new Post(['id' => 5, 'title' => 'Test Title'])]), - 10, 15, 1 - ); - })->middleware(CastToResource::class); - - $response = $this->withoutExceptionHandling()->get( - '/', ['Accept' => 'application/json'] - ); - - $response->assertStatus(200); - - $response->assertJson([ - 'data' => [ - [ - 'id' => 5, - 'title' => 'Test Title', - 'custom' => true, - ], - ], - 'links' => [ - 'first' => '/?page=1', - 'last' => '/?page=1', - 'prev' => null, - 'next' => null, - ], - 'meta' => [ - 'current_page' => 1, - 'from' => 1, - 'last_page' => 1, - 'path' => '/', - 'per_page' => 15, - 'to' => 1, - 'total' => 10, - ], - ]); - } - public function test_to_json_may_be_left_off_of_collection() { Route::get('/', function () { @@ -386,7 +238,7 @@ public function test_to_json_may_be_left_off_of_collection() collect([new Post(['id' => 5, 'title' => 'Test Title'])]), 10, 15, 1 )); - })->middleware(CastToResource::class); + }); $response = $this->withoutExceptionHandling()->get( '/', ['Accept' => 'application/json'] @@ -442,38 +294,9 @@ public function test_to_json_may_be_left_off_of_single_resource() ] ]); } - - public function test_resource_types_may_be_macroed() - { - Resource::format('xml', function ($resource) { - $this->assertInstanceOf(PostResource::class, $resource); - - return new class implements Responsable { - public function toResponse($request) - { - return 'xml response'; - } - }; - }); - - Route::get('/', function () { - return new PostResource(new Post([ - 'id' => 5, - 'title' => 'Test Title', - ])); - }); - - $response = $this->withoutExceptionHandling()->get( - '/', ['Accept' => 'application/xml'] - ); - - $response->assertStatus(200); - - $this->assertEquals('xml response', $response->original); - } } -class Post extends Model implements CastsToResource +class Post extends Model { /** * The attributes that aren't mass assignable. @@ -481,58 +304,33 @@ class Post extends Model implements CastsToResource * @var array */ protected $guarded = []; - - /** - * Cast the given model into a resource. - * - * @param \Illuminate\Http\Request $request - * @param mixed $model - */ - public static function castToResource($request, $model) - { - return new PostResource($model); - } - - /** - * Cast the given collection into a resource. - * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Support\Collection $collection - */ - public static function castCollectionToResource($request, $collection) - { - return new PostCollectionResource($collection); - } } class PostResource extends Resource { - public function toHtml($request) - { - return 'html'; - } - - public function toCss($request) - { - return 'css'; - } - - public function toJson($request) + public function toArray($request) { return ['id' => $this->id, 'title' => $this->title, 'custom' => true]; } - public function withJsonResponse($request, $response) + public function withResponse($request, $response) { $response->header('X-Resource', 'True'); } } +class PostResourceWithExtraData extends PostResource +{ + public function with($request) + { + return ['foo' => 'bar']; + } +} class SerializablePostResource extends Resource { - public function toJson($request) + public function toArray($request) { return new JsonSerializableResource($this); } @@ -543,7 +341,7 @@ class PostCollectionResource extends ResourceCollection { public $collects = PostResource::class; - public function toJson($request) + public function toArray($request) { return ['data' => $this->collection]; } @@ -575,6 +373,8 @@ public function __construct($resource) public function jsonSerialize() { - return $this->resource->toArray(); + return [ + 'id' => $this->resource->id, + ]; } } From 51169338e7ddfd2af93ca5a68c406adc8072ec91 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 13:17:44 -0500 Subject: [PATCH 37/45] wip --- src/Illuminate/Http/Resources/Json/ResourceResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Resources/Json/ResourceResponse.php b/src/Illuminate/Http/Resources/Json/ResourceResponse.php index 12b05c184601..b01b1d2aa0f6 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceResponse.php +++ b/src/Illuminate/Http/Resources/Json/ResourceResponse.php @@ -87,7 +87,7 @@ protected function haveDefaultWrapperAndDataIsUnwrapped($data) */ protected function haveAdditionalInformationAndDataIsUnwrapped($data, $with) { - return ! empty($this->with) && + return ! empty($with) && (! $this->wrapper() || ! array_key_exists($this->wrapper(), $data)); } From 9500417e1dffae143dfc09e6408d71f4a25b94bd Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 13:28:15 -0500 Subject: [PATCH 38/45] add method --- .../Http/Resources/Json/Resource.php | 22 ++++++++++++++++++- tests/Integration/Http/ResourceTest.php | 22 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 22c36ed8716c..98b0f99390f5 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -36,6 +36,13 @@ class Resource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutabl */ public $visible = []; + /** + * The additional data that should be added to the top-level resource array. + * + * @var array + */ + public $with = []; + /** * The "data" wrapper that should be applied. * @@ -136,6 +143,19 @@ public function toArray($request) return $values; } + /** + * Merge the given data into the resource array. + * + * @param array $data + * @return $this + */ + public function merge(array $data) + { + $this->with = $data; + + return $this; + } + /** * Get any additional data that should be returned with the resource array. * @@ -144,7 +164,7 @@ public function toArray($request) */ public function with($request) { - return []; + return $this->with; } /** diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index 3977eb3fd329..ccb38def400e 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -127,6 +127,28 @@ public function test_resources_may_customize_extra_data() ]); } + public function test_resources_may_customize_adhoc_extra_data() + { + Route::get('/', function () { + return PostResource::make(new Post([ + 'id' => 5, + 'title' => 'Test Title', + ]))->merge(['foo' => 'bar']); + }); + + $response = $this->withoutExceptionHandling()->get( + '/', ['Accept' => 'application/json'] + ); + + $response->assertJson([ + 'data' => [ + 'id' => 5, + 'title' => 'Test Title', + ], + 'foo' => 'bar', + ]); + } + public function test_custom_headers_may_be_set_on_responses() { Route::get('/', function () { From 7f0bfcfc01f5a8e8e837c11645a305cc0064a72e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 13:28:44 -0500 Subject: [PATCH 39/45] wip --- src/Illuminate/Http/Resources/Json/Resource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 98b0f99390f5..95a2b1bbb063 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -144,7 +144,7 @@ public function toArray($request) } /** - * Merge the given data into the resource array. + * Merge additional data into the resource array. * * @param array $data * @return $this From d26149d51f37b1bbc6da98d0c18db3ac8df0cbed Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 13:42:25 -0500 Subject: [PATCH 40/45] wip --- .../Console/stubs/resource-collection.stub | 8 ++++---- .../Foundation/Console/stubs/resource.stub | 8 ++++---- .../Http/Resources/CollectsResources.php | 18 ++++++++++++++++++ 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Foundation/Console/stubs/resource-collection.stub b/src/Illuminate/Foundation/Console/stubs/resource-collection.stub index 7365bfc767dd..6f5115620ff8 100644 --- a/src/Illuminate/Foundation/Console/stubs/resource-collection.stub +++ b/src/Illuminate/Foundation/Console/stubs/resource-collection.stub @@ -2,18 +2,18 @@ namespace DummyNamespace; -use Illuminate\Http\ResourceCollection; +use Illuminate\Http\Resources\Json\ResourceCollection; class DummyClass extends ResourceCollection { /** - * Transform the resource collection into a JSON array. + * Transform the resource collection into an array. * * @param \Illuminate\Http\Request * @return array */ - public function toJson($request) + public function toArray($request) { - return parent::toJson($request); + return parent::toArray($request); } } diff --git a/src/Illuminate/Foundation/Console/stubs/resource.stub b/src/Illuminate/Foundation/Console/stubs/resource.stub index aed0c1db4186..615edb285b5d 100644 --- a/src/Illuminate/Foundation/Console/stubs/resource.stub +++ b/src/Illuminate/Foundation/Console/stubs/resource.stub @@ -2,7 +2,7 @@ namespace DummyNamespace; -use Illuminate\Http\Resource; +use Illuminate\Http\Resources\Json\Resource; class DummyClass extends Resource { @@ -14,13 +14,13 @@ class DummyClass extends Resource public $hidden = []; /** - * Transform the resource into a JSON array. + * Transform the resource into an array. * * @param \Illuminate\Http\Request * @return array */ - public function toJson($request) + public function toArray($request) { - return parent::toJson($request); + return parent::toArray($request); } } diff --git a/src/Illuminate/Http/Resources/CollectsResources.php b/src/Illuminate/Http/Resources/CollectsResources.php index a394948b0772..0b38f6f9b64d 100644 --- a/src/Illuminate/Http/Resources/CollectsResources.php +++ b/src/Illuminate/Http/Resources/CollectsResources.php @@ -4,7 +4,9 @@ use Illuminate\Support\Str; use Illuminate\Support\Collection; +use Illuminate\Database\Eloquent\Model; use Illuminate\Pagination\AbstractPaginator; +use Illuminate\Database\Eloquent\Collection as EloquentCollection; trait CollectsResources { @@ -18,11 +20,27 @@ protected function collectResource($resource) { $this->collection = $resource->mapInto($this->collects()); + if ($this->containsModels()) { + $this->collection = new EloquentCollection($this->collection->all()); + } + return $resource instanceof AbstractPaginator ? $resource->setCollection($this->collection) : $this->collection; } + /** + * Determine if the collection contains models. + * + * @return bool + */ + protected function containsModels() + { + return $this->collection->contains(function ($item) { + return $item->resource instanceof Model; + }); + } + /** * Get the resource that this resource collects. * From 92c8bcdd8d841e12079cd4128c3ff7481bee4cd7 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 14:00:45 -0500 Subject: [PATCH 41/45] wip --- .../Http/Resources/CollectsResources.php | 16 ------------ .../Http/Resources/Json/Resource.php | 26 +++++++++++++++++++ .../Resources/Json/ResourceCollection.php | 26 +++++++++++++++++++ 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/Illuminate/Http/Resources/CollectsResources.php b/src/Illuminate/Http/Resources/CollectsResources.php index 0b38f6f9b64d..ae69e5616744 100644 --- a/src/Illuminate/Http/Resources/CollectsResources.php +++ b/src/Illuminate/Http/Resources/CollectsResources.php @@ -20,27 +20,11 @@ protected function collectResource($resource) { $this->collection = $resource->mapInto($this->collects()); - if ($this->containsModels()) { - $this->collection = new EloquentCollection($this->collection->all()); - } - return $resource instanceof AbstractPaginator ? $resource->setCollection($this->collection) : $this->collection; } - /** - * Determine if the collection contains models. - * - * @return bool - */ - protected function containsModels() - { - return $this->collection->contains(function ($item) { - return $item->resource instanceof Model; - }); - } - /** * Get the resource that this resource collects. * diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 95a2b1bbb063..12388883e3e0 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -101,6 +101,32 @@ public function __construct($resource, $collects) }; } + /** + * Eager load relations on the resource. + * + * @param array|string $relations + * @return $this + */ + public function load($relations) + { + $this->resource->load($relations); + + return $this; + } + + /** + * Eager load relations on the resource if they are not already eager loaded. + * + * @param array|string $relations + * @return $this + */ + public function loadMissing($relations) + { + $this->resource->load($relations); + + return $this; + } + /** * Resolve the resource to an array. * diff --git a/src/Illuminate/Http/Resources/Json/ResourceCollection.php b/src/Illuminate/Http/Resources/Json/ResourceCollection.php index e3f1a9b5f7fb..e343ca3ffb32 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceCollection.php +++ b/src/Illuminate/Http/Resources/Json/ResourceCollection.php @@ -6,6 +6,7 @@ use Illuminate\Support\Collection; use Illuminate\Pagination\AbstractPaginator; use Illuminate\Http\Resources\CollectsResources; +use Illuminate\Database\Eloquent\Collection as EloquentCollection; class ResourceCollection extends Resource implements IteratorAggregate { @@ -38,6 +39,31 @@ public function __construct($resource) $this->resource = $this->collectResource($resource); } + /** + * Eager load relations on the resource. + * + * @param array|string $relations + * @return $this + */ + public function load($relations) + { + $this->collection = (new EloquentCollection($this->collection->all())) + ->load($relations)->toBase(); + + return $this; + } + + /** + * Eager load relations on the resource if they are not already eager loaded. + * + * @param array|string $relations + * @return $this + */ + public function loadMissing($relations) + { + return $this->load($relations); + } + /** * Transform the resource into a JSON array. * From e69667da4283ac8e47733da37e639d3c95bf4b0d Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 14:08:06 -0500 Subject: [PATCH 42/45] wip --- src/Illuminate/Http/Resources/Json/Resource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 12388883e3e0..10aa9361e049 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -122,7 +122,7 @@ public function load($relations) */ public function loadMissing($relations) { - $this->resource->load($relations); + $this->resource->loadMissing($relations); return $this; } From 8526b5ddf2fe032df86b6ebcd2bb4dcffc61d5d7 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 15:05:08 -0500 Subject: [PATCH 43/45] remove visible and hidden --- .../Http/Resources/Json/Resource.php | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 10aa9361e049..89d1252c3a93 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -22,20 +22,6 @@ class Resource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutabl */ public $resource; - /** - * The attributes that should be hidden when serialized. - * - * @var array - */ - public $hidden = []; - - /** - * The attributes that should be visible when serialized. - * - * @var array - */ - public $visible = []; - /** * The additional data that should be added to the top-level resource array. * @@ -156,17 +142,7 @@ public function resolve($request) */ public function toArray($request) { - $values = $this->resource->toArray(); - - if (count($this->visible) > 0) { - $values = array_intersect_key($values, array_flip($this->visible)); - } - - if (count($this->hidden) > 0) { - $values = array_diff_key($values, array_flip($this->hidden)); - } - - return $values; + return $this->resource->toArray(); } /** From d7108c35ba5353a451665d75d29e931a14766627 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 15:05:41 -0500 Subject: [PATCH 44/45] tweak stub --- src/Illuminate/Foundation/Console/stubs/resource.stub | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Illuminate/Foundation/Console/stubs/resource.stub b/src/Illuminate/Foundation/Console/stubs/resource.stub index 615edb285b5d..d09a98a7efed 100644 --- a/src/Illuminate/Foundation/Console/stubs/resource.stub +++ b/src/Illuminate/Foundation/Console/stubs/resource.stub @@ -6,13 +6,6 @@ use Illuminate\Http\Resources\Json\Resource; class DummyClass extends Resource { - /** - * The attributes that should be hidden when serialized. - * - * @var array - */ - public $hidden = []; - /** * Transform the resource into an array. * From 7eaa192393f4a7e7caab6b57d1d6fa9578c641db Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2017 15:06:52 -0500 Subject: [PATCH 45/45] remove loading --- .../Http/Resources/Json/Resource.php | 26 ------------------- .../Resources/Json/ResourceCollection.php | 25 ------------------ 2 files changed, 51 deletions(-) diff --git a/src/Illuminate/Http/Resources/Json/Resource.php b/src/Illuminate/Http/Resources/Json/Resource.php index 89d1252c3a93..7ca1a757c629 100644 --- a/src/Illuminate/Http/Resources/Json/Resource.php +++ b/src/Illuminate/Http/Resources/Json/Resource.php @@ -87,32 +87,6 @@ public function __construct($resource, $collects) }; } - /** - * Eager load relations on the resource. - * - * @param array|string $relations - * @return $this - */ - public function load($relations) - { - $this->resource->load($relations); - - return $this; - } - - /** - * Eager load relations on the resource if they are not already eager loaded. - * - * @param array|string $relations - * @return $this - */ - public function loadMissing($relations) - { - $this->resource->loadMissing($relations); - - return $this; - } - /** * Resolve the resource to an array. * diff --git a/src/Illuminate/Http/Resources/Json/ResourceCollection.php b/src/Illuminate/Http/Resources/Json/ResourceCollection.php index e343ca3ffb32..b65a5cfdb972 100644 --- a/src/Illuminate/Http/Resources/Json/ResourceCollection.php +++ b/src/Illuminate/Http/Resources/Json/ResourceCollection.php @@ -39,31 +39,6 @@ public function __construct($resource) $this->resource = $this->collectResource($resource); } - /** - * Eager load relations on the resource. - * - * @param array|string $relations - * @return $this - */ - public function load($relations) - { - $this->collection = (new EloquentCollection($this->collection->all())) - ->load($relations)->toBase(); - - return $this; - } - - /** - * Eager load relations on the resource if they are not already eager loaded. - * - * @param array|string $relations - * @return $this - */ - public function loadMissing($relations) - { - return $this->load($relations); - } - /** * Transform the resource into a JSON array. *