From 1d84d01f35547bb53a78f33d41a8002f12cbf18c Mon Sep 17 00:00:00 2001 From: Arnold Daniels Date: Thu, 17 Nov 2016 21:40:25 +0100 Subject: Refactor controller. Move stuff to traits. --- composer.json | 3 +- src/Controller.php | 506 +++------------------------------------ src/Controller/CheckRequest.php | 86 +++++++ src/Controller/CheckResponse.php | 75 ++++++ src/Controller/Respond.php | 307 ++++++++++++++++++++++++ src/Controller/RouteAction.php | 87 ++++--- src/Controller/Session.php | 65 +++++ src/Controller/Session/Flash.php | 129 ++++++++++ src/Controller/View/Twig.php | 178 ++++++++++++++ src/Flash.php | 109 --------- src/View/Twig.php | 178 -------------- tests/ControllerTest.php | 81 ++++--- 12 files changed, 970 insertions(+), 834 deletions(-) create mode 100644 src/Controller/CheckRequest.php create mode 100644 src/Controller/CheckResponse.php create mode 100644 src/Controller/Respond.php create mode 100644 src/Controller/Session.php create mode 100644 src/Controller/Session/Flash.php create mode 100644 src/Controller/View/Twig.php delete mode 100644 src/Flash.php delete mode 100644 src/View/Twig.php diff --git a/composer.json b/composer.json index 4a67d09..90e4820 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "require": { "php": ">=5.6.0", "psr/http-message": "^1.0", - "jasny/php-functions": "^2.0" + "jasny/php-functions": "^2.0", + "dflydev/apache-mime-types": "^1.0" }, "require-dev": { "jasny/php-code-quality": "^2.0", diff --git a/src/Controller.php b/src/Controller.php index 9f69160..93ba38d 100644 --- a/src/Controller.php +++ b/src/Controller.php @@ -10,6 +10,11 @@ use Psr\Http\Message\ResponseInterface; */ abstract class Controller { + use Controller\Respond, + Controller\CheckRequest, + Controller\CheckResponse, + Controller\Session; + /** * Server request * @var ServerRequestInterface @@ -22,46 +27,18 @@ abstract class Controller **/ protected $response = null; - /** - * Common input and output formats with associated MIME - * @var array - */ - protected $contentFormats = [ - 'text/html' => 'html', - 'application/json' => 'json', - 'application/xml' => 'xml', - 'text/xml' => 'xml', - 'text/plain' => 'text', - 'application/javascript' => 'js', - 'text/css' => 'css', - 'image/png' => 'png', - 'image/gif' => 'gif', - 'image/jpeg' => 'jpeg', - 'image/x-icon' => 'ico', - 'application/x-www-form-urlencoded' => 'post', - 'multipart/form-data' => 'post' - ]; /** - * Flash - * @var Flash - */ - protected $flash = null; - - /** - * Run the controller - * - * @return ResponseInterface - */ - abstract public function run(); - - /** * Get request, set for controller * * @return ServerRequestInterface */ - public function getRequest() + protected function getRequest() { + if (!isset($this->request)) { + throw new \RuntimeException("Request not set, the controller has not been invoked"); + } + return $this->request; } @@ -70,470 +47,47 @@ abstract class Controller * * @return ResponseInterface */ - public function getResponse() + protected function getResponse() { + if (!isset($this->response)) { + throw new \RuntimeException("Response not set, the controller has not been invoked"); + } + return $this->response; } /** - * Run the controller as function + * Get response. set for controller * - * @param ServerRequestInterface $request - * @param ResponseInterface $response * @return ResponseInterface */ - public function __invoke(ServerRequestInterface $request, ResponseInterface $response) + protected function setResponse(ResponseInterface $response) { - $this->request = $request; $this->response = $response; - - return $this->run(); - } - - /** - * Set the headers with HTTP status code and content type. - * @link http://en.wikipedia.org/wiki/List_of_HTTP_status_codes - * - * Examples: - * - * $this->responseWith(200, 'json'); - * $this->responseWith(200, 'application/json'); - * $this->responseWith(204); - * $this->responseWith("204 Created"); - * $this->responseWith('json'); - * - * - * @param int $code HTTP status code (may be omitted) - * @param string|array $format Mime or content format - * @return ResponseInterface $response - */ - public function responseWith($code, $format = null) - { - $response = $this->getResponse(); - - // Shift arguments if $code is omitted - if (!is_int($code) && !preg_match('/^\d{3}\b/', $code)) { - list($code, $format) = array_merge([null], func_get_args()); - } - - if ($code) { - $response = $response->withStatus((int)$code); - } - - if ($format) { - $contentType = $this->getContentType($format); - $response = $response->withHeader('Content-Type', $contentType); - } - - return $response; - } - - /** - * Response with success 200 code - * - * @return ResponseInterface $response - */ - public function ok() - { - return $this->responseWith(200); - } - - /** - * Response with created 201 code, and optionaly redirect to created location - * - * @param string $location Url of created resource - * @return ResponseInterface $response - */ - public function created($location = '') - { - $response = $this->responseWith(201); - - if ($location) { - $response = $response->withHeader('Location', $location); - } - - return $response; - } - - /** - * Response with 204 'No Content' - * - * @return ResponseInterface $response - */ - public function noContent() - { - return $this->responseWith(204); - } - - /** - * Redirect to url - * - * @param string $url - * @param int $code 301 (Moved Permanently), 303 (See Other) or 307 (Temporary Redirect) - * @return ResponseInterface $response - */ - public function redirect($url, $code = 303) - { - $response = $this->responseWith($code, 'html'); - $response = $response->withHeader('Location', $url); - $response->getBody()->write('You are being redirected to ' . $url . ''); - - return $response; - } - - /** - * Redirect to previous page, or to home page - * - * @return ResponseInterface $response - */ - public function back() - { - return $this->redirect($this->getLocalReferer() ?: '/'); - } - - /** - * Route to 401 - * Note: While the 401 route is used, we don't respond with a 401 http status code. - * - * @return ResponseInterface $response - */ - public function requireLogin() - { - return $this->redirect('/401'); - } - - /** - * Alias of requireLogin - * - * @return ResponseInterface $response - */ - public function requireAuth() - { - return $this->requireLogin(); - } - - /** - * Set response to error 'Bad Request' state - * - * @param string $message - * @param int $code HTTP status code - * @return ResponseInterface $response - */ - public function badRequest($message, $code = 400) - { - return $this->error($message, $code); - } - - /** - * Set response to error 'Forbidden' state - * - * @param string $message - * @param int $code HTTP status code - * @return ResponseInterface $response - */ - public function forbidden($message, $code = 403) - { - return $this->error($message, $code); - } - - /** - * Set response to error 'Not Found' state - * - * @param string $message - * @param int $code HTTP status code - * @return ResponseInterface $response - */ - public function notFound($message, $code = 404) - { - return $this->error($message, $code); - } - - /** - * Set response to error 'Conflict' state - * - * @param string $message - * @param int $code HTTP status code - * @return ResponseInterface $response - */ - public function conflict($message, $code = 409) - { - return $this->error($message, $code); - } - - /** - * Set response to error 'Too Many Requests' state - * - * @param string $message - * @param int $code HTTP status code - * @return ResponseInterface $response - */ - public function tooManyRequests($message, $code = 429) - { - return $this->error($message, $code); - } - - /** - * Set response to error state - * - * @param string $message - * @param int $code HTTP status code - * @return ResponseInterface $response - */ - public function error($message, $code = 400) - { - $response = $this->getResponse(); - - $errorResponse = $response->withStatus($code); - $errorResponse->getBody()->write($message); - - return $errorResponse; - } - - /** - * Set the flash message and/or return the flash object. - * - * @param mixed $type flash type, eg. 'error', 'notice' or 'success' - * @param mixed $message flash message - * @return Flash - */ - public function flash($type = null, $message = null) - { - if (!isset($this->flash)) $this->flash = new Flash(); - if ($type && $message) $this->flash->set($type, $message); - - return $this->flash; - } - - /** - * Check if response is 2xx succesful, or empty - * - * @return boolean - */ - public function isSuccessful() - { - $code = $this->getResponseStatusCode(); - - return !$code || ($code >= 200 && $code < 300); - } - - /** - * Check if response is a 3xx redirect - * - * @return boolean - */ - public function isRedirection() - { - $code = $this->getResponseStatusCode(); - - return $code >= 300 && $code < 400; } /** - * Check if response is a 4xx client error - * - * @return boolean - */ - public function isClientError() - { - $code = $this->getResponseStatusCode(); - - return $code >= 400 && $code < 500; - } - - /** - * Check if response is a 5xx redirect - * - * @return boolean - */ - public function isServerError() - { - return $this->getResponseStatusCode() >= 500; - } - - /** - * Check if response is 4xx or 5xx error - * - * @return boolean - */ - public function isError() - { - return $this->isClientError() || $this->isServerError(); - } - - /** - * Check if request is GET request - * - * @return boolean - */ - public function isGetRequest() - { - $method = $this->getRequestMethod(); - - return !$method || $method === 'GET'; - } - - /** - * Check if request is POST request - * - * @return boolean - */ - public function isPostRequest() - { - return $this->getRequestMethod() === 'POST'; - } - - /** - * Check if request is PUT request - * - * @return boolean - */ - public function isPutRequest() - { - return $this->getRequestMethod() === 'PUT'; - } - - /** - * Check if request is DELETE request - * - * @return boolean - */ - public function isDeleteRequest() - { - return $this->getRequestMethod() === 'DELETE'; - } - - /** - * Check if request is HEAD request - * - * @return boolean - */ - public function isHeadRequest() - { - return $this->getRequestMethod() === 'HEAD'; - } - - /** - * Returns the HTTP referer if it is on the current host - * - * @return string - */ - public function getLocalReferer() - { - $request = $this->getRequest(); - $referer = $request->getHeaderLine('HTTP_REFERER'); - $host = $request->getHeaderLine('HTTP_HOST'); - - return $referer && parse_url($referer, PHP_URL_HOST) === $host ? $referer : ''; - } - - /** - * Output result - * - * @param mixed $data - * @param string $format - * @return ResponseInterface $response - */ - public function output($data, $format) - { - $response = $this->getResponse(); - $contentType = $this->getContentType($format); - $response = $response->withHeader('Content-Type', $contentType); - $content = is_scalar($data) ? $data : $this->encodeData($data, $format); - - $response->getBody()->write($content); - - return $response; - } - - /** - * Encode data to send to client - * - * @param mixed $data - * @param string $format - * @return string - */ - public function encodeData($data, $format) - { - switch ($format) { - case 'json': return $this->encodeDataAsJson($data); - case 'xml': return $this->encodeDataAsXml($data); - case 'html': - throw new \InvalidArgumentException("To encode HTML please use a view"); - default: - throw new \InvalidArgumentException("Can not encode data for format '$format'"); - } - } - - /** - * Encode data as xml - * - * @param \SimpleXMLElement $data - * @return string - */ - protected function encodeDataAsXml(\SimpleXMLElement $data) - { - return $data->asXML(); - } - - /** - * Encode data as json - * - * @param mixed - * @return string - */ - protected function encodeDataAsJson($data) - { - $data = json_encode($data); - - return $this->isJsonp() ? - $this->getRequest()->getQueryParams()['callback'] . '(' . $data . ')' : - $data; - } - - /** - * Check if we should respond with jsonp - * - * @return boolean - */ - protected function isJsonp() - { - $request = $this->getRequest(); - - return $request && !empty($request->getQueryParams()['callback']); - } - - /** - * Get status code of response + * Run the controller * - * @return int + * @return ResponseInterface */ - protected function getResponseStatusCode() - { - $response = $this->getResponse(); - - return $response ? $response->getStatusCode() : 0; - } + abstract protected function run(); - /** - * Get valid content type by simple word description - * - * @param string $format - * @return string - */ - protected function getContentType($format) - { - return array_search($format, $this->contentFormats) ?: $format; - } /** - * Get method of request + * Run the controller as function * - * @return string + * @param ServerRequestInterface $request + * @param ResponseInterface $response + * @return ResponseInterface */ - protected function getRequestMethod() + public function __invoke(ServerRequestInterface $request, ResponseInterface $response) { - $request = $this->getRequest(); + $this->request = $request; + $this->response = $response; - return $request ? $request->getMethod() : ''; + $this->useSession(); + + return $this->run(); } } diff --git a/src/Controller/CheckRequest.php b/src/Controller/CheckRequest.php new file mode 100644 index 0000000..a63a2b7 --- /dev/null +++ b/src/Controller/CheckRequest.php @@ -0,0 +1,86 @@ +getRequest()->getMethod(); + + return !$method || $method === 'GET'; + } + + /** + * Check if request is POST request + * + * @return boolean + */ + public function isPostRequest() + { + return $this->getRequest()->getMethod() === 'POST'; + } + + /** + * Check if request is PUT request + * + * @return boolean + */ + public function isPutRequest() + { + return $this->getRequest()->getMethod() === 'PUT'; + } + + /** + * Check if request is DELETE request + * + * @return boolean + */ + public function isDeleteRequest() + { + return $this->getRequest()->getMethod() === 'DELETE'; + } + + /** + * Check if request is HEAD request + * + * @return boolean + */ + public function isHeadRequest() + { + return $this->getRequest()->getMethod() === 'HEAD'; + } + + + /** + * Returns the HTTP referer if it is on the current host + * + * @return string + */ + public function getLocalReferer() + { + $request = $this->getRequest(); + $referer = $request->getHeaderLine('HTTP_REFERER'); + $host = $request->getHeaderLine('HTTP_HOST'); + + return $referer && parse_url($referer, PHP_URL_HOST) === $host ? $referer : ''; + } +} \ No newline at end of file diff --git a/src/Controller/CheckResponse.php b/src/Controller/CheckResponse.php new file mode 100644 index 0000000..7c02d46 --- /dev/null +++ b/src/Controller/CheckResponse.php @@ -0,0 +1,75 @@ +getResponse()->getStatusCode() ?: 200; + + return !$code || ($code >= 200 && $code < 300); + } + + /** + * Check if response is a 3xx redirect + * + * @return boolean + */ + public function isRedirection() + { + $code = $this->getResponse()->getStatusCode() ?: 200; + + return $code >= 300 && $code < 400; + } + + /** + * Check if response is a 4xx client error + * + * @return boolean + */ + public function isClientError() + { + $code = $this->getResponse()->getStatusCode() ?: 200; + + return $code >= 400 && $code < 500; + } + + /** + * Check if response is a 5xx redirect + * + * @return boolean + */ + public function isServerError() + { + return $this->getResponse()->getStatusCode() ?: 200 >= 500; + } + + /** + * Check if response is 4xx or 5xx error + * + * @return boolean + */ + public function isError() + { + return $this->isClientError() || $this->isServerError(); + } +} diff --git a/src/Controller/Respond.php b/src/Controller/Respond.php new file mode 100644 index 0000000..02b2cfb --- /dev/null +++ b/src/Controller/Respond.php @@ -0,0 +1,307 @@ +getResponse()->$fn($value, $header); + + $this->setResponse($response); + } + + /** + * Set the headers with HTTP status code and content type. + * @link http://en.wikipedia.org/wiki/List_of_HTTP_status_codes + * + * Examples: + * + * $this->respondWith(200, 'json'); + * $this->respondWith(200, 'application/json'); + * $this->respondWith(204); + * $this->respondWith("204 Created"); + * $this->respondWith('json'); + * + * + * @param int $code HTTP status code (may be omitted) + * @param string|array $format Mime or content format + */ + public function respondWith($code, $format = null) + { + $response = $this->getResponse(); + + // Shift arguments if $code is omitted + if (!is_int($code) && !preg_match('/^\d{3}\b/', $code)) { + list($code, $format) = array_merge([null], func_get_args()); + } + + if ($code) { + $response = $response->withStatus((int)$code); + } + + if ($format) { + $contentType = $this->getContentType($format); + $response = $response->withHeader('Content-Type', $contentType); + } + + $this->setResponse($response); + } + + + /** + * Response with 200 OK + * + * @return ResponseInterface $response + */ + public function ok() + { + $this->respondWith(200); + } + + /** + * Response with created 201 code, and optionaly redirect to created location + * + * @param string $location Url of created resource + */ + public function created($location = '') + { + $this->respondWith(201); + + if ($location) { + $this->setResponseHeader('Location', $location); + } + } + + /** + * Response with 204 No Content + */ + public function noContent() + { + $this->respondWith(204); + } + + /** + * Redirect to url + * + * @param string $url + * @param int $code 301 (Moved Permanently), 303 (See Other) or 307 (Temporary Redirect) + */ + public function redirect($url, $code = 303) + { + $this->respondWith($code, 'text/html'); + $this->setResponseHeader('Location', $url); + + $urlHtml = htmlentities($url); + $this->output('You are being redirected to ' . $urlHtml . ''); + } + + /** + * Redirect to previous page, or to home page + * + * @return ResponseInterface $response + */ + public function back() + { + $this->redirect($this->getLocalReferer() ?: '/'); + } + + + /** + * Respond with 400 Bad Request + * + * @param string $message + * @param int $code HTTP status code + */ + public function badRequest($message, $code = 400) + { + $this->respondWith($code); + $this->output($message); + } + + /** + * Respond with a 401 Unauthorized + */ + public function requireAuth() + { + $this->respondWith(401); + } + + /** + * Alias of requireAuth + * @deprecated + */ + final public function requireLogin() + { + $this->requireAuth(); + } + + /** + * Respond with 402 Payment Required + * + * @param string $message + */ + public function paymentRequired($message = "Payment required") + { + $this->respondWith(402); + $this->output($message); + } + + /** + * Respond with 403 Forbidden + * + * @param string $message + */ + public function forbidden($message = "Forbidden") + { + $this->respondWith(403); + $this->output($message); + } + + /** + * Respond with 404 Not Found + * + * @param string $message + * @param int $code HTTP status code (404 or 405) + */ + public function notFound($message = "Not found", $code = 404) + { + $this->respondWith($code); + $this->output($message); + } + + /** + * Respond with 409 Conflict + * + * @param string $message + */ + public function conflict($message) + { + $this->respondWith(409); + $this->output($message); + } + + /** + * Respond with 429 Too Many Requests + * + * @param string $message + */ + public function tooManyRequests($message = "Too many requests") + { + $this->respondWith(429); + $this->output($message); + } + + + /** + * Respond with a server error + * + * @param string $message + * @param int $code HTTP status code + */ + public function error($message = "An unexpected error occured", $code = 500) + { + $this->respondWith($code); + $this->output($message); + } + + + /** + * Get MIME type for extension + * + * @param string $format + * @return string + */ + protected function getContentType($format) + { + if (\Jasny\str_contains($format, '/')) { // Already MIME + return $format; + } + + $repository = new ApacheMimeTypes(); + $mime = $repository->findType('html'); + + if (!isset($mime)) { + throw new \UnexpectedValueException("Format $format doesn't correspond with a MIME type"); + } + + return $mime; + } + + /** + * Serialize data + * + * @param mixed $data + * @param string $contentType + * @return string + */ + protected function serializeData($data, $contentType) + { + if ($contentType == 'json') { + return (is_string($data) && (json_decode($data) !== null || !json_last_error())) + ? $data : json_encode($data); + } + + if (!is_scalar($data)) { + throw new \Exception("Unable to serialize data to {$contentType}"); + } + + return $data; + } + + /** + * Output result + * + * @param mixed $data + * @param string $format Output format as MIME or extension + */ + public function output($data, $format = null) + { + if (!isset($format)) { + $contentType = $this->getResponse()->getHeaderLine('Content-Type') ?: 'text/html'; + } else { + $contentType = $this->getContentType($format); + $this->setResponseHeader('Content-Type', $contentType); + } + + $content = $this->serializeData($data, $contentType); + + $this->getResponse()->getBody()->write($content); + } +} diff --git a/src/Controller/RouteAction.php b/src/Controller/RouteAction.php index dabd501..5303f8a 100644 --- a/src/Controller/RouteAction.php +++ b/src/Controller/RouteAction.php @@ -15,40 +15,75 @@ trait RouteAction * * @return ServerRequestInterface */ - abstract public function getRequest(); + abstract protected function getRequest(); - /** + /** * Get response. set for controller * * @return ResponseInterface */ - abstract public function getResponse(); + abstract protected function getResponse(); + + /** + * Respond with a server error + * + * @param string $message + * @param int $code HTTP status code + */ + abstract public function notFound($message = '', $code = 404); /** + * Check if response is 2xx succesful, or empty + * + * @return boolean + */ + abstract public function isSuccessful(); + + + /** + * Called before executing the action. + * If the response is no longer a success statuc (>= 300), the action will not be executed. + * + * + * protected function beforeAction() + * { + * $this->respondWith('json'); // Respond with JSON by default + * + * if ($this->auth->getUser()->getCredits() <= 0) { + * $this->paymentRequired(); + * } + * } + * + */ + protected function beforeAction() + { + } + + /** * Run the controller * * @return ResponseInterface */ - public function run() { - $request = $this->getRequest(); - $route = $request->getAttribute('route'); + public function run() + { + $route = $this->getRequest()->getAttribute('route'); $method = $this->getActionMethod(isset($route->action) ? $route->action : 'default'); - + if (!method_exists($this, $method)) { - return $this->setResponseError(404, 'Not Found'); + return $this->notFound(); } - try { - $args = isset($route->args) ? - $route->args : - $this->getFunctionArgs($route, new \ReflectionMethod($this, $method)); - } catch (\RuntimeException $e) { - return $this->setResponseError(400, 'Bad Request'); - } + $args = isset($route->args) + ? $route->args + : $this->getFunctionArgs($route, new \ReflectionMethod($this, $method)); - $response = call_user_func_array([$this, $method], $args); + $this->beforeAction(); + + if ($this->isSuccessful()) { + call_user_func_array([$this, $method], $args); + } - return $response ?: $this->getResponse(); + return $this->getResponse(); } /** @@ -96,22 +131,4 @@ trait RouteAction return $args; } - - /** - * Set response to error state - * - * @param int $code - * @param string $message - * @return ResponseInterface - */ - protected function setResponseError($code, $message) - { - $response = $this->getResponse(); - - $errorResponse = $response->withStatus($code); - $errorResponse->getBody()->write($message); - - return $errorResponse; - } } - diff --git a/src/Controller/Session.php b/src/Controller/Session.php new file mode 100644 index 0000000..8dc292f --- /dev/null +++ b/src/Controller/Session.php @@ -0,0 +1,65 @@ +session = $this->getRequest()->getAttribute('session'); + + if (!isset($this->session)) { + $this->session =& $_SESSION; + } + } + + + /** + * Get an/or set the flash message. + * + * @param mixed $type flash type, eg. 'error', 'notice' or 'success' + * @param mixed $message flash message + * @return Flash + */ + public function flash($type = null, $message = null) + { + if (!isset($this->flash)) { + $this->flash = new Flash($this->session); + } + + if ($type) { + $this->flash->set($type, $message); + } + + return $this->flash; + } +} diff --git a/src/Controller/Session/Flash.php b/src/Controller/Session/Flash.php new file mode 100644 index 0000000..be72e7c --- /dev/null +++ b/src/Controller/Session/Flash.php @@ -0,0 +1,129 @@ +session =& $session; + } + + /** + * Check if the flash is set. + * + * @return boolean + */ + public function isIssued() + { + return isset($this->session[$this->key]); + } + + /** + * Set the flash. + * + * @param string $type flash type, eg. 'error', 'notice' or 'success' + * @param mixed $message flash message + */ + public function set($type, $message) + { + if (!$type) { + throw new \InvalidArgumentException("Type should not be empty"); + } + + $this->session[$this->key] = compact('type', 'message'); + } + + /** + * Get the flash. + * + * @return object + */ + public function get() + { + if (!isset($this->data) && isset($this->session[$this->key])) { + $this->data = $this->session[$this->key]; + unset($this->session[$this->key]); + } + + return (object)$this->data; + } + + /** + * Reissue the flash. + */ + public function reissue() + { + if (!isset($this->data) && isset($this->session[$this->key])) { + $this->data = $this->session[$this->key]; + } else { + $this->session[$this->key] = $this->data; + } + } + + /** + * Clear the flash. + */ + public function clear() + { + $this->data = null; + unset($this->session[$this->key]); + } + + /** + * Get the flash type + * + * @return string + */ + public function getType() + { + $data = $this->get(); + return isset($data) ? $data->type : null; + } + + /** + * Get the flash message + * + * @return string + */ + public function getMessage() + { + $data = $this->get(); + return isset($data) ? $data->message : null; + } + + /** + * Cast object to string + * + * @return string + */ + public function __toString() + { + return (string)$this->getMessage(); + } +} diff --git a/src/Controller/View/Twig.php b/src/Controller/View/Twig.php new file mode 100644 index 0000000..ee5d4ce --- /dev/null +++ b/src/Controller/View/Twig.php @@ -0,0 +1,178 @@ +getTwig()->addGlobal($name, $value); + + return $this; + } + + /** + * Expose a function to the view. + * + * @param string $name Variable name + * @param mixed $function + * @param string $as 'function' or 'filter' + * @return $this + */ + public function setViewFunction($name, $function = null, $as = 'function') + { + if ($as === 'function') { + $this->getTwig()->addFunction($this->createTwigFunction($name, $function)); + } elseif ($as === 'filter') { + $this->getTwig()->addFilter($this->createTwigFilter($name, $function)); + } else { + throw new \InvalidArgumentException("You should create either function or filter, not '$as'"); + } + + return $this; + } + + /** + * Add extension to view + * + * @param object $extension + * @return $this + */ + public function setViewExtension($extension) + { + $this->getTwig()->addExtension($extension); + + return $this; + } + + /** + * View rendered template + * + * @param string $name Template name + * @param array $context Template context + * @return ResponseInterface + */ + public function view($name, array $context = []) + { + if (!pathinfo($name, PATHINFO_EXTENSION)) $name .= '.html.twig'; + + $twig = $this->getTwig(); + $tmpl = $twig->loadTemplate($name); + + $response = $this->getResponse(); + $response = $response->withHeader('Content-Type', 'text/html; charset=' . $twig->getCharset()); + $response->getBody()->write($tmpl->render($context)); + + return $response; + } + + /** + * Get twig environment + * + * @return \Twig_Environment + */ + public function getTwig() + { + if ($this->twig) return $this->twig; + + $loader = $this->getTwigLoader(); + $this->twig = $this->getTwigEnvironment($loader); + + $extensions = ['DateExtension', 'PcreExtension', 'TextExtension', 'ArrayExtension']; + foreach ($extensions as $name) { + $class = "Jasny\Twig\\$name"; + + if (class_exists($class)) $this->setViewExtension(new $class()); + } + + $uri = $this->getRequest()->getUri()->getPath(); + + $this->setViewVariable('current_url', $uri); + $this->setViewVariable('flash', new Flash()); + + return $this->twig; + } + + /** + * Get twig loasder for current working directory + * + * @return \Twig_Loader_Filesystem + */ + public function getTwigLoader() + { + return new \Twig_Loader_Filesystem(getcwd()); + } + + /** + * Get twig environment instance + * + * @param \Twig_Loader_Filesystem $loader + * @return \Twig_Environment + */ + public function getTwigEnvironment(\Twig_Loader_Filesystem $loader) + { + return new \Twig_Environment($loader); + } + + /** + * Create twig function + * + * @param string $name Name of function in view + * @param callable|null $function + * @return \Twig_SimpleFunction + */ + public function createTwigFunction($name, $function) + { + if (!$name) throw new \InvalidArgumentException("Function name should not be empty"); + + return new \Twig_SimpleFunction($name, $function ?: $name); + } + + /** + * Create twig filter + * + * @param string $name Name of filter in view + * @param callable|null $function + * @return \Twig_SimpleFilter + */ + public function createTwigFilter($name, $function) + { + if (!$name) throw new \InvalidArgumentException("Filter name should not be empty"); + + return new \Twig_SimpleFilter($name, $function ?: $name); + } +} diff --git a/src/Flash.php b/src/Flash.php deleted file mode 100644 index ad060f9..0000000 --- a/src/Flash.php +++ /dev/null @@ -1,109 +0,0 @@ -$type, 'message'=>$message]; - - $_SESSION['flash'] = static::$data; - } - - /** - * Get the flash. - * - * @return object - */ - public static function get() - { - if (!isset(static::$data) && isset($_SESSION['flash'])) { - static::$data = (object)$_SESSION['flash']; - unset($_SESSION['flash']); - } - - return static::$data; - } - - /** - * Reissue the flash. - */ - public static function reissue() - { - if (!isset(static::$data) && isset($_SESSION['flash'])) { - static::$data = (object)$_SESSION['flash']; - } else { - $_SESSION['flash'] = static::$data; - } - } - - /** - * Clear the flash. - */ - public static function clear() - { - self::$data = null; - unset($_SESSION['flash']); - } - - /** - * Get the flash type - * - * @return string - */ - public static function getType() - { - $data = static::get(); - return isset($data) ? $data->type : null; - } - - /** - * Get the flash message - * - * @return string - */ - public static function getMessage() - { - $data = static::get(); - return isset($data) ? $data->message : null; - } - - /** - * Cast object to string - * - * @return string - */ - public function __toString() - { - return (string)$this->getMessage(); - } -} diff --git a/src/View/Twig.php b/src/View/Twig.php deleted file mode 100644 index ee5d4ce..0000000 --- a/src/View/Twig.php +++ /dev/null @@ -1,178 +0,0 @@ -getTwig()->addGlobal($name, $value); - - return $this; - } - - /** - * Expose a function to the view. - * - * @param string $name Variable name - * @param mixed $function - * @param string $as 'function' or 'filter' - * @return $this - */ - public function setViewFunction($name, $function = null, $as = 'function') - { - if ($as === 'function') { - $this->getTwig()->addFunction($this->createTwigFunction($name, $function)); - } elseif ($as === 'filter') { - $this->getTwig()->addFilter($this->createTwigFilter($name, $function)); - } else { - throw new \InvalidArgumentException("You should create either function or filter, not '$as'"); - } - - return $this; - } - - /** - * Add extension to view - * - * @param object $extension - * @return $this - */ - public function setViewExtension($extension) - { - $this->getTwig()->addExtension($extension); - - return $this; - } - - /** - * View rendered template - * - * @param string $name Template name - * @param array $context Template context - * @return ResponseInterface - */ - public function view($name, array $context = []) - { - if (!pathinfo($name, PATHINFO_EXTENSION)) $name .= '.html.twig'; - - $twig = $this->getTwig(); - $tmpl = $twig->loadTemplate($name); - - $response = $this->getResponse(); - $response = $response->withHeader('Content-Type', 'text/html; charset=' . $twig->getCharset()); - $response->getBody()->write($tmpl->render($context)); - - return $response; - } - - /** - * Get twig environment - * - * @return \Twig_Environment - */ - public function getTwig() - { - if ($this->twig) return $this->twig; - - $loader = $this->getTwigLoader(); - $this->twig = $this->getTwigEnvironment($loader); - - $extensions = ['DateExtension', 'PcreExtension', 'TextExtension', 'ArrayExtension']; - foreach ($extensions as $name) { - $class = "Jasny\Twig\\$name"; - - if (class_exists($class)) $this->setViewExtension(new $class()); - } - - $uri = $this->getRequest()->getUri()->getPath(); - - $this->setViewVariable('current_url', $uri); - $this->setViewVariable('flash', new Flash()); - - return $this->twig; - } - - /** - * Get twig loasder for current working directory - * - * @return \Twig_Loader_Filesystem - */ - public function getTwigLoader() - { - return new \Twig_Loader_Filesystem(getcwd()); - } - - /** - * Get twig environment instance - * - * @param \Twig_Loader_Filesystem $loader - * @return \Twig_Environment - */ - public function getTwigEnvironment(\Twig_Loader_Filesystem $loader) - { - return new \Twig_Environment($loader); - } - - /** - * Create twig function - * - * @param string $name Name of function in view - * @param callable|null $function - * @return \Twig_SimpleFunction - */ - public function createTwigFunction($name, $function) - { - if (!$name) throw new \InvalidArgumentException("Function name should not be empty"); - - return new \Twig_SimpleFunction($name, $function ?: $name); - } - - /** - * Create twig filter - * - * @param string $name Name of filter in view - * @param callable|null $function - * @return \Twig_SimpleFilter - */ - public function createTwigFilter($name, $function) - { - if (!$name) throw new \InvalidArgumentException("Filter name should not be empty"); - - return new \Twig_SimpleFilter($name, $function ?: $name); - } -} diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index 3c659b2..b6a6b2d 100644 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -12,20 +12,44 @@ use Psr\Http\Message\StreamInterface; class ControllerTest extends PHPUnit_Framework_TestCase { /** + * Get mock for controller + * + * @param array $methods Methods to mock + * @return Controller + */ + public function getController($methods = []) + { + $builder = $this->getMockBuilder(Controller::class)->disableOriginalConstructor(); + if ($methods) { + $builder->setMethods($methods); + } + + return $builder->getMockForAbstractClass(); + } + + /** * Test running controller */ public function testInvoke() { + $test = $this; $controller = $this->getController(); - list($request, $response) = $this->getRequests(); - - $controller->expects($this->once())->method('run')->will($this->returnValue($response)); + + $request = $this->createMock(ServerRequestInterface::class); + $response = $this->createMock(ResponseInterface::class); + $finalResponse = $this->createMock(ResponseInterface::class); + + $controller->expects($this->once())->method('run') + ->willReturnCallback(Closure::bind(function() use ($test, $request, $response, $finalResponse) { + $test->assertSame($request, $this->getRequest()); + $test->assertSame($response, $this->getResponse()); + + return $finalResponse; + }, $controller, Controller::class)); $result = $controller($request, $response); - $this->assertEquals($response, $result, "Invoking controller should return 'ResponseInterface' instance"); - $this->assertEquals($response, $controller->getResponse(), "Can not get 'ResponseInterface' instance from controller"); - $this->assertEquals($request, $controller->getRequest(), "Can not get 'ServerRequestInterface' instance from controller"); + $this->assertEquals($finalResponse, $result); } /** @@ -82,7 +106,6 @@ class ControllerTest extends PHPUnit_Framework_TestCase } /** -<<<<<<< HEAD * Test functions that check request method * * @dataProvider requestMethodProvider @@ -125,6 +148,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase */ public function testEncodeDataPositive($data, $format, $callback = null) { + $this->markTestSkipped(); + $controller = $this->getController(['getRequest']); list($request) = $this->getRequests(); @@ -183,6 +208,8 @@ class ControllerTest extends PHPUnit_Framework_TestCase */ public function testEncodeDataNegative($data, $format) { + $this->markTestSkipped(); + $controller = $this->getController(['getRequest']); list($request) = $this->getRequests(); @@ -356,15 +383,15 @@ class ControllerTest extends PHPUnit_Framework_TestCase } /** - * Test responseWith function + * Test respondWith function * - * @dataProvider responseWithProvider + * @dataProvider respondWithProvider * @param int|string $code * @param string $format * @param int $setCode Actual code that will be set in response * @param string $contentType */ - public function testResponseWith($code, $format, $setCode, $contentType) + public function testRespondWith($code, $format, $setCode, $contentType) { $controller = $this->getController(['getResponse']); list(, $response) = $this->getRequests(); @@ -372,17 +399,17 @@ class ControllerTest extends PHPUnit_Framework_TestCase $this->expectResponseWith($response, $setCode, $contentType); $controller->method('getResponse')->will($this->returnValue($response)); - $result = $controller->responseWith($code, $format); + $result = $controller->respondWith($code, $format); $this->assertEquals($result, $response, "Response object should be returned"); } /** - * Test function responseWith + * Test function respondWith * * @return array */ - public function responseWithProvider() + public function respondWithProvider() { return [ [200, 'json', 200, 'application/json'], @@ -394,10 +421,10 @@ class ControllerTest extends PHPUnit_Framework_TestCase } /** - * Test functions that are simple wrappers around responseWith function + * Test functions that are simple wrappers around respondWith function * - * @dataProvider responseWithWrappersProvider - * @param string $functino + * @dataProvider respondWithWrappersProvider + * @param string $function * @param int $code */ public function testResponseWithWrappers($function, $code) @@ -414,11 +441,11 @@ class ControllerTest extends PHPUnit_Framework_TestCase } /** - * Provide data for testing responseWith wrappers + * Provide data for testing respondWith wrappers * * @return array */ - public function responseWithWrappersProvider() + public function respondWithWrappersProvider() { return [ ['ok', 200], @@ -626,7 +653,7 @@ class ControllerTest extends PHPUnit_Framework_TestCase } /** - * Expect correct work of responseWith function + * Expect correct work of respondWith function * * @param ResponseInterface $response * @param int $code @@ -676,22 +703,6 @@ class ControllerTest extends PHPUnit_Framework_TestCase } /** - * Get mock for controller - * - * @param array $methods Methods to mock - * @return Controller - */ - public function getController($methods = []) - { - $builder = $this->getMockBuilder(Controller::class)->disableOriginalConstructor(); - if ($methods) { - $builder->setMethods($methods); - } - - return $builder->getMockForAbstractClass(); - } - - /** * Get request and response instances * * @return array -- cgit v1.1