From 38a056e92fb19dd851f952ac466137cb202feea7 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Tue, 24 Dec 2019 13:47:37 +0200 Subject: [PATCH 01/21] Add fields and JSON:API support for post and list --- routes/api.php | 1 + src/Commands/stubs/repository.stub | 18 +- src/Controllers/RestController.php | 9 +- src/Controllers/RestResponse.php | 225 ++++++++++++++++-- src/Fields/BaseField.php | 12 + src/Fields/Field.php | 136 +++++++++++ src/Fields/OrganicField.php | 11 + src/Fields/RulesTrait.php | 78 ++++++ .../Controllers/RepositoryIndexController.php | 2 +- .../Controllers/RepositoryStoreController.php | 63 +++++ .../Requests/InteractWithRepositories.php | 14 ++ ...Request.php => RepositoryIndexRequest.php} | 2 +- src/Http/Requests/RepositoryStoreRequest.php | 10 + src/Repositories/Repository.php | 17 +- src/Repositories/RepositoryFillFields.php | 70 ++++++ src/Repositories/ValidatingTrait.php | 86 +++++++ src/Traits/InteractWithSearch.php | 23 +- src/Traits/PerformsRequestValidation.php | 12 + .../RepositoryIndexControllerTest.php | 3 - .../RepositoryStoreControllerTest.php | 51 ++++ tests/Fixtures/PostRepository.php | 19 ++ tests/Fixtures/UserRepository.php | 6 + tests/RestControllerTest.php | 2 +- 23 files changed, 827 insertions(+), 43 deletions(-) create mode 100644 src/Fields/BaseField.php create mode 100644 src/Fields/Field.php create mode 100644 src/Fields/OrganicField.php create mode 100644 src/Fields/RulesTrait.php create mode 100644 src/Http/Controllers/RepositoryStoreController.php rename src/Http/Requests/{ResourceIndexRequest.php => RepositoryIndexRequest.php} (71%) create mode 100644 src/Http/Requests/RepositoryStoreRequest.php create mode 100644 src/Repositories/RepositoryFillFields.php create mode 100644 src/Repositories/ValidatingTrait.php create mode 100644 src/Traits/PerformsRequestValidation.php create mode 100644 tests/Controllers/RepositoryStoreControllerTest.php diff --git a/routes/api.php b/routes/api.php index 8a54b7509..ee8c38c30 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,3 +1,4 @@ storingRules('required')->messages([ + 'required' => 'This field is required bro.', + ]), + ]; + + } + } diff --git a/src/Controllers/RestController.php b/src/Controllers/RestController.php index 6f00bd83b..ec8110d76 100644 --- a/src/Controllers/RestController.php +++ b/src/Controllers/RestController.php @@ -22,6 +22,7 @@ use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Http\JsonResponse; use Illuminate\Routing\Controller as BaseController; +use Illuminate\Support\Arr; use Illuminate\Support\Facades\Password; /** @@ -153,9 +154,11 @@ public function search($modelClass, $filters = []) $items = $paginator->getCollection()->map->serializeForIndex($this->request()); } - return array_merge($paginator->toArray(), [ - 'data' => $items, - ]); + return [ + 'meta' => Arr::except($paginator->toArray(), ['data', 'next_page_url', 'last_page_url', 'first_page_url', 'prev_page_url', 'path']), + 'links' => Arr::only($paginator->toArray(), ['next_page_url', 'last_page_url', 'first_page_url', 'prev_page_url', 'path']), + 'data' => $items + ]; } /** diff --git a/src/Controllers/RestResponse.php b/src/Controllers/RestResponse.php index e5020a3dc..f5283f0b7 100644 --- a/src/Controllers/RestResponse.php +++ b/src/Controllers/RestResponse.php @@ -2,6 +2,8 @@ namespace Binaryk\LaravelRestify\Controllers; +use Binaryk\LaravelRestify\Contracts\RestifySearchable; +use Binaryk\LaravelRestify\Repositories\Repository; use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Http\JsonResponse; @@ -32,6 +34,8 @@ class RestResponse 'file', 'stack', 'data', + 'meta', + 'links', 'errors', ]; @@ -63,23 +67,42 @@ class RestResponse protected $line; /** - * Attributes to be appended to response at root level. + * The value of the attributes key MUST be an object (an “attributes object”). + * Members of the attributes object (“attributes”) represent information + * about the resource object in which it’s defined. * * @var array */ - protected $attributes = []; + protected $attributes; + + /** + * Where specified, a meta member can be used to include non-standard meta-information. + * The value of each meta member MUST be an object (a “meta object”). + * @var array + */ + protected $meta; + + /** + * A links object containing links related to the resource. + * @var + */ + protected $links; + /** * @var string */ private $file; + /** * @var string|null */ private $stack; + /** * @var array|null */ - private $errors = []; + private $errors; + /** * @var array|null */ @@ -90,11 +113,27 @@ class RestResponse */ protected $headers; + /** + * @var string + */ + protected $type; + + /** + * Key of the newly created resource + * @var + */ + protected $id; + /** + * Model related entities + * @var + */ + protected $relationships; + /** * RestResponse constructor. - * @param mixed $content - * @param int $status - * @param array $headers + * @param mixed $content + * @param int $status + * @param array $headers */ public function __construct($content = null, $status = 200, array $headers = []) { @@ -106,7 +145,7 @@ public function __construct($content = null, $status = 200, array $headers = []) /** * Set response data. * - * @param mixed $data + * @param mixed $data * @return $this|mixed */ public function data($data = null) @@ -123,7 +162,7 @@ public function data($data = null) /** * Set response errors. * - * @param array $errors + * @param array $errors * @return $this|null */ public function errors(array $errors = null) @@ -140,12 +179,12 @@ public function errors(array $errors = null) /** * Add error to response errors. * - * @param mixed $message + * @param mixed $message * @return $this */ public function addError($message) { - if (! isset($this->errors)) { + if ( ! isset($this->errors)) { $this->errors = []; } @@ -157,7 +196,7 @@ public function addError($message) /** * Set response Http code. * - * @param int $code + * @param int $code * @return $this|int */ public function code($code = self::REST_RESPONSE_SUCCESS_CODE) @@ -174,7 +213,7 @@ public function code($code = self::REST_RESPONSE_SUCCESS_CODE) /** * Set response Http code. * - * @param int $line + * @param int $line * @return $this|int */ public function line($line = null) @@ -189,7 +228,7 @@ public function line($line = null) } /** - * @param string $file + * @param string $file * @return $this|int */ public function file(string $file = null) @@ -204,7 +243,7 @@ public function file(string $file = null) } /** - * @param string|null $stack + * @param string|null $stack * @return $this|int */ public function stack(string $stack = null) @@ -221,7 +260,7 @@ public function stack(string $stack = null) /** * Magic to get response code constants. * - * @param string $key + * @param string $key * @return mixed|null */ public function __get($key) @@ -230,7 +269,7 @@ public function __get($key) return $this->$key; } - $code = 'static::REST_RESPONSE_'.strtoupper($key).'_CODE'; + $code = 'static::REST_RESPONSE_' . strtoupper($key) . '_CODE'; return defined($code) ? constant($code) : null; } @@ -245,7 +284,7 @@ public function __get($key) */ public function __call($func, $args) { - $code = 'static::REST_RESPONSE_'.strtoupper($func).'_CODE'; + $code = 'static::REST_RESPONSE_' . strtoupper($func) . '_CODE'; if (defined($code)) { return $this->code(constant($code)); @@ -257,15 +296,16 @@ public function __call($func, $args) /** * Build a new response with our response data. * - * @param mixed $response + * @param mixed $response * * @return JsonResponse * @throws \Illuminate\Contracts\Container\BindingResolutionException */ public function respond($response = null) { - if (! func_num_args()) { + if ( ! func_num_args()) { $response = new \stdClass(); + $response->data = new \stdClass(); foreach ($this->fillable() as $property) { if (isset($this->{$property})) { @@ -273,24 +313,75 @@ public function respond($response = null) } } - foreach ($this->attributes as $attribute => $value) { - $response->{$attribute} = $value; + //according with https://jsonapi.org/format/#document-top-level these fields should be in data: + foreach (['attributes', 'relationships', 'type', 'id'] as $item) { + if (isset($this->{$item})) { + $response->data->{$item} = $this->{$item}; + } } } - return $this->response()->json($response, is_int($this->code()) ? $this->code() : self::REST_RESPONSE_SUCCESS_CODE, $this->headers); + return $this->response()->json(static::beforeRespond($response), is_int($this->code()) ? $this->code() : self::REST_RESPONSE_SUCCESS_CODE, $this->headers); } /** - * Set a root attribute on response object. + * Set a root meta on response object. * * @param $name * @param $value * @return $this */ - public function setAttribute($name, $value) + public function setMeta($name, $value) + { + $this->meta[$name] = $value; + + return $this; + } + + /** + * Set a root meta on response object. + * + * @param $meta + * @return $this + */ + public function meta($meta) { - $this->attributes[$name] = $value; + if (func_num_args()) { + $this->meta = ($meta instanceof Arrayable) ? $meta->toArray() : $meta; + + return $this; + } + + return $this; + } + + /** + * Set a root meta on response object. + * + * @param $links + * @return $this + */ + public function links($links) + { + if (func_num_args()) { + $this->links = ($links instanceof Arrayable) ? $links->toArray() : $links; + + return $this; + } + + return $this; + } + + /** + * Set a root link on response object. + * + * @param $name + * @param $value + * @return $this + */ + public function setLink($name, $value) + { + $this->links[$name] = $value; return $this; } @@ -302,7 +393,7 @@ public function setAttribute($name, $value) */ public function message($message) { - return $this->setAttribute('message', $message); + return $this->setMeta('message', $message); } /** @@ -316,6 +407,19 @@ public function getAttribute($name) return $this->attributes[$name]; } + /** + * Set attributes at root level + * + * @param array $attributes + * @return mixed + */ + public function setAttributes(array $attributes) + { + $this->attributes = $attributes; + + return $this; + } + /** * @return array */ @@ -332,4 +436,73 @@ protected function response() { return app()->make(ResponseFactory::class); } + + /** + * @param $name + * @param $value + * @return RestResponse + */ + public function header($name, $value) + { + $this->headers[$name] = $value; + return $this; + } + + /** + * @param $type + * @return $this + */ + public function type($type) + { + $this->type = $type; + return $this; + } + + /** + * Useful when newly created repository, will prepare the response according + * with JSON:API https://jsonapi.org/format/#document-resource-object-fields + * + * @param Repository $repository + * @param bool $withRelations + * @return $this + */ + public function forRepository(Repository $repository, $withRelations = false) + { + $model = $repository->model(); + if (is_null($model->getKey())) { + return $this; + } + $this->type($repository::uriKey()); + $this->setAttributes($model->attributesToArray()); + $this->id = $model->getKey(); + + if ($withRelations && $model instanceof RestifySearchable && $model::getWiths()) { + foreach ($model::getWiths() as $k => $relation) { + if ($model->relationLoaded($relation)) { + $this->relationships[$relation] = $model->{$relation}->get(); + } + } + } + + return $this; + } + + public static function beforeRespond($response) + { + //The members data and errors MUST NOT coexist in the same document. - https://jsonapi.org/format/#introduction + if (isset($response->errors)) { + unset($response->data); + + return $response; + } + + if (isset($response->data)) { + unset($response->errors); + + return $response; + } + + + return $response; + } } diff --git a/src/Fields/BaseField.php b/src/Fields/BaseField.php new file mode 100644 index 000000000..5d6be957b --- /dev/null +++ b/src/Fields/BaseField.php @@ -0,0 +1,12 @@ + + */ +abstract class BaseField +{ + +} diff --git a/src/Fields/Field.php b/src/Fields/Field.php new file mode 100644 index 000000000..073c5d298 --- /dev/null +++ b/src/Fields/Field.php @@ -0,0 +1,136 @@ + + */ +class Field extends OrganicField implements JsonSerializable +{ + use RulesTrait; + + /** + * Column name of the field + * @var string + */ + public $attribute; + + /** + * Callback called when the value is filled, this callback will do not override the fill action + * @var Closure + */ + public $storeCallback; + + /** + * Callback called when trying to fill this attribute, this callback will override the fill action, so make + * sure you assign the attribute to the model over this callback + * + * @var Closure + */ + public $fillCallback; + + /** + * Create a new field. + * + * @param string|callable|null $attribute + * @param callable|null $resolveCallback + */ + public function __construct($attribute, callable $resolveCallback = null) + { + $this->attribute = $attribute; + } + + /** + * Create a new element. + * + * @return static + */ + public static function fire(...$arguments) + { + return new static(...$arguments); + } + + /** + * @inheritDoc + */ + public function jsonSerialize() + { + return []; + } + + /** + * Callback called when the value is filled, this callback will do not override the fill action. If fillCallback is defined + * this will do not be called + * + * @param Closure $callback + * @return Field + */ + public function storeCallback(Closure $callback) + { + $this->storeCallback = $callback; + + return $this; + } + + /** + * Callback called when trying to fill this attribute, this callback will override the fill action, so make + * sure you assign the attribute to the model over this callback + * + * @param Closure $callback + * @return $this + */ + public function fillCallback(Closure $callback) + { + $this->fillCallback = $callback; + return $this; + } + + /** + * Fill attribute with value from the request or delegate this action to the user defined callback + * + * @param RestifyRequest $request + * @param $model + * @return mixed|void + */ + public function fillAttribute(RestifyRequest $request, $model) + { + if (isset($this->fillCallback)) { + return call_user_func( + $this->fillCallback, $request, $model, $this->attribute + ); + } + + return $this->fillAttributeFromRequest( + $request, $model, $this->attribute + ); + } + + /** + * Fill the model with value from the request + * + * @param RestifyRequest $request + * @param $model + * @param $attribute + */ + protected function fillAttributeFromRequest(RestifyRequest $request, $model, $attribute) + { + if ($request->exists($attribute)) { + $value = $request[$attribute]; + + $model->{$attribute} = is_callable($this->storeCallback) ? call_user_func($this->storeCallback, $value, $request, $model) : $value; + } + } + + /** + * @return callable|string|null + */ + public function getAttribute() + { + return $this->attribute; + } +} diff --git a/src/Fields/OrganicField.php b/src/Fields/OrganicField.php new file mode 100644 index 000000000..0cb7c7614 --- /dev/null +++ b/src/Fields/OrganicField.php @@ -0,0 +1,11 @@ + + */ +abstract class OrganicField extends BaseField +{ +} diff --git a/src/Fields/RulesTrait.php b/src/Fields/RulesTrait.php new file mode 100644 index 000000000..9631f1537 --- /dev/null +++ b/src/Fields/RulesTrait.php @@ -0,0 +1,78 @@ + + */ +trait RulesTrait +{ + /** + * Rules for applied when store + * + * @var array + */ + public $storingRules = []; + + /** + * Rules for applied when store and update + * + * @var array + */ + public $rules = []; + + + /** + * @var array + */ + public $messages = []; + + /** + * Validation rules for store + * @param callable|array|string $rules + * @return RulesTrait + */ + public function storingRules($rules) + { + $this->storingRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; + + return $this; + } + + /** + * Validation rules for store + * @param callable|array|string $rules + * @return RulesTrait + */ + public function rules($rules) + { + $this->rules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; + + return $this; + } + + /** + * Validation messages + * + * @param array $messages + * @return RulesTrait + */ + public function messages(array $messages) + { + $this->messages = $messages; + return $this; + } + + /** + * Validation rules for storing + * + * @return array + */ + public function getStoringRules() + { + return array_merge($this->rules, $this->storingRules); + } +} diff --git a/src/Http/Controllers/RepositoryIndexController.php b/src/Http/Controllers/RepositoryIndexController.php index 9303cf7ea..60b246ce4 100644 --- a/src/Http/Controllers/RepositoryIndexController.php +++ b/src/Http/Controllers/RepositoryIndexController.php @@ -20,6 +20,6 @@ public function handle(RestifyRequest $request) { $data = $this->search($request->newRepository()); - return $this->respond($data); + return $this->response()->data($data['data'])->meta($data['meta'])->links($data['links'])->respond(); } } diff --git a/src/Http/Controllers/RepositoryStoreController.php b/src/Http/Controllers/RepositoryStoreController.php new file mode 100644 index 000000000..92339313a --- /dev/null +++ b/src/Http/Controllers/RepositoryStoreController.php @@ -0,0 +1,63 @@ + + */ +class RepositoryStoreController extends RepositoryController +{ + /** + * @param RepositoryStoreRequest $request + * @return JsonResponse + * @throws BindingResolutionException + * @throws EntityNotFoundException + * @throws UnauthorizedException + * @throws AuthorizationException + * @throws Throwable + */ + public function handle(RepositoryStoreRequest $request) + { + /** + * @var Repository $repository + */ + $repository = $request->repository(); + + $repository::authorizeToCreate($request); + + $instance = $request->newRepository(); + + $validator = $repository::validatorForStoring($request); + + if ($validator->fails()) { + return $this->response()->invalid()->errors($validator->errors()->toArray())->respond(); + } + + $model = DB::transaction(function () use ($request, $repository) { + [$model] = $repository::fillWhenStore( + $request, $repository::newModel() + ); + + $model->save(); + + return $model; + }); + + return $this->response() + ->code(201) + ->forRepository($request->newRepositoryWith($model), true) + ->header('Location', Restify::path() . '/' . $repository::uriKey() . '/' . $model->id) + ->respond(); + } +} diff --git a/src/Http/Requests/InteractWithRepositories.php b/src/Http/Requests/InteractWithRepositories.php index ccc374529..7de006157 100644 --- a/src/Http/Requests/InteractWithRepositories.php +++ b/src/Http/Requests/InteractWithRepositories.php @@ -103,4 +103,18 @@ public function isResolvedByRestify() return true; } } + + /** + * Get a new instance of the resource being requested. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @return Repository + */ + public function newRepositoryWith($model) + { + $resource = $this->repository(); + + return new $resource($model); + } + } diff --git a/src/Http/Requests/ResourceIndexRequest.php b/src/Http/Requests/RepositoryIndexRequest.php similarity index 71% rename from src/Http/Requests/ResourceIndexRequest.php rename to src/Http/Requests/RepositoryIndexRequest.php index 32a25469f..0284849f0 100644 --- a/src/Http/Requests/ResourceIndexRequest.php +++ b/src/Http/Requests/RepositoryIndexRequest.php @@ -5,6 +5,6 @@ /** * @author Eduard Lupacescu */ -class ResourceIndexRequest extends RestifyRequest +class RepositoryIndexRequest extends RestifyRequest { } diff --git a/src/Http/Requests/RepositoryStoreRequest.php b/src/Http/Requests/RepositoryStoreRequest.php new file mode 100644 index 000000000..edbd97433 --- /dev/null +++ b/src/Http/Requests/RepositoryStoreRequest.php @@ -0,0 +1,10 @@ + + */ +class RepositoryStoreRequest extends RestifyRequest +{ +} diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 9e59ac952..8b2ac294d 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -3,10 +3,12 @@ namespace Binaryk\LaravelRestify\Repositories; use Binaryk\LaravelRestify\Contracts\RestifySearchable; +use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Traits\InteractWithSearch; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Resources\DelegatesToResource; +use Illuminate\Support\Collection; use Illuminate\Support\Str; /** @@ -15,7 +17,9 @@ abstract class Repository implements RestifySearchable { use InteractWithSearch, - DelegatesToResource; + DelegatesToResource, + ValidatingTrait, + RepositoryFillFields; /** * This is named `resource` because of the forwarding properties from DelegatesToResource trait. @@ -82,4 +86,15 @@ public function toArray() return $model->toArray(); } + + abstract public function fields(RestifyRequest $request); + + /** + * @param RestifyRequest $request + * @return Collection + */ + public function collectFields(RestifyRequest $request) + { + return collect($this->fields($request)); + } } diff --git a/src/Repositories/RepositoryFillFields.php b/src/Repositories/RepositoryFillFields.php new file mode 100644 index 000000000..ceaab7208 --- /dev/null +++ b/src/Repositories/RepositoryFillFields.php @@ -0,0 +1,70 @@ + + */ +trait RepositoryFillFields +{ + + /** + * Fill fields on store request + * + * @param RestifyRequest $request + * @param $model + * @return array + */ + public static function fillWhenStore(RestifyRequest $request, $model) + { + return [$model, static::fillFields( + $request, $model, + (new static($model))->collectFields($request) + ), static::fillExtra($request, $model, + (new static($model))->collectFields($request) + )]; + } + + /** + * Fill each field separately + * + * @param RestifyRequest $request + * @param Model $model + * @param Collection $fields + * @return Model + */ + protected static function fillFields(RestifyRequest $request, Model $model, Collection $fields) + { + $fields->map(function (Field $field) use ($request, $model) { + return $field->fillAttribute($request, $model); + })->values()->all(); + + return $model; + } + + /** + * If some fields were not defined in the @fields method, but they are in fillable attributes and present in request, + * they should be also filled on request + * @param RestifyRequest $request + * @param Model $model + * @param Collection $fields + * @return array + */ + protected static function fillExtra(RestifyRequest $request, Model $model, Collection $fields) + { + $definedAttributes = $fields->map->getAttribute()->toArray(); + $fromRequest = collect($request->only($model->getFillable()))->keys()->filter(function ($attribute) use ($definedAttributes) { + return ! in_array($attribute, $definedAttributes); + }); + + return $fromRequest->each(function ($attribute) use ($request, $model) { + $model->{$attribute} = $request->{$attribute}; + })->values()->all(); + } +} diff --git a/src/Repositories/ValidatingTrait.php b/src/Repositories/ValidatingTrait.php new file mode 100644 index 000000000..e4a454fda --- /dev/null +++ b/src/Repositories/ValidatingTrait.php @@ -0,0 +1,86 @@ + + */ +trait ValidatingTrait +{ + /** + * @param RestifyRequest $request + * @return Collection + */ + abstract public function collectFields(RestifyRequest $request); + + /** + * @return mixed + */ + abstract public static function newModel(); + + + /** + * @param RestifyRequest $request + * @return \Illuminate\Contracts\Validation\Validator + */ + public static function validatorForStoring(RestifyRequest $request) + { + $on = (new static(static::newModel())); + + $messages = $on->collectFields($request)->flatMap(function ($k) { + $messages = []; + foreach ($k->messages as $ruleFor => $message) { + $messages[$k->attribute . '.' . $ruleFor] = $message; + } + return $messages; + })->toArray(); + + return Validator::make($request->all(), $on->getStoringRules($request), $messages)->after(function ($validator) use ($request) { + static::afterValidation($request, $validator); + static::afterStoringValidation($request, $validator); + }); + + } + + /** + * Handle any post-validation processing. + * + * @param RestifyRequest $request + * @param \Illuminate\Validation\Validator $validator + * @return void + */ + protected static function afterValidation(RestifyRequest $request, $validator) + { + // + } + + /** + * Handle any post-storing validation processing. + * + * @param RestifyRequest $request + * @param \Illuminate\Validation\Validator $validator + * @return void + */ + protected static function afterStoringValidation(RestifyRequest $request, $validator) + { + } + + /** + * @param RestifyRequest $request + * @return array + */ + public function getStoringRules(RestifyRequest $request) + { + return $this->collectFields($request)->mapWithKeys(function (Field $k) { + return [ + $k->attribute => $k->getStoringRules() + ]; + })->toArray(); + } +} diff --git a/src/Traits/InteractWithSearch.php b/src/Traits/InteractWithSearch.php index 54da86a4d..3f0a010d8 100644 --- a/src/Traits/InteractWithSearch.php +++ b/src/Traits/InteractWithSearch.php @@ -3,6 +3,7 @@ namespace Binaryk\LaravelRestify\Traits; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; +use Illuminate\Support\Str; /** * @author Eduard Lupacescu @@ -62,12 +63,22 @@ public static function getOrderByFields() */ public function serializeForIndex(RestifyRequest $request, array $fields = null) { - return array_merge($fields ?: $this->toArray(), [ - 'authorizedToView' => $this->authorizedToView($request), - 'authorizedToCreate' => $this->authorizedToCreate($request), - 'authorizedToUpdate' => $this->authorizedToUpdate($request), - 'authorizedToDelete' => $this->authorizedToDelete($request), - ]); + $serialized = [ + 'type' => Str::plural(Str::kebab(class_basename(get_called_class()))), + 'attributes' => $fields ?: $this->toArray(), + 'meta' => [ + 'authorizedToView' => $this->authorizedToView($request), + 'authorizedToCreate' => $this->authorizedToCreate($request), + 'authorizedToUpdate' => $this->authorizedToUpdate($request), + 'authorizedToDelete' => $this->authorizedToDelete($request), + ], + ]; + + if ($this->getKey()) { + $serialized['id'] = $this->getKey(); + } + + return $serialized; } /** diff --git a/src/Traits/PerformsRequestValidation.php b/src/Traits/PerformsRequestValidation.php new file mode 100644 index 000000000..da60f6fda --- /dev/null +++ b/src/Traits/PerformsRequestValidation.php @@ -0,0 +1,12 @@ + + */ +trait PerformsRequestValidation +{ + +} diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index 9872a5f92..e96750469 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -92,7 +92,6 @@ public function test_search_query_works() 'to' => 1, 'total' => 1, ], - 'errors' => [], ]); $this->withExceptionHandling() @@ -113,7 +112,6 @@ public function test_search_query_works() 'to' => 1, 'total' => 1, ], - 'errors' => [], ]); } @@ -183,7 +181,6 @@ public function test_that_match_param_works() 'to' => 1, 'total' => 1, ], - 'errors' => [], ]); } diff --git a/tests/Controllers/RepositoryStoreControllerTest.php b/tests/Controllers/RepositoryStoreControllerTest.php new file mode 100644 index 000000000..10335074f --- /dev/null +++ b/tests/Controllers/RepositoryStoreControllerTest.php @@ -0,0 +1,51 @@ + + */ +class RepositoryStoreControllerTest extends IntegrationTest +{ + protected function setUp(): void + { + parent::setUp(); + } + + public function test_basic_validation_works() + { + $this->withExceptionHandling()->post('/restify-api/posts', [ + 'title' => 'Title', + ]) + ->assertStatus(400) + ->assertJson([ + 'errors' => [ + 'description' => [ + 'Description field is required bro.' + ], + ], + ]); + } + + public function test_success_storing() + { + $user = $this->mockUsers()->first(); + $r = $this->withExceptionHandling()->post('/restify-api/posts', [ + 'user_id' => $user->id, + 'title' => 'Some post title', + 'description' => 'A very short description', + ]) + ->assertStatus(201) + ->assertHeader('Location', '/restify-api/posts/1') + ->getOriginalContent(); + + $this->assertEquals($r->data->attributes['title'], 'Some post title'); + $this->assertEquals($r->data->attributes['description'], 'A very short description'); + $this->assertEquals($r->data->attributes['user_id'], $user->id); + $this->assertEquals($r->data->id, 1); + $this->assertEquals($r->data->type, 'posts'); + } +} diff --git a/tests/Fixtures/PostRepository.php b/tests/Fixtures/PostRepository.php index c551686ab..e9095c42b 100644 --- a/tests/Fixtures/PostRepository.php +++ b/tests/Fixtures/PostRepository.php @@ -2,6 +2,8 @@ namespace Binaryk\LaravelRestify\Tests\Fixtures; +use Binaryk\LaravelRestify\Fields\Field; +use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Repository; /** @@ -20,4 +22,21 @@ public static function uriKey() { return 'posts'; } + + /** + * @param RestifyRequest $request + * @return array + */ + public function fields(RestifyRequest $request) + { + return [ + Field::fire('title')->storingRules('required')->messages([ + 'required' => 'This field is required bro.', + ]), + Field::fire('description')->storingRules('required')->messages([ + 'required' => 'Description field is required bro.', + ]), + ]; + + } } diff --git a/tests/Fixtures/UserRepository.php b/tests/Fixtures/UserRepository.php index 57292911d..ace103caa 100644 --- a/tests/Fixtures/UserRepository.php +++ b/tests/Fixtures/UserRepository.php @@ -2,6 +2,7 @@ namespace Binaryk\LaravelRestify\Tests\Fixtures; +use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Repository; /** @@ -20,4 +21,9 @@ public static function uriKey() { return 'users'; } + + public function fields(RestifyRequest $request) + { + return []; + } } diff --git a/tests/RestControllerTest.php b/tests/RestControllerTest.php index 6cac26031..d5988e12e 100644 --- a/tests/RestControllerTest.php +++ b/tests/RestControllerTest.php @@ -102,7 +102,7 @@ public function test_making_custom_response_message() Gate::shouldReceive('check') ->andReturnTrue(); $response = $this->controller->destroy($user->id); - $this->assertSame($response->getData()->message, 'User deleted.'); + $this->assertSame($response->getData()->data->meta->message, 'User deleted.'); } public function test_can_access_config_repository() From 3574d57a0135a0460b36adedf3cac8d46d3a0c59 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Tue, 24 Dec 2019 13:47:56 +0200 Subject: [PATCH 02/21] Apply fixes from StyleCI (#64) --- src/Controllers/RestController.php | 2 +- src/Controllers/RestResponse.php | 19 ++++++++++--------- src/Fields/BaseField.php | 2 -- src/Fields/Field.php | 18 +++++++++--------- src/Fields/OrganicField.php | 1 - src/Fields/RulesTrait.php | 15 +++++++-------- .../Controllers/RepositoryStoreController.php | 4 ++-- .../Requests/InteractWithRepositories.php | 1 - src/Repositories/RepositoryFillFields.php | 8 +++----- src/Repositories/ValidatingTrait.php | 8 +++----- src/Traits/PerformsRequestValidation.php | 2 -- .../RepositoryStoreControllerTest.php | 3 +-- tests/Fixtures/PostRepository.php | 1 - 13 files changed, 36 insertions(+), 48 deletions(-) diff --git a/src/Controllers/RestController.php b/src/Controllers/RestController.php index ec8110d76..9f0cedab7 100644 --- a/src/Controllers/RestController.php +++ b/src/Controllers/RestController.php @@ -157,7 +157,7 @@ public function search($modelClass, $filters = []) return [ 'meta' => Arr::except($paginator->toArray(), ['data', 'next_page_url', 'last_page_url', 'first_page_url', 'prev_page_url', 'path']), 'links' => Arr::only($paginator->toArray(), ['next_page_url', 'last_page_url', 'first_page_url', 'prev_page_url', 'path']), - 'data' => $items + 'data' => $items, ]; } diff --git a/src/Controllers/RestResponse.php b/src/Controllers/RestResponse.php index f5283f0b7..7ea83a7a3 100644 --- a/src/Controllers/RestResponse.php +++ b/src/Controllers/RestResponse.php @@ -119,12 +119,12 @@ class RestResponse protected $type; /** - * Key of the newly created resource + * Key of the newly created resource. * @var */ protected $id; /** - * Model related entities + * Model related entities. * @var */ protected $relationships; @@ -184,7 +184,7 @@ public function errors(array $errors = null) */ public function addError($message) { - if ( ! isset($this->errors)) { + if (! isset($this->errors)) { $this->errors = []; } @@ -269,7 +269,7 @@ public function __get($key) return $this->$key; } - $code = 'static::REST_RESPONSE_' . strtoupper($key) . '_CODE'; + $code = 'static::REST_RESPONSE_'.strtoupper($key).'_CODE'; return defined($code) ? constant($code) : null; } @@ -284,7 +284,7 @@ public function __get($key) */ public function __call($func, $args) { - $code = 'static::REST_RESPONSE_' . strtoupper($func) . '_CODE'; + $code = 'static::REST_RESPONSE_'.strtoupper($func).'_CODE'; if (defined($code)) { return $this->code(constant($code)); @@ -303,7 +303,7 @@ public function __call($func, $args) */ public function respond($response = null) { - if ( ! func_num_args()) { + if (! func_num_args()) { $response = new \stdClass(); $response->data = new \stdClass(); @@ -408,7 +408,7 @@ public function getAttribute($name) } /** - * Set attributes at root level + * Set attributes at root level. * * @param array $attributes * @return mixed @@ -445,6 +445,7 @@ protected function response() public function header($name, $value) { $this->headers[$name] = $value; + return $this; } @@ -455,12 +456,13 @@ public function header($name, $value) public function type($type) { $this->type = $type; + return $this; } /** * Useful when newly created repository, will prepare the response according - * with JSON:API https://jsonapi.org/format/#document-resource-object-fields + * with JSON:API https://jsonapi.org/format/#document-resource-object-fields. * * @param Repository $repository * @param bool $withRelations @@ -502,7 +504,6 @@ public static function beforeRespond($response) return $response; } - return $response; } } diff --git a/src/Fields/BaseField.php b/src/Fields/BaseField.php index 5d6be957b..77e8e1c5a 100644 --- a/src/Fields/BaseField.php +++ b/src/Fields/BaseField.php @@ -3,10 +3,8 @@ namespace Binaryk\LaravelRestify\Fields; /** - * @package Binaryk\LaravelRestify; * @author Eduard Lupacescu */ abstract class BaseField { - } diff --git a/src/Fields/Field.php b/src/Fields/Field.php index 073c5d298..97996bedb 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -7,7 +7,6 @@ use JsonSerializable; /** - * @package Binaryk\LaravelRestify; * @author Eduard Lupacescu */ class Field extends OrganicField implements JsonSerializable @@ -15,20 +14,20 @@ class Field extends OrganicField implements JsonSerializable use RulesTrait; /** - * Column name of the field + * Column name of the field. * @var string */ public $attribute; /** - * Callback called when the value is filled, this callback will do not override the fill action + * Callback called when the value is filled, this callback will do not override the fill action. * @var Closure */ public $storeCallback; /** * Callback called when trying to fill this attribute, this callback will override the fill action, so make - * sure you assign the attribute to the model over this callback + * sure you assign the attribute to the model over this callback. * * @var Closure */ @@ -56,7 +55,7 @@ public static function fire(...$arguments) } /** - * @inheritDoc + * {@inheritdoc} */ public function jsonSerialize() { @@ -65,7 +64,7 @@ public function jsonSerialize() /** * Callback called when the value is filled, this callback will do not override the fill action. If fillCallback is defined - * this will do not be called + * this will do not be called. * * @param Closure $callback * @return Field @@ -79,7 +78,7 @@ public function storeCallback(Closure $callback) /** * Callback called when trying to fill this attribute, this callback will override the fill action, so make - * sure you assign the attribute to the model over this callback + * sure you assign the attribute to the model over this callback. * * @param Closure $callback * @return $this @@ -87,11 +86,12 @@ public function storeCallback(Closure $callback) public function fillCallback(Closure $callback) { $this->fillCallback = $callback; + return $this; } /** - * Fill attribute with value from the request or delegate this action to the user defined callback + * Fill attribute with value from the request or delegate this action to the user defined callback. * * @param RestifyRequest $request * @param $model @@ -111,7 +111,7 @@ public function fillAttribute(RestifyRequest $request, $model) } /** - * Fill the model with value from the request + * Fill the model with value from the request. * * @param RestifyRequest $request * @param $model diff --git a/src/Fields/OrganicField.php b/src/Fields/OrganicField.php index 0cb7c7614..3d4c38343 100644 --- a/src/Fields/OrganicField.php +++ b/src/Fields/OrganicField.php @@ -3,7 +3,6 @@ namespace Binaryk\LaravelRestify\Fields; /** - * @package Binaryk\LaravelRestify; * @author Eduard Lupacescu */ abstract class OrganicField extends BaseField diff --git a/src/Fields/RulesTrait.php b/src/Fields/RulesTrait.php index 9631f1537..094a0a1da 100644 --- a/src/Fields/RulesTrait.php +++ b/src/Fields/RulesTrait.php @@ -5,33 +5,31 @@ use Illuminate\Contracts\Validation\Rule; /** - * @package Binaryk\LaravelRestify\Fields; * @author Eduard Lupacescu */ trait RulesTrait { /** - * Rules for applied when store + * Rules for applied when store. * * @var array */ public $storingRules = []; /** - * Rules for applied when store and update + * Rules for applied when store and update. * * @var array */ public $rules = []; - /** * @var array */ public $messages = []; /** - * Validation rules for store + * Validation rules for store. * @param callable|array|string $rules * @return RulesTrait */ @@ -43,7 +41,7 @@ public function storingRules($rules) } /** - * Validation rules for store + * Validation rules for store. * @param callable|array|string $rules * @return RulesTrait */ @@ -55,7 +53,7 @@ public function rules($rules) } /** - * Validation messages + * Validation messages. * * @param array $messages * @return RulesTrait @@ -63,11 +61,12 @@ public function rules($rules) public function messages(array $messages) { $this->messages = $messages; + return $this; } /** - * Validation rules for storing + * Validation rules for storing. * * @return array */ diff --git a/src/Http/Controllers/RepositoryStoreController.php b/src/Http/Controllers/RepositoryStoreController.php index 92339313a..52e7a7c9a 100644 --- a/src/Http/Controllers/RepositoryStoreController.php +++ b/src/Http/Controllers/RepositoryStoreController.php @@ -30,7 +30,7 @@ class RepositoryStoreController extends RepositoryController public function handle(RepositoryStoreRequest $request) { /** - * @var Repository $repository + * @var Repository */ $repository = $request->repository(); @@ -57,7 +57,7 @@ public function handle(RepositoryStoreRequest $request) return $this->response() ->code(201) ->forRepository($request->newRepositoryWith($model), true) - ->header('Location', Restify::path() . '/' . $repository::uriKey() . '/' . $model->id) + ->header('Location', Restify::path().'/'.$repository::uriKey().'/'.$model->id) ->respond(); } } diff --git a/src/Http/Requests/InteractWithRepositories.php b/src/Http/Requests/InteractWithRepositories.php index 7de006157..fd40683a3 100644 --- a/src/Http/Requests/InteractWithRepositories.php +++ b/src/Http/Requests/InteractWithRepositories.php @@ -116,5 +116,4 @@ public function newRepositoryWith($model) return new $resource($model); } - } diff --git a/src/Repositories/RepositoryFillFields.php b/src/Repositories/RepositoryFillFields.php index ceaab7208..660bc09c5 100644 --- a/src/Repositories/RepositoryFillFields.php +++ b/src/Repositories/RepositoryFillFields.php @@ -8,14 +8,12 @@ use Illuminate\Support\Collection; /** - * @package Binaryk\LaravelRestify\Repositories; * @author Eduard Lupacescu */ trait RepositoryFillFields { - /** - * Fill fields on store request + * Fill fields on store request. * * @param RestifyRequest $request * @param $model @@ -32,7 +30,7 @@ public static function fillWhenStore(RestifyRequest $request, $model) } /** - * Fill each field separately + * Fill each field separately. * * @param RestifyRequest $request * @param Model $model @@ -50,7 +48,7 @@ protected static function fillFields(RestifyRequest $request, Model $model, Coll /** * If some fields were not defined in the @fields method, but they are in fillable attributes and present in request, - * they should be also filled on request + * they should be also filled on request. * @param RestifyRequest $request * @param Model $model * @param Collection $fields diff --git a/src/Repositories/ValidatingTrait.php b/src/Repositories/ValidatingTrait.php index e4a454fda..f6d485d80 100644 --- a/src/Repositories/ValidatingTrait.php +++ b/src/Repositories/ValidatingTrait.php @@ -8,7 +8,6 @@ use Illuminate\Support\Facades\Validator; /** - * @package Binaryk\LaravelRestify\Repositories; * @author Eduard Lupacescu */ trait ValidatingTrait @@ -24,7 +23,6 @@ abstract public function collectFields(RestifyRequest $request); */ abstract public static function newModel(); - /** * @param RestifyRequest $request * @return \Illuminate\Contracts\Validation\Validator @@ -36,8 +34,9 @@ public static function validatorForStoring(RestifyRequest $request) $messages = $on->collectFields($request)->flatMap(function ($k) { $messages = []; foreach ($k->messages as $ruleFor => $message) { - $messages[$k->attribute . '.' . $ruleFor] = $message; + $messages[$k->attribute.'.'.$ruleFor] = $message; } + return $messages; })->toArray(); @@ -45,7 +44,6 @@ public static function validatorForStoring(RestifyRequest $request) static::afterValidation($request, $validator); static::afterStoringValidation($request, $validator); }); - } /** @@ -79,7 +77,7 @@ public function getStoringRules(RestifyRequest $request) { return $this->collectFields($request)->mapWithKeys(function (Field $k) { return [ - $k->attribute => $k->getStoringRules() + $k->attribute => $k->getStoringRules(), ]; })->toArray(); } diff --git a/src/Traits/PerformsRequestValidation.php b/src/Traits/PerformsRequestValidation.php index da60f6fda..ab4470412 100644 --- a/src/Traits/PerformsRequestValidation.php +++ b/src/Traits/PerformsRequestValidation.php @@ -3,10 +3,8 @@ namespace Binaryk\LaravelRestify\Traits; /** - * @package Binaryk\LaravelRestify\Traits; * @author Eduard Lupacescu */ trait PerformsRequestValidation { - } diff --git a/tests/Controllers/RepositoryStoreControllerTest.php b/tests/Controllers/RepositoryStoreControllerTest.php index 10335074f..42cb6d9cc 100644 --- a/tests/Controllers/RepositoryStoreControllerTest.php +++ b/tests/Controllers/RepositoryStoreControllerTest.php @@ -5,7 +5,6 @@ use Binaryk\LaravelRestify\Tests\IntegrationTest; /** - * @package Binaryk\LaravelRestify\Tests\Controllers; * @author Eduard Lupacescu */ class RepositoryStoreControllerTest extends IntegrationTest @@ -24,7 +23,7 @@ public function test_basic_validation_works() ->assertJson([ 'errors' => [ 'description' => [ - 'Description field is required bro.' + 'Description field is required bro.', ], ], ]); diff --git a/tests/Fixtures/PostRepository.php b/tests/Fixtures/PostRepository.php index e9095c42b..0bd059867 100644 --- a/tests/Fixtures/PostRepository.php +++ b/tests/Fixtures/PostRepository.php @@ -37,6 +37,5 @@ public function fields(RestifyRequest $request) 'required' => 'Description field is required bro.', ]), ]; - } } From 680e75ffb14cac852716f85a3123adbc902a4e08 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Tue, 24 Dec 2019 19:54:01 +0200 Subject: [PATCH 03/21] Tests coverage --- src/Fields/Field.php | 5 +- src/Traits/InteractWithSearch.php | 2 +- .../RepositoryIndexControllerTest.php | 84 +++++++++++-------- tests/RestControllerTest.php | 2 +- 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/Fields/Field.php b/src/Fields/Field.php index 073c5d298..a0a089ea6 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -38,9 +38,8 @@ class Field extends OrganicField implements JsonSerializable * Create a new field. * * @param string|callable|null $attribute - * @param callable|null $resolveCallback */ - public function __construct($attribute, callable $resolveCallback = null) + public function __construct($attribute) { $this->attribute = $attribute; } @@ -48,6 +47,7 @@ public function __construct($attribute, callable $resolveCallback = null) /** * Create a new element. * + * @param array $arguments * @return static */ public static function fire(...$arguments) @@ -55,6 +55,7 @@ public static function fire(...$arguments) return new static(...$arguments); } + /** * @inheritDoc */ diff --git a/src/Traits/InteractWithSearch.php b/src/Traits/InteractWithSearch.php index 3f0a010d8..c271101fd 100644 --- a/src/Traits/InteractWithSearch.php +++ b/src/Traits/InteractWithSearch.php @@ -64,7 +64,7 @@ public static function getOrderByFields() public function serializeForIndex(RestifyRequest $request, array $fields = null) { $serialized = [ - 'type' => Str::plural(Str::kebab(class_basename(get_called_class()))), + 'type' => $request->isResolvedByRestify() ? static::uriKey() : Str::plural(Str::kebab(class_basename(get_called_class()))), 'attributes' => $fields ?: $this->toArray(), 'meta' => [ 'authorizedToView' => $this->authorizedToView($request), diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index e96750469..89b3d6464 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -23,7 +23,7 @@ public function test_list_resource() $response = $this->withExceptionHandling() ->getJson('/restify-api/users'); - $response->assertJsonCount(3, 'data.data'); + $response->assertJsonCount(3, 'data'); } public function test_the_rest_controller_can_paginate() @@ -73,45 +73,52 @@ public function users() public function test_search_query_works() { $users = $this->mockUsers(10, ['eduard.lupacescu@binarcode.com']); - $expected = $users->where('email', 'eduard.lupacescu@binarcode.com')->first()->serializeForIndex(Mockery::mock(RestifyRequest::class)); + $request = Mockery::mock(RestifyRequest::class); + $request->shouldReceive('isResolvedByRestify') + ->andReturnFalse(); + $expected = $users->where('email', 'eduard.lupacescu@binarcode.com')->first()->serializeForIndex($request); $this->withExceptionHandling() ->getJson('/restify-api/users?search=eduard.lupacescu@binarcode.com') ->assertStatus(200) ->assertJson([ - 'data' => [ - 'data' => [$expected], - 'current_page' => 1, - 'first_page_url' => 'http://localhost/restify-api/users?page=1', - 'from' => 1, - 'last_page' => 1, + 'links' => [ 'last_page_url' => 'http://localhost/restify-api/users?page=1', 'next_page_url' => null, 'path' => 'http://localhost/restify-api/users', - 'per_page' => 15, + 'first_page_url' => 'http://localhost/restify-api/users?page=1', 'prev_page_url' => null, + ], + 'meta' => [ + 'current_page' => 1, + 'from' => 1, + 'last_page' => 1, + 'per_page' => 15, 'to' => 1, 'total' => 1, ], + 'data' => [$expected], ]); $this->withExceptionHandling() ->getJson('/restify-api/users?search=some_unexpected_string_here') ->assertStatus(200) ->assertJson([ - 'data' => [ - 'data' => [], - 'current_page' => 1, - 'first_page_url' => 'http://localhost/restify-api/users?page=1', - 'from' => 1, - 'last_page' => 1, + 'links' => [ 'last_page_url' => 'http://localhost/restify-api/users?page=1', 'next_page_url' => null, 'path' => 'http://localhost/restify-api/users', - 'per_page' => 15, + 'first_page_url' => 'http://localhost/restify-api/users?page=1', 'prev_page_url' => null, + ], + 'meta' => [ + 'current_page' => 1, + 'from' => 1, + 'last_page' => 1, + 'per_page' => 15, 'to' => 1, 'total' => 1, ], + 'data' => [] ]); } @@ -122,8 +129,8 @@ public function test_that_desc_sort_query_param_works() ->assertStatus(200) ->getOriginalContent(); - $this->assertSame($response->data['data'][0]['id'], 10); - $this->assertSame($response->data['data'][9]['id'], 1); + $this->assertSame($response->data[0]['attributes']['id'], 10); + $this->assertSame($response->data[9]['attributes']['id'], 1); } public function test_that_asc_sort_query_param_works() @@ -134,15 +141,15 @@ public function test_that_asc_sort_query_param_works() ->assertStatus(200) ->getOriginalContent(); - $this->assertSame($response->data['data'][0]['id'], 1); - $this->assertSame($response->data['data'][9]['id'], 10); + $this->assertSame($response->data[0]['attributes']['id'], 1); + $this->assertSame($response->data[9]['attributes']['id'], 10); $response = $this->withExceptionHandling()->get('/restify-api/users?sort=id')//assert default ASC sort ->assertStatus(200) ->getOriginalContent(); - $this->assertSame($response->data['data'][0]['id'], 1); - $this->assertSame($response->data['data'][9]['id'], 10); + $this->assertSame($response->data[0]['attributes']['id'], 1); + $this->assertSame($response->data[9]['attributes']['id'], 10); } public function test_that_default_asc_sort_query_param_works() @@ -153,34 +160,40 @@ public function test_that_default_asc_sort_query_param_works() ->assertStatus(200) ->getOriginalContent(); - $this->assertSame($response->data['data'][0]['id'], 1); - $this->assertSame($response->data['data'][9]['id'], 10); + $this->assertSame($response->data[0]['attributes']['id'], 1); + $this->assertSame($response->data[9]['attributes']['id'], 10); } public function test_that_match_param_works() { User::$match = ['email' => RestifySearchable::MATCH_TEXT]; // it will automatically filter over these queries (email='test@email.com') $users = $this->mockUsers(10, ['eduard.lupacescu@binarcode.com']); - $expected = $users->where('email', 'eduard.lupacescu@binarcode.com')->first()->serializeForIndex(Mockery::mock(RestifyRequest::class)); + $request = Mockery::mock(RestifyRequest::class); + $request->shouldReceive('isResolvedByRestify') + ->andReturnFalse(); + + $expected = $users->where('email', 'eduard.lupacescu@binarcode.com')->first()->serializeForIndex($request); $this->withExceptionHandling() ->get('/restify-api/users?email=eduard.lupacescu@binarcode.com') ->assertStatus(200) ->assertJson([ - 'data' => [ - 'data' => [$expected], - 'current_page' => 1, - 'first_page_url' => 'http://localhost/restify-api/users?page=1', - 'from' => 1, - 'last_page' => 1, + 'links' => [ 'last_page_url' => 'http://localhost/restify-api/users?page=1', 'next_page_url' => null, 'path' => 'http://localhost/restify-api/users', - 'per_page' => 15, + 'first_page_url' => 'http://localhost/restify-api/users?page=1', 'prev_page_url' => null, + ], + 'meta' => [ + 'current_page' => 1, + 'from' => 1, + 'last_page' => 1, + 'per_page' => 15, 'to' => 1, 'total' => 1, ], + 'data' => [$expected], ]); } @@ -189,13 +202,16 @@ public function test_that_with_param_works() User::$match = ['email' => RestifySearchable::MATCH_TEXT]; // it will automatically filter over these queries (email='test@email.com') $users = $this->mockUsers(1); $posts = $this->mockPosts(1, 2); - $expected = $users->first()->serializeForIndex(Mockery::mock(RestifyRequest::class)); + $request = Mockery::mock(RestifyRequest::class); + $request->shouldReceive('isResolvedByRestify') + ->andReturnFalse(); + $expected = $users->first()->serializeForIndex($request); $expected['posts'] = $posts->toArray(); $r = $this->withExceptionHandling() ->get('/restify-api/users?with=posts') ->assertStatus(200) ->getOriginalContent(); - $this->assertSameSize($r->data['data']->first()['posts'], $expected['posts']); + $this->assertSameSize($r->data[0]['attributes']['posts'], $expected['posts']); } } diff --git a/tests/RestControllerTest.php b/tests/RestControllerTest.php index d5988e12e..50796c1cc 100644 --- a/tests/RestControllerTest.php +++ b/tests/RestControllerTest.php @@ -102,7 +102,7 @@ public function test_making_custom_response_message() Gate::shouldReceive('check') ->andReturnTrue(); $response = $this->controller->destroy($user->id); - $this->assertSame($response->getData()->data->meta->message, 'User deleted.'); + $this->assertSame($response->getData()->meta->message, 'User deleted.'); } public function test_can_access_config_repository() From 4505ef425e861936b446300959a743f80521e39e Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Tue, 24 Dec 2019 19:54:31 +0200 Subject: [PATCH 04/21] Apply fixes from StyleCI (#66) --- src/Fields/Field.php | 1 - tests/Controllers/RepositoryIndexControllerTest.php | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Fields/Field.php b/src/Fields/Field.php index ca3d9a6da..3ab14673b 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -54,7 +54,6 @@ public static function fire(...$arguments) return new static(...$arguments); } - /** * {@inheritdoc} */ diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index 89b3d6464..8107525e5 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -73,7 +73,7 @@ public function users() public function test_search_query_works() { $users = $this->mockUsers(10, ['eduard.lupacescu@binarcode.com']); - $request = Mockery::mock(RestifyRequest::class); + $request = Mockery::mock(RestifyRequest::class); $request->shouldReceive('isResolvedByRestify') ->andReturnFalse(); $expected = $users->where('email', 'eduard.lupacescu@binarcode.com')->first()->serializeForIndex($request); @@ -118,7 +118,7 @@ public function test_search_query_works() 'to' => 1, 'total' => 1, ], - 'data' => [] + 'data' => [], ]); } @@ -168,7 +168,7 @@ public function test_that_match_param_works() { User::$match = ['email' => RestifySearchable::MATCH_TEXT]; // it will automatically filter over these queries (email='test@email.com') $users = $this->mockUsers(10, ['eduard.lupacescu@binarcode.com']); - $request = Mockery::mock(RestifyRequest::class); + $request = Mockery::mock(RestifyRequest::class); $request->shouldReceive('isResolvedByRestify') ->andReturnFalse(); @@ -202,7 +202,7 @@ public function test_that_with_param_works() User::$match = ['email' => RestifySearchable::MATCH_TEXT]; // it will automatically filter over these queries (email='test@email.com') $users = $this->mockUsers(1); $posts = $this->mockPosts(1, 2); - $request = Mockery::mock(RestifyRequest::class); + $request = Mockery::mock(RestifyRequest::class); $request->shouldReceive('isResolvedByRestify') ->andReturnFalse(); $expected = $users->first()->serializeForIndex($request); From e82c241c23c54292aa53dbbee96ee099b9226e45 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Thu, 26 Dec 2019 20:52:02 +0200 Subject: [PATCH 05/21] Support for customizing response from the repository. Using Laravel repository. --- routes/api.php | 1 + .../stubs/RestifyServiceProvider.stub | 3 + src/Controllers/RestController.php | 39 +++-- .../Controllers/RepositoryIndexController.php | 8 +- .../Controllers/RepositoryShowController.php | 33 ++++ .../Requests/InteractWithRepositories.php | 50 +++++- src/Repositories/Repository.php | 50 ++++-- src/Repositories/RepositoryCollection.php | 142 ++++++++++++++++++ src/Repositories/ResponseResolver.php | 89 +++++++++++ src/Restify.php | 13 ++ src/Services/Search/SearchService.php | 4 +- src/Traits/InteractWithSearch.php | 31 ---- 12 files changed, 400 insertions(+), 63 deletions(-) create mode 100644 src/Http/Controllers/RepositoryShowController.php create mode 100644 src/Repositories/RepositoryCollection.php create mode 100644 src/Repositories/ResponseResolver.php diff --git a/routes/api.php b/routes/api.php index ee8c38c30..03d72b80d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -2,3 +2,4 @@ Route::get('/{repository}', 'RepositoryIndexController@handle'); Route::post('/{repository}', 'RepositoryStoreController@handle'); +Route::get('/{repository}/{repositoryId}', 'RepositoryShowController@handle'); diff --git a/src/Commands/stubs/RestifyServiceProvider.stub b/src/Commands/stubs/RestifyServiceProvider.stub index 11d9fe7ac..4d73bb812 100644 --- a/src/Commands/stubs/RestifyServiceProvider.stub +++ b/src/Commands/stubs/RestifyServiceProvider.stub @@ -4,6 +4,7 @@ namespace App\Providers; use Illuminate\Support\Facades\Gate; use Binaryk\LaravelRestify\Restify; +use Illuminate\Http\Resources\Json\Resource; use Binaryk\LaravelRestify\RestifyApplicationServiceProvider; class RestifyServiceProvider extends RestifyApplicationServiceProvider @@ -16,6 +17,8 @@ class RestifyServiceProvider extends RestifyApplicationServiceProvider public function boot() { parent::boot(); + + Resource::withoutWrapping(); } /** diff --git a/src/Controllers/RestController.php b/src/Controllers/RestController.php index 9f0cedab7..071a78c49 100644 --- a/src/Controllers/RestController.php +++ b/src/Controllers/RestController.php @@ -24,6 +24,7 @@ use Illuminate\Routing\Controller as BaseController; use Illuminate\Support\Arr; use Illuminate\Support\Facades\Password; +use Throwable; /** * Abstract Class RestController. @@ -36,7 +37,7 @@ */ abstract class RestController extends BaseController { - use AuthorizesRequests, DispatchesJobs, ValidatesRequests, PerformsQueries; + use AuthorizesRequests, DispatchesJobs, ValidatesRequests; /** * @var RestResponse @@ -133,21 +134,12 @@ protected function response($data = null, $status = 200, array $headers = []) * @return array * @throws BindingResolutionException * @throws InstanceOfException + * @throws Throwable */ public function search($modelClass, $filters = []) { - $results = SearchService::instance() - ->setPredefinedFilters($filters) - ->search($this->request(), $modelClass instanceof Repository ? $modelClass->model() : new $modelClass); + $paginator = $this->paginator($modelClass, $filters); - $results->tap(function ($query) { - static::indexQuery($this->request(), $query); - }); - - /** - * @var \Illuminate\Pagination\Paginator - */ - $paginator = $results->paginate($this->request()->get('perPage') ?? ($modelClass::$defaultPerPage ?? RestifySearchable::DEFAULT_PER_PAGE)); if ($modelClass instanceof Repository) { $items = $paginator->getCollection()->mapInto(get_class($modelClass))->map->serializeForIndex($this->request()); } else { @@ -161,6 +153,29 @@ public function search($modelClass, $filters = []) ]; } + /** + * @param $modelClass + * @param array $filters + * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator + * @throws BindingResolutionException + * @throws InstanceOfException + * @throws Throwable + */ + public function paginator($modelClass, $filters = []) + { + $results = SearchService::instance() + ->setPredefinedFilters($filters) + ->search($this->request(), $modelClass instanceof Repository ? $modelClass->model() : new $modelClass); + + $results->tap(function ($query) use ($modelClass) { + if ($modelClass instanceof Repository) { + $modelClass::indexQuery($this->request(), $query); + } + }); + + return $results->paginate($this->request()->get('perPage') ?? ($modelClass::$defaultPerPage ?? RestifySearchable::DEFAULT_PER_PAGE)); + } + /** * @param $policy * @param $objects diff --git a/src/Http/Controllers/RepositoryIndexController.php b/src/Http/Controllers/RepositoryIndexController.php index 60b246ce4..f659810bf 100644 --- a/src/Http/Controllers/RepositoryIndexController.php +++ b/src/Http/Controllers/RepositoryIndexController.php @@ -11,15 +11,17 @@ class RepositoryIndexController extends RepositoryController { /** * @param RestifyRequest $request - * @return \Illuminate\Http\JsonResponse + * @return \Binaryk\LaravelRestify\Repositories\Repository * @throws \Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException + * @throws \Binaryk\LaravelRestify\Exceptions\InstanceOfException * @throws \Binaryk\LaravelRestify\Exceptions\UnauthorizedException * @throws \Illuminate\Contracts\Container\BindingResolutionException + * @throws \Throwable */ public function handle(RestifyRequest $request) { - $data = $this->search($request->newRepository()); + $data = $this->paginator($request->newRepository()); - return $this->response()->data($data['data'])->meta($data['meta'])->links($data['links'])->respond(); + return $request->newRepositoryWith($data); } } diff --git a/src/Http/Controllers/RepositoryShowController.php b/src/Http/Controllers/RepositoryShowController.php new file mode 100644 index 000000000..03524f2d4 --- /dev/null +++ b/src/Http/Controllers/RepositoryShowController.php @@ -0,0 +1,33 @@ + + */ +class RepositoryShowController extends RepositoryController +{ + /** + * @param RestifyRequest $request + * @return \Binaryk\LaravelRestify\Controllers\RestResponse|mixed + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Throwable + */ + public function handle(RestifyRequest $request) + { + /** + * @var Repository $repository + */ + $repository = $request->newRepositoryWith(tap(SearchService::instance()->prepareRelations($request, $request->findModelQuery()), function ($query) use ($request) { + $request->newRepository()->detailQuery($request, $query); + })->firstOrFail()); + + $repository->authorizeToView($request); + + return $repository; + } +} diff --git a/src/Http/Requests/InteractWithRepositories.php b/src/Http/Requests/InteractWithRepositories.php index fd40683a3..9c39ecb80 100644 --- a/src/Http/Requests/InteractWithRepositories.php +++ b/src/Http/Requests/InteractWithRepositories.php @@ -44,7 +44,7 @@ public function repository() ]), 404); } - if (! $repository::authorizedToViewAny($this)) { + if ( ! $repository::authorizedToViewAny($this)) { throw new UnauthorizedException(__('Unauthorized to view repository :name.', [ 'name' => $repository, ]), 403); @@ -68,7 +68,7 @@ public function rules() * Get the route handling the request. * * @param string|null $param - * @param mixed $default + * @param mixed $default * @return \Illuminate\Routing\Route|object|string */ abstract public function route($param = null, $default = null); @@ -105,15 +105,53 @@ public function isResolvedByRestify() } /** - * Get a new instance of the resource being requested. + * Get a new instance of the repository being requested. + * As a model it could accept either a model instance, a collection or even paginated collection * - * @param \Illuminate\Database\Eloquent\Model $model + * @param $model * @return Repository */ public function newRepositoryWith($model) { - $resource = $this->repository(); + $repository = $this->repository(); + + return new $repository($model); + } + + + /** + * Get a new, scopeless query builder for the underlying model. + * + * @return \Illuminate\Database\Eloquent\Builder + */ + public function newQueryWithoutScopes() + { + return $this->model()->newQueryWithoutScopes(); + } + + /** + * Get a new instance of the underlying model. + * + * @return \Illuminate\Database\Eloquent\Model + */ + public function model() + { + $repository = $this->repository(); + + return $repository::newModel(); + } - return new $resource($model); + /** + * Get the query to find the model instance for the request. + * + * @param mixed|null $repositoryId + * @return \Illuminate\Database\Eloquent\Builder + */ + public function findModelQuery($repositoryId = null) + { + return $this->newQueryWithoutScopes()->whereKey( + $repositoryId ?? $this->repositoryId + ); } + } diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 8b2ac294d..9d371e4bd 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -5,25 +5,33 @@ use Binaryk\LaravelRestify\Contracts\RestifySearchable; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Traits\InteractWithSearch; +use Binaryk\LaravelRestify\Traits\PerformsQueries; +use Illuminate\Container\Container; +use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; -use Illuminate\Http\Resources\DelegatesToResource; use Illuminate\Support\Collection; use Illuminate\Support\Str; /** + * This class serve as repository collection and repository single model + * This allow you to use all of the Laravel default repositories features (as adding headers in the response, or customizing + * response) * @author Eduard Lupacescu */ -abstract class Repository implements RestifySearchable +abstract class Repository extends RepositoryCollection implements RestifySearchable { use InteractWithSearch, - DelegatesToResource, ValidatingTrait, - RepositoryFillFields; + RepositoryFillFields, + PerformsQueries, + ResponseResolver; /** * This is named `resource` because of the forwarding properties from DelegatesToResource trait. - * @var Model + * This may be a single model or a illuminate collection, or even a paginator instance + * + * @var Model|LengthAwarePaginator */ public $resource; @@ -34,16 +42,23 @@ abstract class Repository implements RestifySearchable */ public function __construct($model) { + parent::__construct($model); $this->resource = $model; } /** * Get the underlying model instance for the resource. * - * @return \Illuminate\Database\Eloquent\Model + * @return \Illuminate\Database\Eloquent\Model|LengthAwarePaginator */ public function model() { + if ($this->isRenderingCollection() || $this->isRenderingPaginated()) { + + return $this->modelFromIterator(); + + } + return $this->resource; } @@ -79,12 +94,29 @@ public static function query() /** * @return array + * @throws \Illuminate\Contracts\Container\BindingResolutionException */ - public function toArray() + public function toArray($request) { - $model = $this->model(); + $request = Container::getInstance()->make('request'); + + if ($this->isRenderingCollection()) { + + return $this->toArrayForCollection($request); + + } + + $serialized = [ + 'id' => $this->when($this->isRenderingRepository(), function () { + return $this->getKey(); + }), + 'type' => method_exists($this, 'uriKey') ? static::uriKey() : Str::plural(Str::kebab(class_basename(get_called_class()))), + 'attributes' => $this->resolveDetailsAttributes($request), + 'relationships' => $this->when(value($this->resolveDetailsRelationships($request)), $this->resolveDetailsRelationships($request)), + 'meta' => $this->when(value($this->resolveDetailsMeta($request)), $this->resolveDetailsMeta($request)), + ]; - return $model->toArray(); + return $this->resolveDetails($serialized); } abstract public function fields(RestifyRequest $request); diff --git a/src/Repositories/RepositoryCollection.php b/src/Repositories/RepositoryCollection.php new file mode 100644 index 000000000..c55346fad --- /dev/null +++ b/src/Repositories/RepositoryCollection.php @@ -0,0 +1,142 @@ + + */ +class RepositoryCollection extends Resource +{ + /** + * When the repository is used as a response for a collection list (index controller) + * + * @param $request + * @return array + */ + public function toArrayForCollection($request) + { + $paginated = parent::toArray($request); + + $currentRepository = Restify::repositoryForModel(get_class($this->model())); + + if (is_null($currentRepository)) { + return parent::toArray($request); + } + + $data = collect([]); + $iterator = $this->iterator(); + + while ($iterator->valid()) { + $data->push($iterator->current()); + $iterator->next(); + } + + $response = $data->mapInto($currentRepository)->toArray($request); + + return [ + 'meta' => $this->when($this->isRenderingPaginated(), $this->meta($paginated)), + 'links' => $this->when($this->isRenderingPaginated(), $this->paginationLinks($paginated)), + 'data' => $response, + ]; + } + + /** + * Get the pagination links for the response. + * + * @param array $paginated + * @return array + */ + protected function paginationLinks($paginated) + { + 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, + ]; + } + + /** + * Gather the meta data for the response. + * + * @param array $paginated + * @return array + */ + protected function meta($paginated) + { + return Arr::except($paginated, [ + 'data', + 'first_page_url', + 'last_page_url', + 'prev_page_url', + 'next_page_url', + ]); + } + + /** + * Check if the repository is used as a response for a list of items or for a single + * model entity + * @return bool + */ + protected function isRenderingRepository() + { + return $this->resource instanceof Model; + } + + /** + * Check if the repository is used as a response for a list of items or for a single + * model entity + * @return bool + */ + protected function isRenderingCollection() + { + return false === $this->resource instanceof Model; + } + + /** + * @return bool + */ + public function isRenderingPaginated() + { + return $this->resource instanceof AbstractPaginator; + } + + /** + * If collection or paginator then return model from the first item + * + * @return Model + */ + protected function modelFromIterator() + { + /** + * @var ArrayIterator $iterator + */ + $iterator = $this->iterator(); + + /** + * This is the first element from the response collection, now we have the class of the restify + * engine + * @var Model $model + */ + $model = $iterator->current(); + + return $model; + } + + /** + * @return ArrayIterator + */ + protected function iterator() + { + return $this->resource->getIterator(); + } +} diff --git a/src/Repositories/ResponseResolver.php b/src/Repositories/ResponseResolver.php new file mode 100644 index 000000000..bd1176d38 --- /dev/null +++ b/src/Repositories/ResponseResolver.php @@ -0,0 +1,89 @@ + + */ +trait ResponseResolver +{ + + /** + * Return the attributes list + * + * @param $request + * @return array + */ + public function resolveDetailsAttributes($request) + { + return parent::toArray($request); + } + + /** + * @param $request + * @return array + */ + public function resolveDetailsMeta($request) + { + return [ + 'authorizedToView' => $this->authorizedToView($request), + 'authorizedToCreate' => $this->authorizedToCreate($request), + 'authorizedToUpdate' => $this->authorizedToUpdate($request), + 'authorizedToDelete' => $this->authorizedToDelete($request), + ]; + } + + /** + * Return a list with relationship for the current model + * + * @param $request + * @return array + */ + public function resolveDetailsRelationships($request) + { + return []; + } + + /** + * Triggered after toArray + * + * @param $serialized + * @return array + */ + public function resolveDetails($serialized) + { + return $serialized; + } + + /** + * Return the attributes list + * + * @param $request + * @return array + */ + public function resolveIndexAttributes($request) + { + return $this->resolveDetailsAttributes($request); + } + + /** + * @param $request + * @return array + */ + public function resolveIndexMeta($request) + { + return $this->resolveDetailsMeta($request); + } + + /** + * Return a list with relationship for the current model + * + * @param $request + * @return array + */ + public function resolveIndexRelationships($request) + { + return $this->resolveDetailsRelationships($request); + } +} diff --git a/src/Restify.php b/src/Restify.php index 229f799c0..96beb11dc 100644 --- a/src/Restify.php +++ b/src/Restify.php @@ -48,6 +48,19 @@ public static function repositoryForKey($key) }); } + /** + * Get the repository class name for a given key. + * + * @param string $model + * @return string + */ + public static function repositoryForModel($model) + { + return collect(static::$repositories)->first(function ($value) use ($model) { + return $value::$model === $model; + }); + } + /** * Register the given repositories. * diff --git a/src/Services/Search/SearchService.php b/src/Services/Search/SearchService.php index 83d8a38a5..37829c8f2 100644 --- a/src/Services/Search/SearchService.php +++ b/src/Services/Search/SearchService.php @@ -17,12 +17,12 @@ class SearchService extends Searchable { /** * @param RestifyRequest $request - * @param Model $model + * @param $model * @return Builder * @throws InstanceOfException * @throws \Throwable */ - public function search(RestifyRequest $request, Model $model) + public function search(RestifyRequest $request, $model) { if (! $model instanceof RestifySearchable) { return $model->newQuery(); diff --git a/src/Traits/InteractWithSearch.php b/src/Traits/InteractWithSearch.php index c271101fd..c0b5275d1 100644 --- a/src/Traits/InteractWithSearch.php +++ b/src/Traits/InteractWithSearch.php @@ -54,35 +54,4 @@ public static function getOrderByFields() return static::$sort ?? []; } - /** - * Prepare the resource for JSON serialization. - * - * @param RestifyRequest $request - * @param array $fields - * @return array - */ - public function serializeForIndex(RestifyRequest $request, array $fields = null) - { - $serialized = [ - 'type' => $request->isResolvedByRestify() ? static::uriKey() : Str::plural(Str::kebab(class_basename(get_called_class()))), - 'attributes' => $fields ?: $this->toArray(), - 'meta' => [ - 'authorizedToView' => $this->authorizedToView($request), - 'authorizedToCreate' => $this->authorizedToCreate($request), - 'authorizedToUpdate' => $this->authorizedToUpdate($request), - 'authorizedToDelete' => $this->authorizedToDelete($request), - ], - ]; - - if ($this->getKey()) { - $serialized['id'] = $this->getKey(); - } - - return $serialized; - } - - /** - * @return array - */ - abstract public function toArray(); } From fa0585df5399ad75b532e1e5c9f70a618e57de33 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Thu, 26 Dec 2019 20:52:33 +0200 Subject: [PATCH 06/21] Apply fixes from StyleCI (#67) --- src/Controllers/RestController.php | 1 - .../Controllers/RepositoryShowController.php | 2 +- src/Http/Requests/InteractWithRepositories.php | 6 ++---- src/Repositories/Repository.php | 8 ++------ src/Repositories/RepositoryCollection.php | 16 +++++++--------- src/Repositories/ResponseResolver.php | 12 +++++------- src/Traits/InteractWithSearch.php | 4 ---- 7 files changed, 17 insertions(+), 32 deletions(-) diff --git a/src/Controllers/RestController.php b/src/Controllers/RestController.php index 071a78c49..832c2106a 100644 --- a/src/Controllers/RestController.php +++ b/src/Controllers/RestController.php @@ -9,7 +9,6 @@ use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Repository; use Binaryk\LaravelRestify\Services\Search\SearchService; -use Binaryk\LaravelRestify\Traits\PerformsQueries; use Illuminate\Config\Repository as Config; use Illuminate\Container\Container; use Illuminate\Contracts\Auth\Access\Gate; diff --git a/src/Http/Controllers/RepositoryShowController.php b/src/Http/Controllers/RepositoryShowController.php index 03524f2d4..7281e38aa 100644 --- a/src/Http/Controllers/RepositoryShowController.php +++ b/src/Http/Controllers/RepositoryShowController.php @@ -20,7 +20,7 @@ class RepositoryShowController extends RepositoryController public function handle(RestifyRequest $request) { /** - * @var Repository $repository + * @var Repository */ $repository = $request->newRepositoryWith(tap(SearchService::instance()->prepareRelations($request, $request->findModelQuery()), function ($query) use ($request) { $request->newRepository()->detailQuery($request, $query); diff --git a/src/Http/Requests/InteractWithRepositories.php b/src/Http/Requests/InteractWithRepositories.php index 9c39ecb80..af3ca5485 100644 --- a/src/Http/Requests/InteractWithRepositories.php +++ b/src/Http/Requests/InteractWithRepositories.php @@ -44,7 +44,7 @@ public function repository() ]), 404); } - if ( ! $repository::authorizedToViewAny($this)) { + if (! $repository::authorizedToViewAny($this)) { throw new UnauthorizedException(__('Unauthorized to view repository :name.', [ 'name' => $repository, ]), 403); @@ -106,7 +106,7 @@ public function isResolvedByRestify() /** * Get a new instance of the repository being requested. - * As a model it could accept either a model instance, a collection or even paginated collection + * As a model it could accept either a model instance, a collection or even paginated collection. * * @param $model * @return Repository @@ -118,7 +118,6 @@ public function newRepositoryWith($model) return new $repository($model); } - /** * Get a new, scopeless query builder for the underlying model. * @@ -153,5 +152,4 @@ public function findModelQuery($repositoryId = null) $repositoryId ?? $this->repositoryId ); } - } diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 9d371e4bd..00a9c9f85 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -16,7 +16,7 @@ /** * This class serve as repository collection and repository single model * This allow you to use all of the Laravel default repositories features (as adding headers in the response, or customizing - * response) + * response). * @author Eduard Lupacescu */ abstract class Repository extends RepositoryCollection implements RestifySearchable @@ -29,7 +29,7 @@ abstract class Repository extends RepositoryCollection implements RestifySearcha /** * This is named `resource` because of the forwarding properties from DelegatesToResource trait. - * This may be a single model or a illuminate collection, or even a paginator instance + * This may be a single model or a illuminate collection, or even a paginator instance. * * @var Model|LengthAwarePaginator */ @@ -54,9 +54,7 @@ public function __construct($model) public function model() { if ($this->isRenderingCollection() || $this->isRenderingPaginated()) { - return $this->modelFromIterator(); - } return $this->resource; @@ -101,9 +99,7 @@ public function toArray($request) $request = Container::getInstance()->make('request'); if ($this->isRenderingCollection()) { - return $this->toArrayForCollection($request); - } $serialized = [ diff --git a/src/Repositories/RepositoryCollection.php b/src/Repositories/RepositoryCollection.php index c55346fad..c0505a4ff 100644 --- a/src/Repositories/RepositoryCollection.php +++ b/src/Repositories/RepositoryCollection.php @@ -9,15 +9,13 @@ use Illuminate\Pagination\AbstractPaginator; use Illuminate\Support\Arr; - /** - * @package Binaryk\LaravelRestify\Repositories; * @author Eduard Lupacescu */ class RepositoryCollection extends Resource { /** - * When the repository is used as a response for a collection list (index controller) + * When the repository is used as a response for a collection list (index controller). * * @param $request * @return array @@ -84,7 +82,7 @@ protected function meta($paginated) /** * Check if the repository is used as a response for a list of items or for a single - * model entity + * model entity. * @return bool */ protected function isRenderingRepository() @@ -94,7 +92,7 @@ protected function isRenderingRepository() /** * Check if the repository is used as a response for a list of items or for a single - * model entity + * model entity. * @return bool */ protected function isRenderingCollection() @@ -111,21 +109,21 @@ public function isRenderingPaginated() } /** - * If collection or paginator then return model from the first item + * If collection or paginator then return model from the first item. * * @return Model */ protected function modelFromIterator() { /** - * @var ArrayIterator $iterator + * @var ArrayIterator */ $iterator = $this->iterator(); /** * This is the first element from the response collection, now we have the class of the restify - * engine - * @var Model $model + * engine. + * @var Model */ $model = $iterator->current(); diff --git a/src/Repositories/ResponseResolver.php b/src/Repositories/ResponseResolver.php index bd1176d38..00ca9556b 100644 --- a/src/Repositories/ResponseResolver.php +++ b/src/Repositories/ResponseResolver.php @@ -3,14 +3,12 @@ namespace Binaryk\LaravelRestify\Repositories; /** - * @package Binaryk\LaravelRestify\Repositories; * @author Eduard Lupacescu */ trait ResponseResolver { - /** - * Return the attributes list + * Return the attributes list. * * @param $request * @return array @@ -35,7 +33,7 @@ public function resolveDetailsMeta($request) } /** - * Return a list with relationship for the current model + * Return a list with relationship for the current model. * * @param $request * @return array @@ -46,7 +44,7 @@ public function resolveDetailsRelationships($request) } /** - * Triggered after toArray + * Triggered after toArray. * * @param $serialized * @return array @@ -57,7 +55,7 @@ public function resolveDetails($serialized) } /** - * Return the attributes list + * Return the attributes list. * * @param $request * @return array @@ -77,7 +75,7 @@ public function resolveIndexMeta($request) } /** - * Return a list with relationship for the current model + * Return a list with relationship for the current model. * * @param $request * @return array diff --git a/src/Traits/InteractWithSearch.php b/src/Traits/InteractWithSearch.php index c0b5275d1..b834508c6 100644 --- a/src/Traits/InteractWithSearch.php +++ b/src/Traits/InteractWithSearch.php @@ -2,9 +2,6 @@ namespace Binaryk\LaravelRestify\Traits; -use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Illuminate\Support\Str; - /** * @author Eduard Lupacescu */ @@ -53,5 +50,4 @@ public static function getOrderByFields() { return static::$sort ?? []; } - } From 8bc2a2a0a6621cae933283651510ba4251467413 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Fri, 27 Dec 2019 00:07:12 +0200 Subject: [PATCH 07/21] Update endpoint with validation --- routes/api.php | 1 + src/Contracts/RestifySearchable.php | 7 --- src/Fields/RulesTrait.php | 27 +++++++++ .../Controllers/RepositoryStoreController.php | 2 - .../RepositoryUpdateController.php | 53 +++++++++++++++++ src/Http/Requests/RepositoryUpdateRequest.php | 10 ++++ src/Repositories/RepositoryFillFields.php | 15 +++++ src/Repositories/ValidatingTrait.php | 59 +++++++++++++++++++ 8 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 src/Http/Controllers/RepositoryUpdateController.php create mode 100644 src/Http/Requests/RepositoryUpdateRequest.php diff --git a/routes/api.php b/routes/api.php index 03d72b80d..a70658c1b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,3 +3,4 @@ Route::get('/{repository}', 'RepositoryIndexController@handle'); Route::post('/{repository}', 'RepositoryStoreController@handle'); Route::get('/{repository}/{repositoryId}', 'RepositoryShowController@handle'); +Route::put('/{repository}/{repositoryId}', 'RepositoryUpdateController@handle'); diff --git a/src/Contracts/RestifySearchable.php b/src/Contracts/RestifySearchable.php index 237127b60..4bc2a45d2 100644 --- a/src/Contracts/RestifySearchable.php +++ b/src/Contracts/RestifySearchable.php @@ -15,13 +15,6 @@ interface RestifySearchable const MATCH_BOOL = 'bool'; const MATCH_INTEGER = 'integer'; - /** - * @param RestifyRequest $request - * @param array $fields - * @return array - */ - public function serializeForIndex(RestifyRequest $request, array $fields = []); - /** * @return array */ diff --git a/src/Fields/RulesTrait.php b/src/Fields/RulesTrait.php index 094a0a1da..114cc9094 100644 --- a/src/Fields/RulesTrait.php +++ b/src/Fields/RulesTrait.php @@ -16,6 +16,12 @@ trait RulesTrait */ public $storingRules = []; + /** + * Rules for applied when update model + * @var array + */ + public $updatingRules = []; + /** * Rules for applied when store and update. * @@ -40,6 +46,19 @@ public function storingRules($rules) return $this; } + /** + * Validation rules for update + * + * @param callable|array|string $rules + * @return RulesTrait + */ + public function updatingRules($rules) + { + $this->updatingRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; + + return $this; + } + /** * Validation rules for store. * @param callable|array|string $rules @@ -74,4 +93,12 @@ public function getStoringRules() { return array_merge($this->rules, $this->storingRules); } + + /** + * @return array + */ + public function getUpdatingRules() + { + return array_merge($this->rules, $this->updatingRules); + } } diff --git a/src/Http/Controllers/RepositoryStoreController.php b/src/Http/Controllers/RepositoryStoreController.php index 52e7a7c9a..0498eebb5 100644 --- a/src/Http/Controllers/RepositoryStoreController.php +++ b/src/Http/Controllers/RepositoryStoreController.php @@ -36,8 +36,6 @@ public function handle(RepositoryStoreRequest $request) $repository::authorizeToCreate($request); - $instance = $request->newRepository(); - $validator = $repository::validatorForStoring($request); if ($validator->fails()) { diff --git a/src/Http/Controllers/RepositoryUpdateController.php b/src/Http/Controllers/RepositoryUpdateController.php new file mode 100644 index 000000000..8443f584d --- /dev/null +++ b/src/Http/Controllers/RepositoryUpdateController.php @@ -0,0 +1,53 @@ + + */ +class RepositoryUpdateController extends RepositoryController +{ + /** + * @param RepositoryStoreRequest $request + * @return JsonResponse + * @throws BindingResolutionException + * @throws EntityNotFoundException + * @throws UnauthorizedException + * @throws AuthorizationException + * @throws Throwable + */ + public function handle(RepositoryUpdateRequest $request) + { + $model = $request->findModelQuery()->lockForUpdate()->firstOrFail(); + + $repository = $request->newRepositoryWith($model); + $repository->authorizeToUpdate($request); + $validator = $repository::validatorForUpdate($request, $repository); + + if ($validator->fails()) { + return $this->response()->invalid()->errors($validator->errors()->toArray())->respond(); + } + + + $repository = DB::transaction(function () use ($request, $repository, $model) { + + [$model] = $repository::fillWhenUpdate($request, $model); + + $model->save(); + + return $repository; + }); + + return $repository; + } +} diff --git a/src/Http/Requests/RepositoryUpdateRequest.php b/src/Http/Requests/RepositoryUpdateRequest.php new file mode 100644 index 000000000..20c8f9f59 --- /dev/null +++ b/src/Http/Requests/RepositoryUpdateRequest.php @@ -0,0 +1,10 @@ + + */ +class RepositoryUpdateRequest extends RestifyRequest +{ +} diff --git a/src/Repositories/RepositoryFillFields.php b/src/Repositories/RepositoryFillFields.php index 660bc09c5..6d07fd9ed 100644 --- a/src/Repositories/RepositoryFillFields.php +++ b/src/Repositories/RepositoryFillFields.php @@ -29,6 +29,21 @@ public static function fillWhenStore(RestifyRequest $request, $model) )]; } + /** + * @param RestifyRequest $request + * @param $model + * @return array + */ + public static function fillWhenUpdate(RestifyRequest $request, $model) + { + return [$model, static::fillFields( + $request, $model, + (new static($model))->collectFields($request) + ), static::fillExtra($request, $model, + (new static($model))->collectFields($request) + )]; + } + /** * Fill each field separately. * diff --git a/src/Repositories/ValidatingTrait.php b/src/Repositories/ValidatingTrait.php index f6d485d80..428a4211f 100644 --- a/src/Repositories/ValidatingTrait.php +++ b/src/Repositories/ValidatingTrait.php @@ -46,6 +46,41 @@ public static function validatorForStoring(RestifyRequest $request) }); } + /** + * Validate a resource update request. + * @param RestifyRequest $request + * @param null $resource + */ + public static function validateForUpdate(RestifyRequest $request, $resource = null) + { + static::validatorForUpdate($request, $resource)->validate(); + } + + + /** + * @param RestifyRequest $request + * @param null $resource + * @return \Illuminate\Contracts\Validation\Validator + */ + public static function validatorForUpdate(RestifyRequest $request, $resource = null) + { + $on = $resource ?? (new static(static::newModel())); + + $messages = $on->collectFields($request)->flatMap(function ($k) { + $messages = []; + foreach ($k->messages as $ruleFor => $message) { + $messages[$k->attribute.'.'.$ruleFor] = $message; + } + + return $messages; + })->toArray(); + + return Validator::make($request->all(), $on->getUpdatingRules($request), $messages)->after(function ($validator) use ($request) { + static::afterValidation($request, $validator); + static::afterUpdatingValidation($request, $validator); + }); + } + /** * Handle any post-validation processing. * @@ -69,6 +104,17 @@ protected static function afterStoringValidation(RestifyRequest $request, $valid { } + /** + * Handle any post-storing validation processing. + * + * @param RestifyRequest $request + * @param \Illuminate\Validation\Validator $validator + * @return void + */ + protected static function afterUpdatingValidation(RestifyRequest $request, $validator) + { + } + /** * @param RestifyRequest $request * @return array @@ -81,4 +127,17 @@ public function getStoringRules(RestifyRequest $request) ]; })->toArray(); } + + /** + * @param RestifyRequest $request + * @return array + */ + public function getUpdatingRules(RestifyRequest $request) + { + return $this->collectFields($request)->mapWithKeys(function (Field $k) { + return [ + $k->attribute => $k->getUpdatingRules() + ]; + })->toArray(); + } } From affd6e584f695c587184dbfad9943149ce290ea5 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Fri, 27 Dec 2019 00:07:35 +0200 Subject: [PATCH 08/21] Apply fixes from StyleCI (#68) --- src/Contracts/RestifySearchable.php | 2 -- src/Fields/RulesTrait.php | 4 ++-- src/Http/Controllers/RepositoryUpdateController.php | 2 -- src/Repositories/ValidatingTrait.php | 3 +-- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Contracts/RestifySearchable.php b/src/Contracts/RestifySearchable.php index 4bc2a45d2..9b232045e 100644 --- a/src/Contracts/RestifySearchable.php +++ b/src/Contracts/RestifySearchable.php @@ -2,8 +2,6 @@ namespace Binaryk\LaravelRestify\Contracts; -use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; - /** * @author Eduard Lupacescu */ diff --git a/src/Fields/RulesTrait.php b/src/Fields/RulesTrait.php index 114cc9094..27a4b6c55 100644 --- a/src/Fields/RulesTrait.php +++ b/src/Fields/RulesTrait.php @@ -17,7 +17,7 @@ trait RulesTrait public $storingRules = []; /** - * Rules for applied when update model + * Rules for applied when update model. * @var array */ public $updatingRules = []; @@ -47,7 +47,7 @@ public function storingRules($rules) } /** - * Validation rules for update + * Validation rules for update. * * @param callable|array|string $rules * @return RulesTrait diff --git a/src/Http/Controllers/RepositoryUpdateController.php b/src/Http/Controllers/RepositoryUpdateController.php index 8443f584d..838f668ee 100644 --- a/src/Http/Controllers/RepositoryUpdateController.php +++ b/src/Http/Controllers/RepositoryUpdateController.php @@ -38,9 +38,7 @@ public function handle(RepositoryUpdateRequest $request) return $this->response()->invalid()->errors($validator->errors()->toArray())->respond(); } - $repository = DB::transaction(function () use ($request, $repository, $model) { - [$model] = $repository::fillWhenUpdate($request, $model); $model->save(); diff --git a/src/Repositories/ValidatingTrait.php b/src/Repositories/ValidatingTrait.php index 428a4211f..dbebf5669 100644 --- a/src/Repositories/ValidatingTrait.php +++ b/src/Repositories/ValidatingTrait.php @@ -56,7 +56,6 @@ public static function validateForUpdate(RestifyRequest $request, $resource = nu static::validatorForUpdate($request, $resource)->validate(); } - /** * @param RestifyRequest $request * @param null $resource @@ -136,7 +135,7 @@ public function getUpdatingRules(RestifyRequest $request) { return $this->collectFields($request)->mapWithKeys(function (Field $k) { return [ - $k->attribute => $k->getUpdatingRules() + $k->attribute => $k->getUpdatingRules(), ]; })->toArray(); } From 3d7372c56651198af70222001c473885f186eb3c Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Fri, 27 Dec 2019 00:31:45 +0200 Subject: [PATCH 09/21] Destroy endpoint --- routes/api.php | 1 + .../RepositoryDestroyController.php | 47 +++++++++++++++++++ .../Requests/RepositoryDestroyRequest.php | 10 ++++ 3 files changed, 58 insertions(+) create mode 100644 src/Http/Controllers/RepositoryDestroyController.php create mode 100644 src/Http/Requests/RepositoryDestroyRequest.php diff --git a/routes/api.php b/routes/api.php index a70658c1b..737451288 100644 --- a/routes/api.php +++ b/routes/api.php @@ -4,3 +4,4 @@ Route::post('/{repository}', 'RepositoryStoreController@handle'); Route::get('/{repository}/{repositoryId}', 'RepositoryShowController@handle'); Route::put('/{repository}/{repositoryId}', 'RepositoryUpdateController@handle'); +Route::delete('/{repository}/{repositoryId}', 'RepositoryDestroyController@handle'); diff --git a/src/Http/Controllers/RepositoryDestroyController.php b/src/Http/Controllers/RepositoryDestroyController.php new file mode 100644 index 000000000..28ac9b5e3 --- /dev/null +++ b/src/Http/Controllers/RepositoryDestroyController.php @@ -0,0 +1,47 @@ + + */ +class RepositoryDestroyController extends RepositoryController +{ + /** + * @param RepositoryDestroyRequest $request + * @return JsonResponse + * @throws AuthorizationException + * @throws BindingResolutionException + * @throws EntityNotFoundException + * @throws Throwable + * @throws UnauthorizedException + */ + public function handle(RepositoryDestroyRequest $request) + { + /** + * @var Repository + */ + $repository = $request->newRepository(); + + $repository->authorizeToDelete($request); + + DB::transaction(function () use ($request, $repository) { + $model = $request->findModelQuery(); + return $model->delete(); + }); + + return $this->response() + ->code(204) + ->respond(); + } +} diff --git a/src/Http/Requests/RepositoryDestroyRequest.php b/src/Http/Requests/RepositoryDestroyRequest.php new file mode 100644 index 000000000..3ff5e9447 --- /dev/null +++ b/src/Http/Requests/RepositoryDestroyRequest.php @@ -0,0 +1,10 @@ + + */ +class RepositoryDestroyRequest extends RestifyRequest +{ +} From a03c0e606ded4ff496c338d248fd8a47dcf8486e Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Fri, 27 Dec 2019 00:32:09 +0200 Subject: [PATCH 10/21] Apply fixes from StyleCI (#69) --- src/Http/Controllers/RepositoryDestroyController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Http/Controllers/RepositoryDestroyController.php b/src/Http/Controllers/RepositoryDestroyController.php index 28ac9b5e3..6c54091c8 100644 --- a/src/Http/Controllers/RepositoryDestroyController.php +++ b/src/Http/Controllers/RepositoryDestroyController.php @@ -37,6 +37,7 @@ public function handle(RepositoryDestroyRequest $request) DB::transaction(function () use ($request, $repository) { $model = $request->findModelQuery(); + return $model->delete(); }); From 41a1c0a80825342832f8a561c8146839e16cbba5 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Fri, 3 Jan 2020 11:22:49 +0200 Subject: [PATCH 11/21] Unit tests --- src/Controllers/RestController.php | 4 +- src/Controllers/RestResponse.php | 2 +- .../RepositoryDestroyController.php | 3 +- .../Controllers/RepositoryStoreController.php | 3 +- .../RepositoryUpdateController.php | 7 +- src/Repositories/Repository.php | 3 +- .../RepositoryIndexControllerTest.php | 97 ++++++++++--------- tests/Fixtures/UserRepository.php | 7 ++ 8 files changed, 74 insertions(+), 52 deletions(-) diff --git a/src/Controllers/RestController.php b/src/Controllers/RestController.php index 832c2106a..d775dd27a 100644 --- a/src/Controllers/RestController.php +++ b/src/Controllers/RestController.php @@ -140,9 +140,9 @@ public function search($modelClass, $filters = []) $paginator = $this->paginator($modelClass, $filters); if ($modelClass instanceof Repository) { - $items = $paginator->getCollection()->mapInto(get_class($modelClass))->map->serializeForIndex($this->request()); + $items = $paginator->getCollection()->mapInto(get_class($modelClass))->map->toArray($this->request()); } else { - $items = $paginator->getCollection()->map->serializeForIndex($this->request()); + $items = $paginator->getCollection(); } return [ diff --git a/src/Controllers/RestResponse.php b/src/Controllers/RestResponse.php index 7ea83a7a3..dc88740a5 100644 --- a/src/Controllers/RestResponse.php +++ b/src/Controllers/RestResponse.php @@ -46,7 +46,7 @@ class RestResponse const REST_RESPONSE_REFRESH_CODE = 103; const REST_RESPONSE_CREATED_CODE = 201; const REST_RESPONSE_UPDATED_CODE = 201; - const REST_RESPONSE_DELETED_CODE = 204; + const REST_RESPONSE_DELETED_CODE = 204; // update or delete with success const REST_RESPONSE_BLANK_CODE = 204; const REST_RESPONSE_ERROR_CODE = 500; const REST_RESPONSE_INVALID_CODE = 400; diff --git a/src/Http/Controllers/RepositoryDestroyController.php b/src/Http/Controllers/RepositoryDestroyController.php index 28ac9b5e3..b5f786b69 100644 --- a/src/Http/Controllers/RepositoryDestroyController.php +++ b/src/Http/Controllers/RepositoryDestroyController.php @@ -2,6 +2,7 @@ namespace Binaryk\LaravelRestify\Http\Controllers; +use Binaryk\LaravelRestify\Controllers\RestResponse; use Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException; use Binaryk\LaravelRestify\Exceptions\UnauthorizedException; use Binaryk\LaravelRestify\Http\Requests\RepositoryDestroyRequest; @@ -41,7 +42,7 @@ public function handle(RepositoryDestroyRequest $request) }); return $this->response() - ->code(204) + ->code(RestResponse::REST_RESPONSE_DELETED_CODE) ->respond(); } } diff --git a/src/Http/Controllers/RepositoryStoreController.php b/src/Http/Controllers/RepositoryStoreController.php index 0498eebb5..a4f41d2ce 100644 --- a/src/Http/Controllers/RepositoryStoreController.php +++ b/src/Http/Controllers/RepositoryStoreController.php @@ -2,6 +2,7 @@ namespace Binaryk\LaravelRestify\Http\Controllers; +use Binaryk\LaravelRestify\Controllers\RestResponse; use Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException; use Binaryk\LaravelRestify\Exceptions\UnauthorizedException; use Binaryk\LaravelRestify\Http\Requests\RepositoryStoreRequest; @@ -53,7 +54,7 @@ public function handle(RepositoryStoreRequest $request) }); return $this->response() - ->code(201) + ->code(RestResponse::REST_RESPONSE_CREATED_CODE) ->forRepository($request->newRepositoryWith($model), true) ->header('Location', Restify::path().'/'.$repository::uriKey().'/'.$model->id) ->respond(); diff --git a/src/Http/Controllers/RepositoryUpdateController.php b/src/Http/Controllers/RepositoryUpdateController.php index 838f668ee..d8b6ae910 100644 --- a/src/Http/Controllers/RepositoryUpdateController.php +++ b/src/Http/Controllers/RepositoryUpdateController.php @@ -2,10 +2,12 @@ namespace Binaryk\LaravelRestify\Http\Controllers; +use Binaryk\LaravelRestify\Controllers\RestResponse; use Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException; use Binaryk\LaravelRestify\Exceptions\UnauthorizedException; use Binaryk\LaravelRestify\Http\Requests\RepositoryStoreRequest; use Binaryk\LaravelRestify\Http\Requests\RepositoryUpdateRequest; +use Binaryk\LaravelRestify\Repositories\Repository; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\JsonResponse; @@ -30,6 +32,9 @@ public function handle(RepositoryUpdateRequest $request) { $model = $request->findModelQuery()->lockForUpdate()->firstOrFail(); + /** + * @var Repository $repository + */ $repository = $request->newRepositoryWith($model); $repository->authorizeToUpdate($request); $validator = $repository::validatorForUpdate($request, $repository); @@ -46,6 +51,6 @@ public function handle(RepositoryUpdateRequest $request) return $repository; }); - return $repository; + return $this->response()->forRepository($repository)->code(RestResponse::REST_RESPONSE_DELETED_CODE)->respond(); } } diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 00a9c9f85..08e85396b 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -4,6 +4,7 @@ use Binaryk\LaravelRestify\Contracts\RestifySearchable; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; +use Binaryk\LaravelRestify\Restify; use Binaryk\LaravelRestify\Traits\InteractWithSearch; use Binaryk\LaravelRestify\Traits\PerformsQueries; use Illuminate\Container\Container; @@ -54,7 +55,7 @@ public function __construct($model) public function model() { if ($this->isRenderingCollection() || $this->isRenderingPaginated()) { - return $this->modelFromIterator(); + return $this->modelFromIterator() ?? static::newModel(); } return $this->resource; diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index 8107525e5..03f438491 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -5,6 +5,7 @@ use Binaryk\LaravelRestify\Contracts\RestifySearchable; use Binaryk\LaravelRestify\Controllers\RestController; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; +use Binaryk\LaravelRestify\Restify; use Binaryk\LaravelRestify\Tests\Fixtures\User; use Binaryk\LaravelRestify\Tests\IntegrationTest; use Mockery; @@ -28,7 +29,7 @@ public function test_list_resource() public function test_the_rest_controller_can_paginate() { - $this->mockUsers(50); + $this->mockUsers(20); $class = (new class extends RestController { public function users() @@ -76,19 +77,23 @@ public function test_search_query_works() $request = Mockery::mock(RestifyRequest::class); $request->shouldReceive('isResolvedByRestify') ->andReturnFalse(); - $expected = $users->where('email', 'eduard.lupacescu@binarcode.com')->first()->serializeForIndex($request); + $model = $users->where('email', 'eduard.lupacescu@binarcode.com')->first(); + $repository = Restify::repositoryForModel(get_class($model)); + $expected = (new $repository($model))->toArray($request); + unset($expected['relationships']); + $this->withExceptionHandling() ->getJson('/restify-api/users?search=eduard.lupacescu@binarcode.com') ->assertStatus(200) ->assertJson([ 'links' => [ - 'last_page_url' => 'http://localhost/restify-api/users?page=1', - 'next_page_url' => null, - 'path' => 'http://localhost/restify-api/users', - 'first_page_url' => 'http://localhost/restify-api/users?page=1', - 'prev_page_url' => null, + 'last' => 'http://localhost/restify-api/users?page=1', + 'next' => null, + 'first' => 'http://localhost/restify-api/users?page=1', + 'prev' => null, ], 'meta' => [ + 'path' => 'http://localhost/restify-api/users', 'current_page' => 1, 'from' => 1, 'last_page' => 1, @@ -104,11 +109,10 @@ public function test_search_query_works() ->assertStatus(200) ->assertJson([ 'links' => [ - 'last_page_url' => 'http://localhost/restify-api/users?page=1', - 'next_page_url' => null, - 'path' => 'http://localhost/restify-api/users', - 'first_page_url' => 'http://localhost/restify-api/users?page=1', - 'prev_page_url' => null, + 'next' => null, + 'last' => 'http://localhost/restify-api/users?page=1', + 'first' => 'http://localhost/restify-api/users?page=1', + 'prev' => null, ], 'meta' => [ 'current_page' => 1, @@ -116,6 +120,7 @@ public function test_search_query_works() 'last_page' => 1, 'per_page' => 15, 'to' => 1, + 'path' => 'http://localhost/restify-api/users', 'total' => 1, ], 'data' => [], @@ -129,39 +134,32 @@ public function test_that_desc_sort_query_param_works() ->assertStatus(200) ->getOriginalContent(); - $this->assertSame($response->data[0]['attributes']['id'], 10); - $this->assertSame($response->data[9]['attributes']['id'], 1); + $this->assertSame($response->getCollection()->first()->id, 10); + $this->assertSame($response->getCollection()->last()->id, 1); } public function test_that_asc_sort_query_param_works() { $this->mockUsers(10); - $response = $this->withExceptionHandling()->get('/restify-api/users?sort=+id') + $response = (array) json_decode($this->withExceptionHandling()->get('/restify-api/users?sort=+id') ->assertStatus(200) - ->getOriginalContent(); - - $this->assertSame($response->data[0]['attributes']['id'], 1); - $this->assertSame($response->data[9]['attributes']['id'], 10); + ->getContent()); - $response = $this->withExceptionHandling()->get('/restify-api/users?sort=id')//assert default ASC sort - ->assertStatus(200) - ->getOriginalContent(); - - $this->assertSame($response->data[0]['attributes']['id'], 1); - $this->assertSame($response->data[9]['attributes']['id'], 10); + $this->assertSame(data_get($response, 'data.0.id'), 1); + $this->assertSame(data_get($response, 'data.9.id'), 10); } public function test_that_default_asc_sort_query_param_works() { $this->mockUsers(10); - $response = $this->withExceptionHandling()->get('/restify-api/users?sort=id') + $response = (array) json_decode($this->withExceptionHandling()->get('/restify-api/users?sort=id') ->assertStatus(200) - ->getOriginalContent(); + ->getContent()); - $this->assertSame($response->data[0]['attributes']['id'], 1); - $this->assertSame($response->data[9]['attributes']['id'], 10); + $this->assertSame(data_get($response, 'data.0.id'), 1); + $this->assertSame(data_get($response, 'data.9.id'), 10); } public function test_that_match_param_works() @@ -169,24 +167,28 @@ public function test_that_match_param_works() User::$match = ['email' => RestifySearchable::MATCH_TEXT]; // it will automatically filter over these queries (email='test@email.com') $users = $this->mockUsers(10, ['eduard.lupacescu@binarcode.com']); $request = Mockery::mock(RestifyRequest::class); - $request->shouldReceive('isResolvedByRestify') + $request->shouldReceive('has') + ->andReturnFalse(); + $request->shouldReceive('get') ->andReturnFalse(); - $expected = $users->where('email', 'eduard.lupacescu@binarcode.com')->first()->serializeForIndex($request); - + $model = $users->where('email', 'eduard.lupacescu@binarcode.com')->first(); + $repository = Restify::repositoryForModel(get_class($model)); + $expected = (new $repository($model))->toArray($request); + unset($expected['relationships']); $this->withExceptionHandling() ->get('/restify-api/users?email=eduard.lupacescu@binarcode.com') ->assertStatus(200) ->assertJson([ 'links' => [ - 'last_page_url' => 'http://localhost/restify-api/users?page=1', - 'next_page_url' => null, - 'path' => 'http://localhost/restify-api/users', - 'first_page_url' => 'http://localhost/restify-api/users?page=1', - 'prev_page_url' => null, + 'last' => 'http://localhost/restify-api/users?page=1', + 'next' => null, + 'first' => 'http://localhost/restify-api/users?page=1', + 'prev' => null, ], 'meta' => [ 'current_page' => 1, + 'path' => 'http://localhost/restify-api/users', 'from' => 1, 'last_page' => 1, 'per_page' => 15, @@ -200,18 +202,23 @@ public function test_that_match_param_works() public function test_that_with_param_works() { User::$match = ['email' => RestifySearchable::MATCH_TEXT]; // it will automatically filter over these queries (email='test@email.com') - $users = $this->mockUsers(1); + $this->mockUsers(1); $posts = $this->mockPosts(1, 2); $request = Mockery::mock(RestifyRequest::class); - $request->shouldReceive('isResolvedByRestify') - ->andReturnFalse(); - $expected = $users->first()->serializeForIndex($request); - $expected['posts'] = $posts->toArray(); + $request->shouldReceive('has') + ->andReturnTrue(); + $request->shouldReceive('get') + ->andReturn('posts'); + $r = $this->withExceptionHandling() - ->get('/restify-api/users?with=posts') + ->getJson('/restify-api/users?with=posts') ->assertStatus(200) - ->getOriginalContent(); + ->getContent(); + $r = (array) json_decode($r); - $this->assertSameSize($r->data[0]['attributes']['posts'], $expected['posts']); + $this->assertSameSize((array) data_get($r, 'data.0.relationships.posts'), $posts->toArray()); + $this->assertSame(array_keys((array) data_get($r, 'data.0.relationships.posts.0')), [ + 'id', 'type', 'attributes', 'meta' + ]); } } diff --git a/tests/Fixtures/UserRepository.php b/tests/Fixtures/UserRepository.php index ace103caa..cb44dac58 100644 --- a/tests/Fixtures/UserRepository.php +++ b/tests/Fixtures/UserRepository.php @@ -26,4 +26,11 @@ public function fields(RestifyRequest $request) { return []; } + + public function resolveDetailsRelationships($request) + { + return [ + 'posts' => PostRepository::collection($this->whenLoaded('posts')), + ]; + } } From d5fdc909c8532480637bedece69e685886f87add Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 09:51:52 +0200 Subject: [PATCH 12/21] Moving show method to the Crudable trait --- src/Commands/CheckPassport.php | 12 ++++---- src/Controllers/RestIndexController.php | 10 ------- .../Controllers/RepositoryShowController.php | 13 +------- src/Repositories/Crudable.php | 30 +++++++++++++++++++ src/Repositories/Repository.php | 4 +-- src/RestifyServiceProvider.php | 6 ++++ .../RepositoryIndexControllerTest.php | 4 +-- 7 files changed, 46 insertions(+), 33 deletions(-) delete mode 100644 src/Controllers/RestIndexController.php create mode 100644 src/Repositories/Crudable.php diff --git a/src/Commands/CheckPassport.php b/src/Commands/CheckPassport.php index bbc559e95..37eb69ae1 100644 --- a/src/Commands/CheckPassport.php +++ b/src/Commands/CheckPassport.php @@ -62,15 +62,15 @@ public function handle() { $userClass = $this->config->get('auth.providers.users.model'); - if (false === $this->provider()) { + if (false === $this->hasProvider()) { return; } - if (false === $this->passportable($userClass)) { + if (false === $this->isPassportable($userClass)) { return; } - if (false === $this->passportClient()) { + if (false === $this->hasPassportClient()) { return; } @@ -87,7 +87,7 @@ public function handle() * @param $userClass * @return bool */ - public function passportable($userClass = null): bool + public function isPassportable($userClass = null): bool { try { $userInstance = $this->container->get($userClass); @@ -116,7 +116,7 @@ public function passportable($userClass = null): bool /** * @return bool */ - public function provider(): bool + public function hasProvider(): bool { $provider = $this->app->getProviders('Laravel\\Passport\\PassportServiceProvider'); @@ -132,7 +132,7 @@ public function provider(): bool /** * @return bool */ - public function passportClient(): bool + public function hasPassportClient(): bool { try { /** diff --git a/src/Controllers/RestIndexController.php b/src/Controllers/RestIndexController.php deleted file mode 100644 index 108bef6a6..000000000 --- a/src/Controllers/RestIndexController.php +++ /dev/null @@ -1,10 +0,0 @@ - - */ -trait RestIndexController -{ -} diff --git a/src/Http/Controllers/RepositoryShowController.php b/src/Http/Controllers/RepositoryShowController.php index 7281e38aa..0815f7c46 100644 --- a/src/Http/Controllers/RepositoryShowController.php +++ b/src/Http/Controllers/RepositoryShowController.php @@ -3,8 +3,6 @@ namespace Binaryk\LaravelRestify\Http\Controllers; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Binaryk\LaravelRestify\Repositories\Repository; -use Binaryk\LaravelRestify\Services\Search\SearchService; /** * @author Eduard Lupacescu @@ -19,15 +17,6 @@ class RepositoryShowController extends RepositoryController */ public function handle(RestifyRequest $request) { - /** - * @var Repository - */ - $repository = $request->newRepositoryWith(tap(SearchService::instance()->prepareRelations($request, $request->findModelQuery()), function ($query) use ($request) { - $request->newRepository()->detailQuery($request, $query); - })->firstOrFail()); - - $repository->authorizeToView($request); - - return $repository; + return $request->newRepositoryWith($request->findModelQuery())->show($request); } } diff --git a/src/Repositories/Crudable.php b/src/Repositories/Crudable.php new file mode 100644 index 000000000..da2276c0b --- /dev/null +++ b/src/Repositories/Crudable.php @@ -0,0 +1,30 @@ + + */ +trait Crudable +{ + /** + * @param RestifyRequest $request + * @return Repository + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Throwable + */ + public function show(RestifyRequest $request) + { + $repository = $request->newRepositoryWith(tap(SearchService::instance()->prepareRelations($request, $request->findModelQuery()), function ($query) use ($request) { + $request->newRepository()->detailQuery($request, $query); + })->firstOrFail()); + + $repository->authorizeToView($request); + + return $repository; + } +} diff --git a/src/Repositories/Repository.php b/src/Repositories/Repository.php index 08e85396b..b239cc0db 100644 --- a/src/Repositories/Repository.php +++ b/src/Repositories/Repository.php @@ -4,7 +4,6 @@ use Binaryk\LaravelRestify\Contracts\RestifySearchable; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; -use Binaryk\LaravelRestify\Restify; use Binaryk\LaravelRestify\Traits\InteractWithSearch; use Binaryk\LaravelRestify\Traits\PerformsQueries; use Illuminate\Container\Container; @@ -26,7 +25,8 @@ abstract class Repository extends RepositoryCollection implements RestifySearcha ValidatingTrait, RepositoryFillFields, PerformsQueries, - ResponseResolver; + ResponseResolver, + Crudable; /** * This is named `resource` because of the forwarding properties from DelegatesToResource trait. diff --git a/src/RestifyServiceProvider.php b/src/RestifyServiceProvider.php index fdae7d453..3c2ca49c8 100644 --- a/src/RestifyServiceProvider.php +++ b/src/RestifyServiceProvider.php @@ -5,6 +5,12 @@ use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; +/** + * This provider is injected in console context by the main provider or by the RestifyInjector + * if a restify request + * + * @package Binaryk\LaravelRestify + */ class RestifyServiceProvider extends ServiceProvider { /** diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index 03f438491..81a1d618f 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -75,9 +75,7 @@ public function test_search_query_works() { $users = $this->mockUsers(10, ['eduard.lupacescu@binarcode.com']); $request = Mockery::mock(RestifyRequest::class); - $request->shouldReceive('isResolvedByRestify') - ->andReturnFalse(); - $model = $users->where('email', 'eduard.lupacescu@binarcode.com')->first(); + $model = $users->where('email', 'eduard.lupacescu@binarcode.com')->first(); //find manually the model $repository = Restify::repositoryForModel(get_class($model)); $expected = (new $repository($model))->toArray($request); unset($expected['relationships']); From 6bedcd57af67fc8ff24e9351b23ee6cd3a5d6670 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 09:52:30 +0200 Subject: [PATCH 13/21] Apply fixes from StyleCI (#70) --- src/Http/Controllers/RepositoryUpdateController.php | 2 +- src/Repositories/Crudable.php | 1 - src/RestifyServiceProvider.php | 4 +--- tests/Controllers/RepositoryIndexControllerTest.php | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Http/Controllers/RepositoryUpdateController.php b/src/Http/Controllers/RepositoryUpdateController.php index d8b6ae910..c398c25b9 100644 --- a/src/Http/Controllers/RepositoryUpdateController.php +++ b/src/Http/Controllers/RepositoryUpdateController.php @@ -33,7 +33,7 @@ public function handle(RepositoryUpdateRequest $request) $model = $request->findModelQuery()->lockForUpdate()->firstOrFail(); /** - * @var Repository $repository + * @var Repository */ $repository = $request->newRepositoryWith($model); $repository->authorizeToUpdate($request); diff --git a/src/Repositories/Crudable.php b/src/Repositories/Crudable.php index da2276c0b..41dc510bf 100644 --- a/src/Repositories/Crudable.php +++ b/src/Repositories/Crudable.php @@ -6,7 +6,6 @@ use Binaryk\LaravelRestify\Services\Search\SearchService; /** - * @package Binaryk\LaravelRestify\Repositories; * @author Eduard Lupacescu */ trait Crudable diff --git a/src/RestifyServiceProvider.php b/src/RestifyServiceProvider.php index 3c2ca49c8..89a62b098 100644 --- a/src/RestifyServiceProvider.php +++ b/src/RestifyServiceProvider.php @@ -7,9 +7,7 @@ /** * This provider is injected in console context by the main provider or by the RestifyInjector - * if a restify request - * - * @package Binaryk\LaravelRestify + * if a restify request. */ class RestifyServiceProvider extends ServiceProvider { diff --git a/tests/Controllers/RepositoryIndexControllerTest.php b/tests/Controllers/RepositoryIndexControllerTest.php index 81a1d618f..6b24fe3bc 100644 --- a/tests/Controllers/RepositoryIndexControllerTest.php +++ b/tests/Controllers/RepositoryIndexControllerTest.php @@ -216,7 +216,7 @@ public function test_that_with_param_works() $this->assertSameSize((array) data_get($r, 'data.0.relationships.posts'), $posts->toArray()); $this->assertSame(array_keys((array) data_get($r, 'data.0.relationships.posts.0')), [ - 'id', 'type', 'attributes', 'meta' + 'id', 'type', 'attributes', 'meta', ]); } } From ed3845fd8f4abf233fd70868d927305c7d026319 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 10:17:41 +0200 Subject: [PATCH 14/21] Renaming fire to make --- src/Controllers/RestResponse.php | 6 ++++++ src/Fields/Field.php | 4 ++-- tests/Fixtures/PostRepository.php | 4 ++-- tests/Fixtures/UserRepository.php | 3 ++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Controllers/RestResponse.php b/src/Controllers/RestResponse.php index dc88740a5..f12c87e44 100644 --- a/src/Controllers/RestResponse.php +++ b/src/Controllers/RestResponse.php @@ -6,6 +6,7 @@ use Binaryk\LaravelRestify\Repositories\Repository; use Illuminate\Contracts\Routing\ResponseFactory; use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Database\Eloquent\Model; use Illuminate\Http\JsonResponse; /** @@ -471,6 +472,11 @@ public function type($type) public function forRepository(Repository $repository, $withRelations = false) { $model = $repository->model(); + + if (false === $model instanceof Model ) { + return $this; + } + if (is_null($model->getKey())) { return $this; } diff --git a/src/Fields/Field.php b/src/Fields/Field.php index 3ab14673b..0db7adba2 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -15,7 +15,7 @@ class Field extends OrganicField implements JsonSerializable /** * Column name of the field. - * @var string + * @var string|callable|null */ public $attribute; @@ -49,7 +49,7 @@ public function __construct($attribute) * @param array $arguments * @return static */ - public static function fire(...$arguments) + public static function make(...$arguments) { return new static(...$arguments); } diff --git a/tests/Fixtures/PostRepository.php b/tests/Fixtures/PostRepository.php index 0bd059867..d7f45312c 100644 --- a/tests/Fixtures/PostRepository.php +++ b/tests/Fixtures/PostRepository.php @@ -30,10 +30,10 @@ public static function uriKey() public function fields(RestifyRequest $request) { return [ - Field::fire('title')->storingRules('required')->messages([ + Field::make('title')->storingRules('required')->messages([ 'required' => 'This field is required bro.', ]), - Field::fire('description')->storingRules('required')->messages([ + Field::make('description')->storingRules('required')->messages([ 'required' => 'Description field is required bro.', ]), ]; diff --git a/tests/Fixtures/UserRepository.php b/tests/Fixtures/UserRepository.php index cb44dac58..397badde6 100644 --- a/tests/Fixtures/UserRepository.php +++ b/tests/Fixtures/UserRepository.php @@ -24,7 +24,8 @@ public static function uriKey() public function fields(RestifyRequest $request) { - return []; + return [ + ]; } public function resolveDetailsRelationships($request) From 3d5ead7c9a3a1179cdb66d66913e4b538e5695d4 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 10:17:56 +0200 Subject: [PATCH 15/21] Apply fixes from StyleCI (#71) --- src/Controllers/RestResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/RestResponse.php b/src/Controllers/RestResponse.php index f12c87e44..0d200564c 100644 --- a/src/Controllers/RestResponse.php +++ b/src/Controllers/RestResponse.php @@ -473,7 +473,7 @@ public function forRepository(Repository $repository, $withRelations = false) { $model = $repository->model(); - if (false === $model instanceof Model ) { + if (false === $model instanceof Model) { return $this; } From a5546a2bb42a3670ce50a53ef6ebcca0f68e6637 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 11:53:47 +0200 Subject: [PATCH 16/21] Move CRUD methods to the repository --- routes/api.php | 2 +- src/Controllers/RestResponse.php | 2 +- src/Fields/Field.php | 71 +++++++++++- src/Fields/OrganicField.php | 24 ++++ src/Fields/RulesTrait.php | 104 ------------------ .../RepositoryDestroyController.php | 10 +- .../Controllers/RepositoryStoreController.php | 16 +-- .../RepositoryUpdateController.php | 10 +- .../Requests/InteractWithRepositories.php | 6 +- src/Repositories/Crudable.php | 60 ++++++++++ 10 files changed, 163 insertions(+), 142 deletions(-) delete mode 100644 src/Fields/RulesTrait.php diff --git a/routes/api.php b/routes/api.php index 737451288..ed6150abb 100644 --- a/routes/api.php +++ b/routes/api.php @@ -3,5 +3,5 @@ Route::get('/{repository}', 'RepositoryIndexController@handle'); Route::post('/{repository}', 'RepositoryStoreController@handle'); Route::get('/{repository}/{repositoryId}', 'RepositoryShowController@handle'); -Route::put('/{repository}/{repositoryId}', 'RepositoryUpdateController@handle'); +Route::patch('/{repository}/{repositoryId}', 'RepositoryUpdateController@handle'); Route::delete('/{repository}/{repositoryId}', 'RepositoryDestroyController@handle'); diff --git a/src/Controllers/RestResponse.php b/src/Controllers/RestResponse.php index f12c87e44..b135d61d0 100644 --- a/src/Controllers/RestResponse.php +++ b/src/Controllers/RestResponse.php @@ -46,7 +46,7 @@ class RestResponse const REST_RESPONSE_AUTH_CODE = 401; const REST_RESPONSE_REFRESH_CODE = 103; const REST_RESPONSE_CREATED_CODE = 201; - const REST_RESPONSE_UPDATED_CODE = 201; + const REST_RESPONSE_UPDATED_CODE = 200; const REST_RESPONSE_DELETED_CODE = 204; // update or delete with success const REST_RESPONSE_BLANK_CODE = 204; const REST_RESPONSE_ERROR_CODE = 500; diff --git a/src/Fields/Field.php b/src/Fields/Field.php index 0db7adba2..1a43c325e 100644 --- a/src/Fields/Field.php +++ b/src/Fields/Field.php @@ -4,6 +4,7 @@ use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Closure; +use Illuminate\Contracts\Validation\Rule; use JsonSerializable; /** @@ -11,8 +12,6 @@ */ class Field extends OrganicField implements JsonSerializable { - use RulesTrait; - /** * Column name of the field. * @var string|callable|null @@ -133,4 +132,72 @@ public function getAttribute() { return $this->attribute; } + + /** + * Validation rules for store. + * @param callable|array|string $rules + * @return Field + */ + public function storingRules($rules) + { + $this->storingRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; + + return $this; + } + + /** + * Validation rules for update. + * + * @param callable|array|string $rules + * @return Field + */ + public function updatingRules($rules) + { + $this->updatingRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; + + return $this; + } + + /** + * Validation rules for store. + * @param callable|array|string $rules + * @return Field + */ + public function rules($rules) + { + $this->rules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; + + return $this; + } + + /** + * Validation messages. + * + * @param array $messages + * @return Field + */ + public function messages(array $messages) + { + $this->messages = $messages; + + return $this; + } + + /** + * Validation rules for storing. + * + * @return array + */ + public function getStoringRules() + { + return array_merge($this->rules, $this->storingRules); + } + + /** + * @return array + */ + public function getUpdatingRules() + { + return array_merge($this->rules, $this->updatingRules); + } } diff --git a/src/Fields/OrganicField.php b/src/Fields/OrganicField.php index 3d4c38343..cf86ee654 100644 --- a/src/Fields/OrganicField.php +++ b/src/Fields/OrganicField.php @@ -7,4 +7,28 @@ */ abstract class OrganicField extends BaseField { + /** + * Rules for applied when store. + * + * @var array + */ + public $storingRules = []; + + /** + * Rules for applied when update model. + * @var array + */ + public $updatingRules = []; + + /** + * Rules for applied when store and update. + * + * @var array + */ + public $rules = []; + + /** + * @var array + */ + public $messages = []; } diff --git a/src/Fields/RulesTrait.php b/src/Fields/RulesTrait.php deleted file mode 100644 index 27a4b6c55..000000000 --- a/src/Fields/RulesTrait.php +++ /dev/null @@ -1,104 +0,0 @@ - - */ -trait RulesTrait -{ - /** - * Rules for applied when store. - * - * @var array - */ - public $storingRules = []; - - /** - * Rules for applied when update model. - * @var array - */ - public $updatingRules = []; - - /** - * Rules for applied when store and update. - * - * @var array - */ - public $rules = []; - - /** - * @var array - */ - public $messages = []; - - /** - * Validation rules for store. - * @param callable|array|string $rules - * @return RulesTrait - */ - public function storingRules($rules) - { - $this->storingRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; - - return $this; - } - - /** - * Validation rules for update. - * - * @param callable|array|string $rules - * @return RulesTrait - */ - public function updatingRules($rules) - { - $this->updatingRules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; - - return $this; - } - - /** - * Validation rules for store. - * @param callable|array|string $rules - * @return RulesTrait - */ - public function rules($rules) - { - $this->rules = ($rules instanceof Rule || is_string($rules)) ? func_get_args() : $rules; - - return $this; - } - - /** - * Validation messages. - * - * @param array $messages - * @return RulesTrait - */ - public function messages(array $messages) - { - $this->messages = $messages; - - return $this; - } - - /** - * Validation rules for storing. - * - * @return array - */ - public function getStoringRules() - { - return array_merge($this->rules, $this->storingRules); - } - - /** - * @return array - */ - public function getUpdatingRules() - { - return array_merge($this->rules, $this->updatingRules); - } -} diff --git a/src/Http/Controllers/RepositoryDestroyController.php b/src/Http/Controllers/RepositoryDestroyController.php index 70900d151..cf3ed54dd 100644 --- a/src/Http/Controllers/RepositoryDestroyController.php +++ b/src/Http/Controllers/RepositoryDestroyController.php @@ -36,14 +36,6 @@ public function handle(RepositoryDestroyRequest $request) $repository->authorizeToDelete($request); - DB::transaction(function () use ($request, $repository) { - $model = $request->findModelQuery(); - - return $model->delete(); - }); - - return $this->response() - ->code(RestResponse::REST_RESPONSE_DELETED_CODE) - ->respond(); + return $repository->destroy($request); } } diff --git a/src/Http/Controllers/RepositoryStoreController.php b/src/Http/Controllers/RepositoryStoreController.php index a4f41d2ce..d2bd85c1d 100644 --- a/src/Http/Controllers/RepositoryStoreController.php +++ b/src/Http/Controllers/RepositoryStoreController.php @@ -43,20 +43,6 @@ public function handle(RepositoryStoreRequest $request) return $this->response()->invalid()->errors($validator->errors()->toArray())->respond(); } - $model = DB::transaction(function () use ($request, $repository) { - [$model] = $repository::fillWhenStore( - $request, $repository::newModel() - ); - - $model->save(); - - return $model; - }); - - return $this->response() - ->code(RestResponse::REST_RESPONSE_CREATED_CODE) - ->forRepository($request->newRepositoryWith($model), true) - ->header('Location', Restify::path().'/'.$repository::uriKey().'/'.$model->id) - ->respond(); + return $request->newRepositoryWith($repository::newModel())->store($request); } } diff --git a/src/Http/Controllers/RepositoryUpdateController.php b/src/Http/Controllers/RepositoryUpdateController.php index c398c25b9..296c3d96c 100644 --- a/src/Http/Controllers/RepositoryUpdateController.php +++ b/src/Http/Controllers/RepositoryUpdateController.php @@ -43,14 +43,6 @@ public function handle(RepositoryUpdateRequest $request) return $this->response()->invalid()->errors($validator->errors()->toArray())->respond(); } - $repository = DB::transaction(function () use ($request, $repository, $model) { - [$model] = $repository::fillWhenUpdate($request, $model); - - $model->save(); - - return $repository; - }); - - return $this->response()->forRepository($repository)->code(RestResponse::REST_RESPONSE_DELETED_CODE)->respond(); + return $repository->update($request, $model); } } diff --git a/src/Http/Requests/InteractWithRepositories.php b/src/Http/Requests/InteractWithRepositories.php index af3ca5485..adff405c0 100644 --- a/src/Http/Requests/InteractWithRepositories.php +++ b/src/Http/Requests/InteractWithRepositories.php @@ -122,6 +122,8 @@ public function newRepositoryWith($model) * Get a new, scopeless query builder for the underlying model. * * @return \Illuminate\Database\Eloquent\Builder + * @throws EntityNotFoundException + * @throws UnauthorizedException */ public function newQueryWithoutScopes() { @@ -132,6 +134,8 @@ public function newQueryWithoutScopes() * Get a new instance of the underlying model. * * @return \Illuminate\Database\Eloquent\Model + * @throws EntityNotFoundException + * @throws UnauthorizedException */ public function model() { @@ -149,7 +153,7 @@ public function model() public function findModelQuery($repositoryId = null) { return $this->newQueryWithoutScopes()->whereKey( - $repositoryId ?? $this->repositoryId + $repositoryId ?? request('repositoryId') ); } } diff --git a/src/Repositories/Crudable.php b/src/Repositories/Crudable.php index 41dc510bf..8b70b4001 100644 --- a/src/Repositories/Crudable.php +++ b/src/Repositories/Crudable.php @@ -2,8 +2,11 @@ namespace Binaryk\LaravelRestify\Repositories; +use Binaryk\LaravelRestify\Controllers\RestResponse; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; +use Binaryk\LaravelRestify\Restify; use Binaryk\LaravelRestify\Services\Search\SearchService; +use Illuminate\Support\Facades\DB; /** * @author Eduard Lupacescu @@ -26,4 +29,61 @@ public function show(RestifyRequest $request) return $repository; } + + /** + * @param RestifyRequest $request + * @return mixed + */ + public function store(RestifyRequest $request) + { + $model = DB::transaction(function () use ($request) { + [$model] = self::fillWhenStore( + $request, self::newModel() + ); + + $model->save(); + + return $model; + }); + + return (new static ($model)) + ->response() + ->setStatusCode(RestResponse::REST_RESPONSE_CREATED_CODE) + ->header('Location', Restify::path() . '/' . self::uriKey() . '/' . $model->id); + } + + /** + * @param RestifyRequest $request + * @param $model + * @return mixed + */ + public function update(RestifyRequest $request, $model) + { + DB::transaction(function () use ($request, $model) { + [$model] = static::fillWhenUpdate($request, $model); + + $model->save(); + + return $this; + }); + + return $this->response()->setStatusCode(RestResponse::REST_RESPONSE_UPDATED_CODE); + } + + /** + * @param RestifyRequest $request + * @return mixed + */ + public function destroy(RestifyRequest $request) + { + DB::transaction(function () use ($request) { + $model = $request->findModelQuery(); + + return $model->delete(); + }); + + return $this->response() + ->setStatusCode(RestResponse::REST_RESPONSE_DELETED_CODE); + } + } From 5aac969c48fb2befa1f9769d2f089fa44cc6d6b7 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 11:54:20 +0200 Subject: [PATCH 17/21] Apply fixes from StyleCI (#72) --- src/Http/Controllers/RepositoryDestroyController.php | 2 -- src/Http/Controllers/RepositoryStoreController.php | 3 --- src/Http/Controllers/RepositoryUpdateController.php | 2 -- src/Repositories/Crudable.php | 3 +-- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/Http/Controllers/RepositoryDestroyController.php b/src/Http/Controllers/RepositoryDestroyController.php index cf3ed54dd..384f456de 100644 --- a/src/Http/Controllers/RepositoryDestroyController.php +++ b/src/Http/Controllers/RepositoryDestroyController.php @@ -2,7 +2,6 @@ namespace Binaryk\LaravelRestify\Http\Controllers; -use Binaryk\LaravelRestify\Controllers\RestResponse; use Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException; use Binaryk\LaravelRestify\Exceptions\UnauthorizedException; use Binaryk\LaravelRestify\Http\Requests\RepositoryDestroyRequest; @@ -10,7 +9,6 @@ use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\JsonResponse; -use Illuminate\Support\Facades\DB; use Throwable; /** diff --git a/src/Http/Controllers/RepositoryStoreController.php b/src/Http/Controllers/RepositoryStoreController.php index d2bd85c1d..a91f71b2b 100644 --- a/src/Http/Controllers/RepositoryStoreController.php +++ b/src/Http/Controllers/RepositoryStoreController.php @@ -2,16 +2,13 @@ namespace Binaryk\LaravelRestify\Http\Controllers; -use Binaryk\LaravelRestify\Controllers\RestResponse; use Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException; use Binaryk\LaravelRestify\Exceptions\UnauthorizedException; use Binaryk\LaravelRestify\Http\Requests\RepositoryStoreRequest; use Binaryk\LaravelRestify\Repositories\Repository; -use Binaryk\LaravelRestify\Restify; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\JsonResponse; -use Illuminate\Support\Facades\DB; use Throwable; /** diff --git a/src/Http/Controllers/RepositoryUpdateController.php b/src/Http/Controllers/RepositoryUpdateController.php index 296c3d96c..547b9c163 100644 --- a/src/Http/Controllers/RepositoryUpdateController.php +++ b/src/Http/Controllers/RepositoryUpdateController.php @@ -2,7 +2,6 @@ namespace Binaryk\LaravelRestify\Http\Controllers; -use Binaryk\LaravelRestify\Controllers\RestResponse; use Binaryk\LaravelRestify\Exceptions\Eloquent\EntityNotFoundException; use Binaryk\LaravelRestify\Exceptions\UnauthorizedException; use Binaryk\LaravelRestify\Http\Requests\RepositoryStoreRequest; @@ -11,7 +10,6 @@ use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Http\JsonResponse; -use Illuminate\Support\Facades\DB; use Throwable; /** diff --git a/src/Repositories/Crudable.php b/src/Repositories/Crudable.php index 8b70b4001..e61ebafb8 100644 --- a/src/Repositories/Crudable.php +++ b/src/Repositories/Crudable.php @@ -49,7 +49,7 @@ public function store(RestifyRequest $request) return (new static ($model)) ->response() ->setStatusCode(RestResponse::REST_RESPONSE_CREATED_CODE) - ->header('Location', Restify::path() . '/' . self::uriKey() . '/' . $model->id); + ->header('Location', Restify::path().'/'.self::uriKey().'/'.$model->id); } /** @@ -85,5 +85,4 @@ public function destroy(RestifyRequest $request) return $this->response() ->setStatusCode(RestResponse::REST_RESPONSE_DELETED_CODE); } - } From 75d993e6642fb62cd46bd0d4ed668e898b94d8eb Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 12:31:49 +0200 Subject: [PATCH 18/21] Move index method to the repository --- src/Commands/stubs/repository.stub | 54 +++++++++++++++++-- .../Controllers/RepositoryIndexController.php | 2 +- src/Repositories/Crudable.php | 22 ++++++-- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/Commands/stubs/repository.stub b/src/Commands/stubs/repository.stub index 53884d7dc..48d868cec 100644 --- a/src/Commands/stubs/repository.stub +++ b/src/Commands/stubs/repository.stub @@ -22,11 +22,59 @@ class DummyClass extends Repository public function fields(RestifyRequest $request) { return [ - Field::fire('title')->storingRules('required')->messages([ - 'required' => 'This field is required bro.', - ]), + // Field::make('title')->storingRules('required')->messages([ + // 'required' => 'This field is required bro.', + // ]), ]; } + /** + * @param RestifyRequest $request + * @param Paginator $paginated + * @return \Illuminate\Http\JsonResponse + */ + public function index(RestifyRequest $request, Paginator $paginated) + { + return parent::index($request, $paginated); + } + + /** + * @param RestifyRequest $request + * @return \Illuminate\Http\JsonResponse + * @throws \Illuminate\Auth\Access\AuthorizationException + * @throws \Throwable + */ + public function show(RestifyRequest $request) + { + return parent::show($request); + } + + /** + * @param RestifyRequest $request + * @return \Illuminate\Http\JsonResponse + */ + public function store(RestifyRequest $request) + { + return parent::store($request); + } + + /** + * @param RestifyRequest $request + * @param $model + * @return \Illuminate\Http\JsonResponse|void + */ + public function update(RestifyRequest $request, $model) + { + return parent::update($request, $model); + } + + /** + * @param RestifyRequest $request + * @return \Illuminate\Http\JsonResponse + */ + public function destroy(RestifyRequest $request) + { + return parent::destroy($request); + } } diff --git a/src/Http/Controllers/RepositoryIndexController.php b/src/Http/Controllers/RepositoryIndexController.php index f659810bf..ad3046f19 100644 --- a/src/Http/Controllers/RepositoryIndexController.php +++ b/src/Http/Controllers/RepositoryIndexController.php @@ -22,6 +22,6 @@ public function handle(RestifyRequest $request) { $data = $this->paginator($request->newRepository()); - return $request->newRepositoryWith($data); + return $request->newRepositoryWith($data)->index($request, $data); } } diff --git a/src/Repositories/Crudable.php b/src/Repositories/Crudable.php index 8b70b4001..7c8bbce74 100644 --- a/src/Repositories/Crudable.php +++ b/src/Repositories/Crudable.php @@ -6,6 +6,8 @@ use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Restify; use Binaryk\LaravelRestify\Services\Search\SearchService; +use Illuminate\Contracts\Pagination\Paginator; +use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\DB; /** @@ -15,7 +17,17 @@ trait Crudable { /** * @param RestifyRequest $request - * @return Repository + * @param Paginator $paginated + * @return JsonResponse + */ + public function index(RestifyRequest $request, Paginator $paginated) + { + return (new static($paginated))->response(); + } + + /** + * @param RestifyRequest $request + * @return JsonResponse * @throws \Illuminate\Auth\Access\AuthorizationException * @throws \Throwable */ @@ -27,12 +39,12 @@ public function show(RestifyRequest $request) $repository->authorizeToView($request); - return $repository; + return $repository->response(); } /** * @param RestifyRequest $request - * @return mixed + * @return JsonResponse */ public function store(RestifyRequest $request) { @@ -55,7 +67,7 @@ public function store(RestifyRequest $request) /** * @param RestifyRequest $request * @param $model - * @return mixed + * @return JsonResponse */ public function update(RestifyRequest $request, $model) { @@ -72,7 +84,7 @@ public function update(RestifyRequest $request, $model) /** * @param RestifyRequest $request - * @return mixed + * @return JsonResponse */ public function destroy(RestifyRequest $request) { From 6afd2c238faacd5c966d9378050a1dc4e56739ec Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 12:42:32 +0200 Subject: [PATCH 19/21] Integration test --- src/Repositories/Crudable.php | 2 +- tests/Controllers/RepositoryStoreControllerTest.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Repositories/Crudable.php b/src/Repositories/Crudable.php index 0d8e250fd..078fcf661 100644 --- a/src/Repositories/Crudable.php +++ b/src/Repositories/Crudable.php @@ -61,7 +61,7 @@ public function store(RestifyRequest $request) return (new static ($model)) ->response() ->setStatusCode(RestResponse::REST_RESPONSE_CREATED_CODE) - ->header('Location', Restify::path().'/'.self::uriKey().'/'.$model->id); + ->header('Location', Restify::path().'/'.static::uriKey().'/'.$model->id); } /** diff --git a/tests/Controllers/RepositoryStoreControllerTest.php b/tests/Controllers/RepositoryStoreControllerTest.php index 42cb6d9cc..bc82b7b30 100644 --- a/tests/Controllers/RepositoryStoreControllerTest.php +++ b/tests/Controllers/RepositoryStoreControllerTest.php @@ -32,18 +32,18 @@ public function test_basic_validation_works() public function test_success_storing() { $user = $this->mockUsers()->first(); - $r = $this->withExceptionHandling()->post('/restify-api/posts', [ + $r = json_decode($this->withExceptionHandling()->post('/restify-api/posts', [ 'user_id' => $user->id, 'title' => 'Some post title', 'description' => 'A very short description', ]) ->assertStatus(201) ->assertHeader('Location', '/restify-api/posts/1') - ->getOriginalContent(); + ->getContent()); - $this->assertEquals($r->data->attributes['title'], 'Some post title'); - $this->assertEquals($r->data->attributes['description'], 'A very short description'); - $this->assertEquals($r->data->attributes['user_id'], $user->id); + $this->assertEquals($r->data->attributes->title, 'Some post title'); + $this->assertEquals($r->data->attributes->description, 'A very short description'); + $this->assertEquals($r->data->attributes->user_id, $user->id); $this->assertEquals($r->data->id, 1); $this->assertEquals($r->data->type, 'posts'); } From 5b5705fa6db2ddbd9b27d2dcf87c0fe09ddb2531 Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 13:21:01 +0200 Subject: [PATCH 20/21] Stub improvements --- src/Commands/stubs/repository.stub | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Commands/stubs/repository.stub b/src/Commands/stubs/repository.stub index 48d868cec..c3e51b640 100644 --- a/src/Commands/stubs/repository.stub +++ b/src/Commands/stubs/repository.stub @@ -5,6 +5,7 @@ namespace DummyNamespace; use Binaryk\LaravelRestify\Fields\Field; use Binaryk\LaravelRestify\Http\Requests\RestifyRequest; use Binaryk\LaravelRestify\Repositories\Repository; +use Illuminate\Contracts\Pagination\Paginator; class DummyClass extends Repository { From eafffc59d6d86daea893c71688d3d39bc3f2732e Mon Sep 17 00:00:00 2001 From: Lupacescu Eduard Date: Mon, 6 Jan 2020 13:34:52 +0200 Subject: [PATCH 21/21] Clean up --- src/Events/RestifyBeforeEach.php | 2 +- src/Events/RestifyStarting.php | 2 +- src/Repositories/Crudable.php | 10 ++++++++-- src/Repositories/RepositoryFillFields.php | 19 +++++++++++++------ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Events/RestifyBeforeEach.php b/src/Events/RestifyBeforeEach.php index 815f061d2..6a714f513 100644 --- a/src/Events/RestifyBeforeEach.php +++ b/src/Events/RestifyBeforeEach.php @@ -16,7 +16,7 @@ class RestifyBeforeEach /** * RestifyAfterEach constructor. - * @param Request $request + * @param $request */ public function __construct($request) { diff --git a/src/Events/RestifyStarting.php b/src/Events/RestifyStarting.php index 76de09431..863fe207f 100644 --- a/src/Events/RestifyStarting.php +++ b/src/Events/RestifyStarting.php @@ -16,7 +16,7 @@ class RestifyStarting /** * RestifyServing constructor. - * @param Request $request + * @param $request */ public function __construct($request) { diff --git a/src/Repositories/Crudable.php b/src/Repositories/Crudable.php index 078fcf661..fcfe98282 100644 --- a/src/Repositories/Crudable.php +++ b/src/Repositories/Crudable.php @@ -49,7 +49,7 @@ public function show(RestifyRequest $request) public function store(RestifyRequest $request) { $model = DB::transaction(function () use ($request) { - [$model] = self::fillWhenStore( + $model = self::fillWhenStore( $request, self::newModel() ); @@ -72,7 +72,7 @@ public function store(RestifyRequest $request) public function update(RestifyRequest $request, $model) { DB::transaction(function () use ($request, $model) { - [$model] = static::fillWhenUpdate($request, $model); + $model = static::fillWhenUpdate($request, $model); $model->save(); @@ -97,4 +97,10 @@ public function destroy(RestifyRequest $request) return $this->response() ->setStatusCode(RestResponse::REST_RESPONSE_DELETED_CODE); } + + /** + * @param null $request + * @return mixed + */ + abstract public function response($request = null); } diff --git a/src/Repositories/RepositoryFillFields.php b/src/Repositories/RepositoryFillFields.php index 6d07fd9ed..df4fdfe0f 100644 --- a/src/Repositories/RepositoryFillFields.php +++ b/src/Repositories/RepositoryFillFields.php @@ -21,12 +21,16 @@ trait RepositoryFillFields */ public static function fillWhenStore(RestifyRequest $request, $model) { - return [$model, static::fillFields( + static::fillFields( $request, $model, (new static($model))->collectFields($request) - ), static::fillExtra($request, $model, + ); + + static::fillExtra($request, $model, (new static($model))->collectFields($request) - )]; + ); + + return $model; } /** @@ -36,12 +40,15 @@ public static function fillWhenStore(RestifyRequest $request, $model) */ public static function fillWhenUpdate(RestifyRequest $request, $model) { - return [$model, static::fillFields( + static::fillFields( $request, $model, (new static($model))->collectFields($request) - ), static::fillExtra($request, $model, + ); + static::fillExtra($request, $model, (new static($model))->collectFields($request) - )]; + ); + + return $model; } /**