diff --git a/CHANGELOG.md b/CHANGELOG.md index 72d9dff..afcfd3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ All notable changes to `Weasley` will be documented in this file. ### Changed - Conformed HTTP middlewares from `http-interop/http-middleware` to `rougin/slytherin`'s own middleware - Improve code quality and code formatting with `phpstan`, `php-cs-fixer` +- `Controllers` directory to `Routes` directory +- `Middleware` directory to `Handlers` directory ### Fixed - Unit tests in running `SessionIntegration` diff --git a/README.md b/README.md index af08d8c..19dd2b9 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,19 @@ Access the generator commands through `vendor/bin/weasley` in the terminal/comma | Command | Description | | ------- | ----------- | -| make:check | Creates a new check (validation) class based on [Valitron](https://github.com/vlucas/valitron) | -| make:handler | Creates a new [Slytherin Middleware](https://github.com/rougin/slytherin/wiki/Middleware) class | -| make:package | Creates a new [Slytherin Integration](https://github.com/rougin/slytherin/wiki/IntegrationInterface-Implementation) class | -| make:route | Creates a new HTTP route class | +| make:check | Creates a new check (validation) class based on [Valitron](https://github.com/vlucas/valitron). | +| make:handler | Creates a new [Slytherin Middleware](https://github.com/rougin/slytherin/wiki/Middleware) class. | +| make:package | Creates a new [Slytherin Integration](https://github.com/rougin/slytherin/wiki/IntegrationInterface-Implementation) class. | +| make:route | Creates a new HTTP route class. | -### HTTP Controllers +### HTTP Routes | Controller | Description | | ---------- | ----------- | -| [JsonController](https://github.com/rougin/weasley/blob/master/src/Controllers/JsonController.php) | Provides methods for RESTful APIs in [JSON](https://en.wikipedia.org/wiki/JSON) format | +| [HttpRoute](https://github.com/rougin/weasley/blob/master/src/Routes/HttpRoute.php) | A simple HTTP route class for RESTful APIs. | +| [JsonRoute](https://github.com/rougin/weasley/blob/master/src/Routes/JsonRoute.php) | Provides methods for RESTful APIs in [JSON](https://en.wikipedia.org/wiki/JSON) format. | + +**NOTE**: In other PHP frameworks, this is also known as `Controllers`. ### Packages @@ -48,17 +51,20 @@ The following classes below are using the [`IntegrationInterface`](https://githu **NOTE**: The mentioned integrations above needs to include their required dependencies first. -### HTTP Middlewares +### HTTP Handlers + +The following classes below uses the [Middleware](https://github.com/rougin/slytherin/wiki/Middleware) component of Slytherin: -| Middleware | Description | +| Handler | Description | | ---------- | ----------- | -| [CrossOriginHeaders](https://github.com/rougin/weasley/blob/master/src/Middleware/CrossOriginHeaders.php) | Adds additional headers for [CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) | -| [EmptyStringToNull](https://github.com/rougin/weasley/blob/master/src/Middleware/EmptyStringToNull.php) | Converts the empty strings from request as `null` | -| [SpoofFormMethod](https://github.com/rougin/weasley/blob/master/src/Middleware/SpoofFormMethod.php) | Replaces the HTTP verb from `_method` value | -| [JsonHeaders](https://github.com/rougin/weasley/blob/master/src/Middleware/Json.php) | Changes content response to `application/json` | -| [TrimString](https://github.com/rougin/weasley/blob/master/src/Middleware/TrimString.php) | Trims the strings from an incoming request | +| [AllowCrossOrigin](https://github.com/rougin/weasley/blob/master/src/Handlers/AllowCrossOrigin.php) | Adds additional headers for [Cross-origin resource sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) (CORS). | +| [EmptyStringToNull](https://github.com/rougin/weasley/blob/master/src/Handlers/EmptyStringToNull.php) | Converts the empty strings from request as `null`. | +| [JsonContentType](https://github.com/rougin/weasley/blob/master/src/Handlers/JsonContentType.php) | Changes content response to `application/json`. | +| [MutateRequest](https://github.com/rougin/weasley/blob/master/src/Handlers/MutateRequest.php) | A middleware that can be extended to mutate/transform values from the request. | +| [SpoofHttpMethod](https://github.com/rougin/weasley/blob/master/src/Handlers/SpoofHttpMethod.php) | Replaces the HTTP verb from `_method` value. | +| [TrimStringValue](https://github.com/rougin/weasley/blob/master/src/Handlers/TrimStringValue.php) | Trims the strings from an incoming request. | -**NOTE**: All of the HTTP middlewares above are implemented in the `v0.4.1` of [PSR-15](https://github.com/http-interop/http-middleware/tree/0.4.1). +**NOTE**: In other PHP frameworks, this is also known as `Middlewares`. ## Changelog @@ -90,4 +96,4 @@ The MIT License (MIT). Please see [LICENSE][link-license] for more information. [link-coverage]: https://app.codecov.io/gh/rougin/weasley [link-downloads]: https://packagist.org/packages/rougin/weasley [link-license]: https://github.com/rougin/weasley/blob/master/LICENSE.md -[link-packagist]: https://packagist.org/packages/rougin/weasley \ No newline at end of file +[link-packagist]: https://packagist.org/packages/rougin/weasley diff --git a/src/Commands/MakeControllerCommand.php b/src/Commands/MakeControllerCommand.php index 4569bd0..84436bc 100644 --- a/src/Commands/MakeControllerCommand.php +++ b/src/Commands/MakeControllerCommand.php @@ -5,7 +5,7 @@ use Rougin\Weasley\Scripts\CreateRoute; /** - * @deprecated since v0.7, use "Scripts/CreateRoute" instead. + * @deprecated since ~0.7, use "Scripts/CreateRoute" instead. * * Make Controller Command * diff --git a/src/Commands/MakeIntegrationCommand.php b/src/Commands/MakeIntegrationCommand.php index bd37e11..ad23204 100644 --- a/src/Commands/MakeIntegrationCommand.php +++ b/src/Commands/MakeIntegrationCommand.php @@ -5,7 +5,7 @@ use Rougin\Weasley\Scripts\CreatePackage; /** - * @deprecated since v0.7, use "Scripts/CreatePackage" instead. + * @deprecated since ~0.7, use "Scripts/CreatePackage" instead. * * Make Integration Command * diff --git a/src/Commands/MakeMiddlewareCommand.php b/src/Commands/MakeMiddlewareCommand.php index 36c5b6f..87c624a 100644 --- a/src/Commands/MakeMiddlewareCommand.php +++ b/src/Commands/MakeMiddlewareCommand.php @@ -5,7 +5,7 @@ use Rougin\Weasley\Scripts\CreateHandler; /** - * @deprecated since v0.7, use "Scripts/CreateHandler" instead. + * @deprecated since ~0.7, use "Scripts/CreateHandler" instead. * * Make Middleware Command * diff --git a/src/Commands/MakeValidatorCommand.php b/src/Commands/MakeValidatorCommand.php index 4d5f227..c4d4829 100644 --- a/src/Commands/MakeValidatorCommand.php +++ b/src/Commands/MakeValidatorCommand.php @@ -5,7 +5,7 @@ use Rougin\Weasley\Scripts\CreateCheck; /** - * @deprecated since v0.7, use "Scripts/CreateCheck" instead. + * @deprecated since ~0.7, use "Scripts/CreateCheck" instead. * * Make Validator Command * diff --git a/src/Controllers/BaseController.php b/src/Controllers/BaseController.php index 1d48cf8..867ee87 100644 --- a/src/Controllers/BaseController.php +++ b/src/Controllers/BaseController.php @@ -2,88 +2,16 @@ namespace Rougin\Weasley\Controllers; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Rougin\Weasley\Transformer\JsonTransformer; +use Rougin\Weasley\Routes\HttpRoute; /** + * @deprecated since ~0.7, use "HttpRoute" instead. + * * Base Controller * * @package Weasley * @author Rougin Gutib */ -class BaseController +class BaseController extends HttpRoute { - /** - * @var \Psr\Http\Message\ServerRequestInterface - */ - protected $request; - - /** - * @var \Psr\Http\Message\ResponseInterface - */ - protected $response; - - /** - * Initializes the controller instance. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response - */ - public function __construct(ServerRequestInterface $request, ResponseInterface $response) - { - $parsed = $request->getParsedBody(); - - is_null($parsed) && $parsed = array(); - - $this->request = $request->withParsedBody($parsed); - - $this->response = $response; - } - - /** - * Sets the server request instance. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @return self - */ - public function request(ServerRequestInterface $request) - { - $this->request = $request; - - return $this; - } - - /** - * Returns the specified data to JSON. - * NOTE: Must be moved to JsonController in v1.0.0. - * The visibility of this method must also be "protected". - * - * @param mixed $data - * @param integer $code - * @param integer $options - * @return \Psr\Http\Message\ResponseInterface - */ - public function json($data, $code = 200, $options = 0) - { - $response = $this->response->withStatus($code); - - $transformer = new JsonTransformer($response, $options); - - return $transformer->transform($data); - } - - /** - * Returns the specified data to JSON. - * NOTE: To be removed in v1.0.0. Use "json" method instead. - * - * @param mixed $data - * @param integer $code - * @param integer $options - * @return \Psr\Http\Message\ResponseInterface - */ - public function toJson($data, $code = 200, $options = 0) - { - return $this->json($data, $code, $options); - } } diff --git a/src/Controllers/JsonController.php b/src/Controllers/JsonController.php index c34ec7f..8b0d2a6 100644 --- a/src/Controllers/JsonController.php +++ b/src/Controllers/JsonController.php @@ -2,275 +2,16 @@ namespace Rougin\Weasley\Controllers; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; +use Rougin\Weasley\Routes\JsonRoute; /** + * @deprecated since ~0.7, use "JsonRoute" instead. + * * JSON Controller * * @package Weasley * @author Rougin Gutib */ -class JsonController extends BaseController +class JsonController extends JsonRoute { - const ELOQUENT = 'Illuminate\Database\Eloquent\Model'; - - /** - * @var string - */ - protected $model = ''; - - /** - * @var \Illuminate\Database\Eloquent\Model - */ - protected $eloquent; - - /** - * @var string|null - */ - protected $transformer = 'Rougin\Weasley\Transformer\ApiTransformer'; - - /** - * @var \Rougin\Weasley\Validators\AbstractValidator - */ - protected $validation; - - /** - * @var string - */ - protected $validator = ''; - - /** - * Initializes the controller instance. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response - */ - public function __construct(ServerRequestInterface $request, ResponseInterface $response) - { - $this->check('validator'); - - /** @var \Rougin\Weasley\Validators\AbstractValidator */ - $validation = new $this->validator; - - $this->validation = $validation; - - $this->check('model'); - - /** @var \Illuminate\Database\Eloquent\Model */ - $model = new $this->model; - - $this->eloquent = $model; - - parent::__construct($request, $response); - } - - /** - * Deletes the specified item from storage. - * - * @param integer $id - * @return \Psr\Http\Message\ResponseInterface - */ - public function delete($id) - { - $item = $this->eloquent->find($id); - - $item->delete(); - - return $this->json(null, 204); - } - - /** - * Returns a listing of items. - * - * @return \Psr\Http\Message\ResponseInterface - */ - public function index() - { - $pagination = 'Illuminate\Pagination\LengthAwarePaginator'; - - $items = null; - - if (class_exists($pagination)) - { - $pagination = $this->pagination(); - - /** @var string[] */ - $columns = $pagination['columns']; - - /** @var integer */ - $current = $pagination['page']; - - /** @var integer */ - $filter = $pagination['limit']; - - $items = $this->eloquent->paginate($filter, $columns, 'page', $current); - } - - $items = $items ? $items : $this->eloquent->all(); - - $transformer = new $this->transformer; - - return $this->json($transformer->transform($items)); - } - - /** - * Shows the specified item. - * - * @param integer $id - * @return \Psr\Http\Message\ResponseInterface - */ - public function show($id) - { - list($code, $result) = array(200, array()); - - try - { - $item = $this->eloquent->findOrFail($id); - - $result = $item->toArray(); - } - catch (\RuntimeException $error) - { - $code = 404; - - $result = 'Specified item not found'; - } - - return $this->json($result, $code); - } - - /** - * Stores the specified item to storage. - * - * @return \Psr\Http\Message\ResponseInterface - */ - public function store() - { - $response = $this->save(); - - /** @var object|array */ - $result = $response[0]; - - /** @var integer */ - $code = $response[1]; - - if (is_object($result) && is_a($result, self::ELOQUENT)) - { - /** @var \Illuminate\Database\Eloquent\Model */ - $model = $result; - - $result = $model->toArray(); - } - - return $this->json($result, $code); - } - - /** - * Updates the specified item from storage. - * - * @param integer $id - * @return \Psr\Http\Message\ResponseInterface - */ - public function update($id) - { - $response = $this->save($id); - - /** @var object|array */ - $result = $response[0]; - - /** @var integer */ - $code = $response[1]; - - if (is_object($result) && is_a($result, self::ELOQUENT)) - { - $result = null; - } - - return $this->json($result, $code); - } - - /** - * Checks the property of the class if it has a value. - * - * @throws \UnexpectedValueException - * - * @param string $name - * @return void - */ - protected function check($name) - { - $names = array('model' => 'Eloquent model ($model)'); - - $names['validator'] = 'Validator ($validator)'; - - if ($this->{$name} === '' || $this->{$name} === null) - { - $message = ' must be defined in the controller'; - - $message = $names[$name] . $message; - - throw new \UnexpectedValueException($message); - } - } - - /** - * Define the variables needed for pagination, if available. - * - * @return array - */ - protected function pagination() - { - $query = $this->request->getQueryParams(); - - $defaults = array('limit' => null, 'page' => null); - - $defaults['columns'] = '*'; - - foreach ($defaults as $key => $value) - { - $exists = ! isset($query[$key]); - - $exists && $query[$key] = $value; - } - - $result = array('page' => $query['page']); - - $result['limit'] = $query['limit']; - - $columns = explode(',', $query['columns']); - - $result['columns'] = $columns; - - return (array) $result; - } - - /** - * Creates/updates the data to storage. - * - * @param integer|null $id - * @return array - */ - protected function save($id = null) - { - $parsed = (array) $this->request->getParsedBody(); - - if ($this->validation->validate($parsed)) - { - if (is_null($id) === false) - { - $item = $this->eloquent->find($id); - - $item->update((array) $parsed); - - return array($item, (int) 204); - } - - $item = $this->eloquent->create($parsed); - - return array($item, (int) 201); - } - - return array($this->validation->errors, 400); - } } diff --git a/src/Handlers/AllowCrossOrigin.php b/src/Handlers/AllowCrossOrigin.php new file mode 100644 index 0000000..6494b7a --- /dev/null +++ b/src/Handlers/AllowCrossOrigin.php @@ -0,0 +1,93 @@ + + */ +class AllowCrossOrigin implements MiddlewareInterface +{ + const METHODS = 'Access-Control-Allow-Methods'; + + const ORIGIN = 'Access-Control-Allow-Origin'; + + /** + * @var string[] + */ + protected $allowed = array(); + + /** + * @var string[] + */ + protected $methods = array('GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'); + + /** + * Initializes the middleware instance. + * + * @param string[]|null $allowed + * @param string[]|null $methods + */ + public function __construct(array $allowed = null, array $methods = null) + { + $this->allowed($allowed === null ? array('*') : $allowed); + + $this->methods($methods === null ? $this->methods : $methods); + } + + /** + * Process an incoming server request and return a response, optionally + * delegating to the next middleware component to create the response. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Rougin\Slytherin\Middleware\HandlerInterface $handler + * @return \Psr\Http\Message\ResponseInterface + */ + public function process(ServerRequestInterface $request, HandlerInterface $handler) + { + $options = $request->getMethod() === 'OPTIONS'; + + $response = new Response; + + if (! $options) + { + $response = $handler->handle($request); + } + + return $response->withHeader(self::ORIGIN, $this->allowed) + ->withHeader(self::METHODS, $this->methods); + } + + /** + * Sets the allowed URLS. + * + * @param string[] $allowed + * @return self + */ + public function allowed($allowed) + { + $this->allowed = $allowed; + + return $this; + } + + /** + * Sets the allowed HTTP methods. + * + * @param string[] $methods + * @return self + */ + public function methods($methods) + { + $this->methods = $methods; + + return $this; + } +} diff --git a/src/Handlers/EmptyStringToNull.php b/src/Handlers/EmptyStringToNull.php new file mode 100644 index 0000000..0a22910 --- /dev/null +++ b/src/Handlers/EmptyStringToNull.php @@ -0,0 +1,23 @@ + + */ +class EmptyStringToNull extends MutateRequest +{ + /** + * Mutates the specified value. + * + * @param mixed $value + * @return mixed + */ + protected function mutate($value) + { + return is_string($value) && $value === '' ? null : $value; + } +} diff --git a/src/Handlers/JsonContentType.php b/src/Handlers/JsonContentType.php new file mode 100644 index 0000000..bf2a9c5 --- /dev/null +++ b/src/Handlers/JsonContentType.php @@ -0,0 +1,31 @@ + + */ +class JsonContentType implements MiddlewareInterface +{ + /** + * Process an incoming server request and return a response, optionally + * delegating to the next middleware component to create the response. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Rougin\Slytherin\Middleware\HandlerInterface $handler + * @return \Psr\Http\Message\ResponseInterface + */ + public function process(ServerRequestInterface $request, HandlerInterface $handler) + { + $response = $handler->handle($request); + + return $response->withHeader('Content-Type', 'application/json'); + } +} diff --git a/src/Handlers/MutateRequest.php b/src/Handlers/MutateRequest.php new file mode 100644 index 0000000..b7b5d9e --- /dev/null +++ b/src/Handlers/MutateRequest.php @@ -0,0 +1,97 @@ + + */ +class MutateRequest implements MiddlewareInterface +{ + /** + * Process an incoming server request and return a response, optionally + * delegating to the next middleware component to create the response. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Rougin\Slytherin\Middleware\HandlerInterface $handler + * @return \Psr\Http\Message\ResponseInterface + */ + public function process(ServerRequestInterface $request, HandlerInterface $handler) + { + /** @var array */ + $query = $request->getQueryParams(); + + $get = $this->map($query); + + $request = $request->withQueryParams($get); + + /** @var array */ + $post = $request->getParsedBody(); + + $post = $this->map($post); + + $post = $request->withParsedBody($post); + + return $handler->handle($post); + } + + /** + * Maps the array to mutate each value. + * + * @param array $items + * @return array + */ + protected function map(array $items) + { + foreach ($items as $key => $value) + { + $mutated = $this->mutate($value); + + // Backward compatibility code from "mutate" --- + if ($mutated === $value) + { + $mutated = $this->transform($mutated); + } + // --------------------------------------------- + + if (is_array($value)) + { + $mutated = $this->map($value); + } + + $items[$key] = $mutated; + } + + return $items; + } + + /** + * @deprecated since ~0.7, use "mutate" instead. + * + * Transforms the specified value. + * + * @param mixed $value + * @return mixed + */ + protected function transform($value) + { + return $value; + } + + /** + * Mutates the specified value. + * + * @param mixed $value + * @return mixed + */ + protected function mutate($value) + { + return $value; + } +} diff --git a/src/Handlers/SpoofHttpMethod.php b/src/Handlers/SpoofHttpMethod.php new file mode 100644 index 0000000..c7d4b38 --- /dev/null +++ b/src/Handlers/SpoofHttpMethod.php @@ -0,0 +1,67 @@ + + */ +class SpoofHttpMethod implements MiddlewareInterface +{ + /** + * @var string + */ + protected $key = ''; + + /** + * Initializes the middleware instance. + * + * @param string|null $key + */ + public function __construct($key = null) + { + $this->key($key === null ? '_method' : $key); + } + + /** + * Process an incoming server request and return a response, optionally + * delegating to the next middleware component to create the response. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Rougin\Slytherin\Middleware\HandlerInterface $handler + * @return \Psr\Http\Message\ResponseInterface + */ + public function process(ServerRequestInterface $request, HandlerInterface $handler) + { + /** @var array */ + $parsed = $request->getParsedBody(); + + if (array_key_exists($this->key, $parsed)) + { + $method = strtoupper($parsed[$this->key]); + + $request = $request->withMethod($method); + } + + return $handler->handle($request); + } + + /** + * Sets the key for the HTTP method. + * + * @param string $key + * @return self + */ + public function key($key) + { + $this->key = $key; + + return $this; + } +} diff --git a/src/Handlers/TrimStringValue.php b/src/Handlers/TrimStringValue.php new file mode 100644 index 0000000..b3b2d53 --- /dev/null +++ b/src/Handlers/TrimStringValue.php @@ -0,0 +1,23 @@ + + */ +class TrimStringValue extends MutateRequest +{ + /** + * Mutates the specified value. + * + * @param mixed $value + * @return mixed + */ + protected function mutate($value) + { + return is_string($value) ? trim($value) : $value; + } +} diff --git a/src/Middleware/CrossOriginHeaders.php b/src/Middleware/CrossOriginHeaders.php index 41fe9a9..85a0063 100644 --- a/src/Middleware/CrossOriginHeaders.php +++ b/src/Middleware/CrossOriginHeaders.php @@ -2,88 +2,16 @@ namespace Rougin\Weasley\Middleware; -use Psr\Http\Message\ServerRequestInterface; -use Rougin\Slytherin\Middleware\HandlerInterface; -use Rougin\Slytherin\Middleware\MiddlewareInterface; -use Rougin\Slytherin\Http\Response; +use Rougin\Weasley\Handlers\AllowCrossOrigin; /** + * @deprecated since ~0.7, use "Handlers/AllowCrossOrigin" instead. + * * Cross Origin Headers Middleware * * @package Weasley * @author Rougin Gutib */ -class CrossOriginHeaders implements MiddlewareInterface +class CrossOriginHeaders extends AllowCrossOrigin { - const METHODS = 'Access-Control-Allow-Methods'; - - const ORIGIN = 'Access-Control-Allow-Origin'; - - /** - * @var string[] - */ - protected $allowed = array(); - - /** - * @var string[] - */ - protected $methods = array('GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'); - - /** - * Initializes the middleware instance. - * - * @param string[]|null $allowed - * @param string[]|null $methods - */ - public function __construct(array $allowed = null, array $methods = null) - { - $this->allowed($allowed === null ? array('*') : $allowed); - - $this->methods($methods === null ? $this->methods : $methods); - } - - /** - * Process an incoming server request and return a response, optionally - * delegating to the next middleware component to create the response. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Rougin\Slytherin\Middleware\HandlerInterface $handler - * @return \Psr\Http\Message\ResponseInterface - */ - public function process(ServerRequestInterface $request, HandlerInterface $handler) - { - $options = $request->getMethod() === 'OPTIONS'; - - $response = $options ? new Response : $handler->handle($request); - - $response = $response->withHeader(self::ORIGIN, $this->allowed); - - return $response->withHeader(self::METHODS, (array) $this->methods); - } - - /** - * Sets the allowed URLS. - * - * @param string[] $allowed - * @return self - */ - public function allowed($allowed) - { - $this->allowed = $allowed; - - return $this; - } - - /** - * Sets the allowed HTTP methods. - * - * @param string[] $methods - * @return self - */ - public function methods($methods) - { - $this->methods = $methods; - - return $this; - } } diff --git a/src/Middleware/EmptyStringToNull.php b/src/Middleware/EmptyStringToNull.php index a318e8a..ea99a07 100644 --- a/src/Middleware/EmptyStringToNull.php +++ b/src/Middleware/EmptyStringToNull.php @@ -2,22 +2,16 @@ namespace Rougin\Weasley\Middleware; +use Rougin\Weasley\Handlers\EmptyStringToNull as Handler; + /** + * @deprecated since ~0.7, use "Handlers/EmptyStringToNull" instead. + * * Empty String To Null Middleware * * @package Weasley * @author Rougin Gutib */ -class EmptyStringToNull extends TransformRequest +class EmptyStringToNull extends Handler { - /** - * Transforms the specified value. - * - * @param mixed $value - * @return mixed - */ - protected function transform($value) - { - return is_string($value) && $value === '' ? null : $value; - } } diff --git a/src/Middleware/JsonHeaders.php b/src/Middleware/JsonHeaders.php index e738a06..cce2ac8 100644 --- a/src/Middleware/JsonHeaders.php +++ b/src/Middleware/JsonHeaders.php @@ -2,32 +2,16 @@ namespace Rougin\Weasley\Middleware; -use Psr\Http\Message\ServerRequestInterface; -use Rougin\Slytherin\Middleware\HandlerInterface; -use Rougin\Slytherin\Middleware\MiddlewareInterface; +use Rougin\Weasley\Handlers\JsonContentType; /** + * @deprecated since ~0.7, use "Handlers/JsonContentType" instead. + * * JSON Headers Middleware * * @package Weasley * @author Rougin Gutib */ -class JsonHeaders implements MiddlewareInterface +class JsonHeaders extends JsonContentType { - /** - * Process an incoming server request and return a response, optionally - * delegating to the next middleware component to create the response. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Rougin\Slytherin\Middleware\HandlerInterface $handler - * @return \Psr\Http\Message\ResponseInterface - */ - public function process(ServerRequestInterface $request, HandlerInterface $handler) - { - $response = $handler->handle($request); - - $new = $response->withHeader('Content-Type', 'application/json'); - - return $response->hasHeader('Content-Type') ? $response : $new; - } } diff --git a/src/Middleware/SpoofFormMethod.php b/src/Middleware/SpoofFormMethod.php index 5d8d964..53d53f4 100644 --- a/src/Middleware/SpoofFormMethod.php +++ b/src/Middleware/SpoofFormMethod.php @@ -2,65 +2,16 @@ namespace Rougin\Weasley\Middleware; -use Psr\Http\Message\ServerRequestInterface; -use Rougin\Slytherin\Middleware\HandlerInterface; -use Rougin\Slytherin\Middleware\MiddlewareInterface; +use Rougin\Weasley\Handlers\SpoofHttpMethod; /** + * @deprecated since ~0.7, use "Handlers/SpoofHttpMethod" instead. + * * Spoof Form Method Middleware * * @package Weasley * @author Rougin Gutib */ -class SpoofFormMethod implements MiddlewareInterface +class SpoofFormMethod extends SpoofHttpMethod { - /** - * @var string - */ - protected $key = ''; - - /** - * Initializes the middleware instance. - * - * @param string|null $key - */ - public function __construct($key = null) - { - $this->key($key === null ? '_method' : $key); - } - - /** - * Process an incoming server request and return a response, optionally - * delegating to the next middleware component to create the response. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Rougin\Slytherin\Middleware\HandlerInterface $handler - * @return \Psr\Http\Message\ResponseInterface - */ - public function process(ServerRequestInterface $request, HandlerInterface $handler) - { - $parsed = $request->getParsedBody(); - - if (isset($parsed[$this->key]) === true) - { - $method = strtoupper($parsed[$this->key]); - - $request = $request->withMethod($method); - } - - return $handler->handle($request); - } - - /** - * Sets the key for the HTTP method. - * - * @param string $key - * @return self - */ - public function key($key) - { - $this->key = $key; - - return $this; - } } diff --git a/src/Middleware/TransformRequest.php b/src/Middleware/TransformRequest.php index f8990c9..912a189 100644 --- a/src/Middleware/TransformRequest.php +++ b/src/Middleware/TransformRequest.php @@ -2,69 +2,16 @@ namespace Rougin\Weasley\Middleware; -use Psr\Http\Message\ServerRequestInterface; -use Rougin\Slytherin\Middleware\HandlerInterface; -use Rougin\Slytherin\Middleware\MiddlewareInterface; +use Rougin\Weasley\Handlers\MutateRequest; /** + * @deprecated since ~0.7, use "Handlers/MutateRequest" instead. + * * Transform Request Middleware * * @package Weasley * @author Rougin Gutib */ -class TransformRequest implements MiddlewareInterface +class TransformRequest extends MutateRequest { - /** - * Process an incoming server request and return a response, optionally - * delegating to the next middleware component to create the response. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Rougin\Slytherin\Middleware\HandlerInterface $handler - * @return \Psr\Http\Message\ResponseInterface - */ - public function process(ServerRequestInterface $request, HandlerInterface $handler) - { - $get = $this->map($request->getQueryParams()); - - $request = $request->withQueryParams($get); - - $post = $request->getParsedBody(); - - $post = $this->map(is_array($post) ? $post : array()); - - $post = $request->withParsedBody($post); - - return $handler->handle($post); - } - - /** - * Maps the array to transform each value. - * - * @param array $items - * @return array - */ - protected function map(array $items) - { - foreach ((array) $items as $key => $value) - { - $new = $this->transform($value); - - is_array($value) && $new = $this->map($value); - - $items[$key] = $new; - } - - return $items; - } - - /** - * Transforms the specified value. - * - * @param mixed $value - * @return mixed - */ - protected function transform($value) - { - return $value; - } } diff --git a/src/Middleware/TrimString.php b/src/Middleware/TrimString.php index d332063..b93a0b6 100644 --- a/src/Middleware/TrimString.php +++ b/src/Middleware/TrimString.php @@ -2,22 +2,16 @@ namespace Rougin\Weasley\Middleware; +use Rougin\Weasley\Handlers\TrimStringValue; + /** + * @deprecated since ~0.7, use "Handlers/TrimStringValue" instead. + * * Trim String Middleware * * @package Weasley * @author Rougin Gutib */ -class TrimString extends TransformRequest +class TrimString extends TrimStringValue { - /** - * Transforms the specified value. - * - * @param mixed $value - * @return mixed - */ - protected function transform($value) - { - return is_string($value) ? trim($value) : $value; - } } diff --git a/src/Route.php b/src/Route.php index 110b5f6..e1276fe 100644 --- a/src/Route.php +++ b/src/Route.php @@ -2,88 +2,14 @@ namespace Rougin\Weasley; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\ServerRequestInterface; -use Rougin\Weasley\Transformer\JsonTransformer; +use Rougin\Weasley\Routes\HttpRoute; /** - * Route + * HTTP Route * * @package Weasley * @author Rougin Gutib */ -class Route +class Route extends HttpRoute { - /** - * @var \Psr\Http\Message\ServerRequestInterface - */ - protected $request; - - /** - * @var \Psr\Http\Message\ResponseInterface - */ - protected $response; - - /** - * Initializes the controller instance. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @param \Psr\Http\Message\ResponseInterface $response - */ - public function __construct(ServerRequestInterface $request, ResponseInterface $response) - { - $parsed = $request->getParsedBody(); - - is_null($parsed) && $parsed = array(); - - $this->request = $request->withParsedBody($parsed); - - $this->response = $response; - } - - /** - * Sets the server request instance. - * - * @param \Psr\Http\Message\ServerRequestInterface $request - * @return self - */ - public function request(ServerRequestInterface $request) - { - $this->request = $request; - - return $this; - } - - /** - * Returns the specified data to JSON. - * NOTE: Must be moved to JsonController in v1.0.0. - * The visibility of this method must also be "protected". - * - * @param mixed $data - * @param integer $code - * @param integer $options - * @return \Psr\Http\Message\ResponseInterface - */ - public function json($data, $code = 200, $options = 0) - { - $response = $this->response->withStatus($code); - - $transformer = new JsonTransformer($response, $options); - - return $transformer->transform($data); - } - - /** - * Returns the specified data to JSON. - * NOTE: To be removed in v1.0.0. Use "json" method instead. - * - * @param mixed $data - * @param integer $code - * @param integer $options - * @return \Psr\Http\Message\ResponseInterface - */ - public function toJson($data, $code = 200, $options = 0) - { - return $this->json($data, $code, $options); - } } diff --git a/src/Routes/HttpRoute.php b/src/Routes/HttpRoute.php new file mode 100644 index 0000000..394824c --- /dev/null +++ b/src/Routes/HttpRoute.php @@ -0,0 +1,89 @@ + + */ +class HttpRoute +{ + /** + * @var \Psr\Http\Message\ServerRequestInterface + */ + protected $request; + + /** + * @var \Psr\Http\Message\ResponseInterface + */ + protected $response; + + /** + * Initializes the controller instance. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response + */ + public function __construct(ServerRequestInterface $request, ResponseInterface $response) + { + $parsed = $request->getParsedBody(); + + is_null($parsed) && $parsed = array(); + + $this->request = $request->withParsedBody($parsed); + + $this->response = $response; + } + + /** + * Sets the server request instance. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @return self + */ + public function request(ServerRequestInterface $request) + { + $this->request = $request; + + return $this; + } + + /** + * Returns the specified data to JSON. + * NOTE: Must be moved to JsonController in v1.0.0. + * The visibility of this method must also be "protected". + * + * @param mixed $data + * @param integer $code + * @param integer $options + * @return \Psr\Http\Message\ResponseInterface + */ + public function json($data, $code = 200, $options = 0) + { + $response = $this->response->withStatus($code); + + $transformer = new JsonTransformer($response, $options); + + return $transformer->transform($data); + } + + /** + * Returns the specified data to JSON. + * NOTE: To be removed in v1.0.0. Use "json" method instead. + * + * @param mixed $data + * @param integer $code + * @param integer $options + * @return \Psr\Http\Message\ResponseInterface + */ + public function toJson($data, $code = 200, $options = 0) + { + return $this->json($data, $code, $options); + } +} diff --git a/src/Routes/JsonRoute.php b/src/Routes/JsonRoute.php new file mode 100644 index 0000000..0055bfb --- /dev/null +++ b/src/Routes/JsonRoute.php @@ -0,0 +1,276 @@ + + */ +class JsonRoute extends HttpRoute +{ + const ELOQUENT = 'Illuminate\Database\Eloquent\Model'; + + /** + * @var string + */ + protected $model = ''; + + /** + * @var \Illuminate\Database\Eloquent\Model + */ + protected $eloquent; + + /** + * @var string|null + */ + protected $transformer = 'Rougin\Weasley\Transformer\ApiTransformer'; + + /** + * @var \Rougin\Weasley\Validators\AbstractValidator + */ + protected $validation; + + /** + * @var string + */ + protected $validator = ''; + + /** + * Initializes the controller instance. + * + * @param \Psr\Http\Message\ServerRequestInterface $request + * @param \Psr\Http\Message\ResponseInterface $response + */ + public function __construct(ServerRequestInterface $request, ResponseInterface $response) + { + $this->check('validator'); + + /** @var \Rougin\Weasley\Validators\AbstractValidator */ + $validation = new $this->validator; + + $this->validation = $validation; + + $this->check('model'); + + /** @var \Illuminate\Database\Eloquent\Model */ + $model = new $this->model; + + $this->eloquent = $model; + + parent::__construct($request, $response); + } + + /** + * Deletes the specified item from storage. + * + * @param integer $id + * @return \Psr\Http\Message\ResponseInterface + */ + public function delete($id) + { + $item = $this->eloquent->find($id); + + $item->delete(); + + return $this->json(null, 204); + } + + /** + * Returns a listing of items. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function index() + { + $pagination = 'Illuminate\Pagination\LengthAwarePaginator'; + + $items = null; + + if (class_exists($pagination)) + { + $pagination = $this->pagination(); + + /** @var string[] */ + $columns = $pagination['columns']; + + /** @var integer */ + $current = $pagination['page']; + + /** @var integer */ + $filter = $pagination['limit']; + + $items = $this->eloquent->paginate($filter, $columns, 'page', $current); + } + + $items = $items ? $items : $this->eloquent->all(); + + $transformer = new $this->transformer; + + return $this->json($transformer->transform($items)); + } + + /** + * Shows the specified item. + * + * @param integer $id + * @return \Psr\Http\Message\ResponseInterface + */ + public function show($id) + { + list($code, $result) = array(200, array()); + + try + { + $item = $this->eloquent->findOrFail($id); + + $result = $item->toArray(); + } + catch (\RuntimeException $error) + { + $code = 404; + + $result = 'Specified item not found'; + } + + return $this->json($result, $code); + } + + /** + * Stores the specified item to storage. + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function store() + { + $response = $this->save(); + + /** @var object|array */ + $result = $response[0]; + + /** @var integer */ + $code = $response[1]; + + if (is_object($result) && is_a($result, self::ELOQUENT)) + { + /** @var \Illuminate\Database\Eloquent\Model */ + $model = $result; + + $result = $model->toArray(); + } + + return $this->json($result, $code); + } + + /** + * Updates the specified item from storage. + * + * @param integer $id + * @return \Psr\Http\Message\ResponseInterface + */ + public function update($id) + { + $response = $this->save($id); + + /** @var object|array */ + $result = $response[0]; + + /** @var integer */ + $code = $response[1]; + + if (is_object($result) && is_a($result, self::ELOQUENT)) + { + $result = null; + } + + return $this->json($result, $code); + } + + /** + * Checks the property of the class if it has a value. + * + * @throws \UnexpectedValueException + * + * @param string $name + * @return void + */ + protected function check($name) + { + $names = array('model' => 'Eloquent model ($model)'); + + $names['validator'] = 'Validator ($validator)'; + + if ($this->{$name} === '' || $this->{$name} === null) + { + $message = ' must be defined in the controller'; + + $message = $names[$name] . $message; + + throw new \UnexpectedValueException($message); + } + } + + /** + * Define the variables needed for pagination, if available. + * + * @return array + */ + protected function pagination() + { + $query = $this->request->getQueryParams(); + + $defaults = array('limit' => null, 'page' => null); + + $defaults['columns'] = '*'; + + foreach ($defaults as $key => $value) + { + $exists = ! isset($query[$key]); + + $exists && $query[$key] = $value; + } + + $result = array('page' => $query['page']); + + $result['limit'] = $query['limit']; + + $columns = explode(',', $query['columns']); + + $result['columns'] = $columns; + + return (array) $result; + } + + /** + * Creates/updates the data to storage. + * + * @param integer|null $id + * @return array + */ + protected function save($id = null) + { + $parsed = (array) $this->request->getParsedBody(); + + if ($this->validation->validate($parsed)) + { + if (is_null($id) === false) + { + $item = $this->eloquent->find($id); + + $item->update((array) $parsed); + + return array($item, (int) 204); + } + + $item = $this->eloquent->create($parsed); + + return array($item, (int) 201); + } + + return array($this->validation->errors, 400); + } +} diff --git a/tests/Middleware/TrimStringTest.php b/tests/Middleware/TrimStringTest.php index 878eb59..f3f18ea 100644 --- a/tests/Middleware/TrimStringTest.php +++ b/tests/Middleware/TrimStringTest.php @@ -19,7 +19,10 @@ class TrimStringTest extends AbstractTestCase */ public function testProcessMethod() { - $query = array('name' => 'Rougin ', 'address' => ' Secret'); + $query = array('name' => 'Rougin '); + $query['address'] = ' Secret'; + $query['details'] = array(); + $query['details']['sports'] = 'Programming '; $request = $this->request->withQueryParams($query); @@ -29,7 +32,10 @@ public function testProcessMethod() $response = $dispatcher->process($request, $this->handler); - $expected = array('name' => 'Rougin', 'address' => 'Secret'); + $expected = array('name' => 'Rougin'); + $expected['address'] = 'Secret'; + $expected['details'] = array(); + $expected['details']['sports'] = 'Programming'; $result = $response->getHeader('Trimmed-Query-Params');