diff --git a/README.markdown b/README.markdown index cad57a612..a772db216 100644 --- a/README.markdown +++ b/README.markdown @@ -66,7 +66,7 @@ should contain this code: Your nginx configuration file should contain this code (along with other settings you may need) in your `location` block: - try_files $uri $uri/ /index.php; + try_files $uri $uri/ /index.php?$args; This assumes that Slim's `index.php` is in the root folder of your project (www root). @@ -79,6 +79,28 @@ lighttpd >= 1.4.24. This assumes that Slim's `index.php` is in the root folder of your project (www root). +#### IIS + +Ensure the `Web.config` and `index.php` files are in the same public-accessible directory. The `Web.config` file should contain this code: + + + + + + + + + + + + + + + + + + + ## Documentation diff --git a/Slim/Environment.php b/Slim/Environment.php index 147d00f20..707b2dc16 100644 --- a/Slim/Environment.php +++ b/Slim/Environment.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -87,7 +87,7 @@ public static function getInstance($refresh = false) */ public static function mock($userSettings = array()) { - self::$environment = new self(array_merge(array( + $defaults = array( 'REQUEST_METHOD' => 'GET', 'SCRIPT_NAME' => '', 'PATH_INFO' => '', @@ -102,7 +102,8 @@ public static function mock($userSettings = array()) 'slim.url_scheme' => 'http', 'slim.input' => '', 'slim.errors' => @fopen('php://stderr', 'w') - ), $userSettings)); + ); + self::$environment = new self(array_merge($defaults, $userSettings)); return self::$environment; } @@ -143,7 +144,7 @@ private function __construct($settings = null) if (strpos($_SERVER['REQUEST_URI'], $_SERVER['SCRIPT_NAME']) === 0) { $env['SCRIPT_NAME'] = $_SERVER['SCRIPT_NAME']; //Without URL rewrite } else { - $env['SCRIPT_NAME'] = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']) ); //With URL rewrite + $env['SCRIPT_NAME'] = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME'])); //With URL rewrite } $env['PATH_INFO'] = substr_replace($_SERVER['REQUEST_URI'], '', 0, strlen($env['SCRIPT_NAME'])); if (strpos($env['PATH_INFO'], '?') !== false) { @@ -161,21 +162,26 @@ private function __construct($settings = null) //Number of server port that is running the script $env['SERVER_PORT'] = $_SERVER['SERVER_PORT']; - //HTTP request headers - $specialHeaders = array('CONTENT_TYPE', 'CONTENT_LENGTH', 'PHP_AUTH_USER', 'PHP_AUTH_PW', 'PHP_AUTH_DIGEST', 'AUTH_TYPE'); - foreach ($_SERVER as $key => $value) { - $value = is_string($value) ? trim($value) : $value; - if (strpos($key, 'HTTP_') === 0) { - $env[substr($key, 5)] = $value; - } elseif (strpos($key, 'X_') === 0 || in_array($key, $specialHeaders)) { - $env[$key] = $value; - } + //HTTP request headers (retains HTTP_ prefix to match $_SERVER) + $headers = \Slim\Http\Headers::extract($_SERVER); + foreach ($headers as $key => $value) { + $env[$key] = $value; } + // $specialHeaders = array('CONTENT_TYPE', 'CONTENT_LENGTH', 'PHP_AUTH_USER', 'PHP_AUTH_PW', 'PHP_AUTH_DIGEST', 'AUTH_TYPE'); + // foreach ($_SERVER as $key => $value) { + // $value = is_string($value) ? trim($value) : $value; + // if (strpos($key, 'HTTP_') === 0) { + // $env[substr($key, 5)] = $value; + // } elseif (strpos($key, 'X_') === 0 || in_array($key, $specialHeaders)) { + // $env[$key] = $value; + // } + // } + //Is the application running under HTTPS or HTTP protocol? $env['slim.url_scheme'] = empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off' ? 'http' : 'https'; - //Input stream (readable one time only; not available for mutipart/form-data requests) + //Input stream (readable one time only; not available for multipart/form-data requests) $rawInput = @file_get_contents('php://input'); if (!$rawInput) { $rawInput = ''; @@ -183,7 +189,7 @@ private function __construct($settings = null) $env['slim.input'] = $rawInput; //Error stream - $env['slim.errors'] = fopen('php://stderr', 'w'); + $env['slim.errors'] = @fopen('php://stderr', 'w'); $this->properties = $env; } diff --git a/Slim/Exception/Pass.php b/Slim/Exception/Pass.php index f4f25fac5..2833dd0ec 100644 --- a/Slim/Exception/Pass.php +++ b/Slim/Exception/Pass.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -46,5 +46,4 @@ */ class Pass extends \Exception { - } diff --git a/Slim/Exception/Stop.php b/Slim/Exception/Stop.php index f008959a4..8667eaeaa 100644 --- a/Slim/Exception/Stop.php +++ b/Slim/Exception/Stop.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -44,5 +44,4 @@ */ class Stop extends \Exception { - } diff --git a/Slim/Helper/Set.php b/Slim/Helper/Set.php new file mode 100644 index 000000000..8a8a56a03 --- /dev/null +++ b/Slim/Helper/Set.php @@ -0,0 +1,210 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 2.3.0 + * @package Slim + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Slim\Helper; + +class Set implements \ArrayAccess, \Countable, \IteratorAggregate +{ + /** + * Key-value array of arbitrary data + * @var array + */ + protected $data = array(); + + /** + * Constructor + * @param array $items Pre-populate set with this key-value array + */ + public function __construct($items = array()) + { + $this->replace($items); + } + + /** + * Normalize data key + * + * Used to transform data key into the necessary + * key format for this set. Used in subclasses + * like \Slim\Http\Headers. + * + * @param string $key The data key + * @return mixed The transformed/normalized data key + */ + protected function normalizeKey($key) + { + return $key; + } + + /** + * Set data key to value + * @param string $key The data key + * @param mixed $value The data value + */ + public function set($key, $value) + { + $this->data[$this->normalizeKey($key)] = $value; + } + + /** + * Get data value with key + * @param string $key The data key + * @param mixed $default The value to return if data key does not exist + * @return mixed The data value, or the default value + */ + public function get($key, $default = null) + { + if ($this->has($key)) { + $isInvokable = is_object($this->data[$this->normalizeKey($key)]) && method_exists($this->data[$this->normalizeKey($key)], '__invoke'); + + return $isInvokable ? $this->data[$this->normalizeKey($key)]($this) : $this->data[$this->normalizeKey($key)]; + } + + return $default; + } + + /** + * Add data to set + * @param array $items Key-value array of data to append to this set + */ + public function replace($items) + { + foreach ($items as $key => $value) { + $this->set($key, $value); // Ensure keys are normalized + } + } + + /** + * Fetch set data + * @return array This set's key-value data array + */ + public function all() + { + return $this->data; + } + + /** + * Fetch set data keys + * @return array This set's key-value data array keys + */ + public function keys() + { + return array_keys($this->data); + } + + /** + * Does this set contain a key? + * @param string $key The data key + * @return boolean + */ + public function has($key) + { + return array_key_exists($this->normalizeKey($key), $this->data); + } + + /** + * Remove value with key from this set + * @param string $key The data key + */ + public function remove($key) + { + unset($this->data[$this->normalizeKey($key)]); + } + + /** + * Clear all values + */ + public function clear() + { + $this->data = array(); + } + + /** + * Array Access + */ + + public function offsetExists($offset) + { + return $this->has($offset); + } + + public function offsetGet($offset) + { + return $this->get($offset); + } + + public function offsetSet($offset, $value) + { + $this->set($offset, $value); + } + + public function offsetUnset($offset) + { + $this->remove($offset); + } + + /** + * Countable + */ + + public function count() + { + return count($this->data); + } + + /** + * IteratorAggregate + */ + + public function getIterator() + { + return new \ArrayIterator($this->data); + } + + /** + * Ensure a value or object will remain globally unique + * @param string $key The value or object name + * @param Closure The closure that defines the object + * @return mixed + */ + public function singleton($key, $value) + { + $this->set($key, function ($c) use ($value) { + static $object; + + if (null === $object) { + $object = $value($c); + } + + return $object; + }); + } +} diff --git a/Slim/Http/Cookies.php b/Slim/Http/Cookies.php new file mode 100644 index 000000000..61c70e742 --- /dev/null +++ b/Slim/Http/Cookies.php @@ -0,0 +1,91 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 2.3.0 + * @package Slim + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +namespace Slim\Http; + +class Cookies extends \Slim\Helper\Set +{ + /** + * Default cookie settings + * @var array + */ + protected $defaults = array( + 'value' => '', + 'domain' => null, + 'path' => null, + 'expires' => null, + 'secure' => false, + 'httponly' => false + ); + + /** + * Set cookie + * + * The second argument may be a single scalar value, in which case + * it will be merged with the default settings and considered the `value` + * of the merged result. + * + * The second argument may also be an array containing any or all of + * the keys shown in the default settings above. This array will be + * merged with the defaults shown above. + * + * @param string $key Cookie name + * @param mixed $value Cookie settings + */ + public function set($key, $value) + { + if (is_array($value)) { + $cookieSettings = array_replace($this->defaults, $value); + } else { + $cookieSettings = array_replace($this->defaults, array('value' => $value)); + } + parent::set($key, $cookieSettings); + } + + /** + * Remove cookie + * + * Unlike \Slim\Helper\Set, this will actually *set* a cookie with + * an expiration date in the past. This expiration date will force + * the client-side cache to remove its cookie with the given name + * and settings. + * + * @param string $key Cookie name + * @param array $settings Optional cookie settings + */ + public function remove($key, $settings = array()) + { + $settings['value'] = ''; + $settings['expires'] = time() - 86400; + $this->set($key, array_replace($this->defaults, $settings)); + } +} diff --git a/Slim/Http/Headers.php b/Slim/Http/Headers.php index 0185f086b..5e0283e6e 100644 --- a/Slim/Http/Headers.php +++ b/Slim/Http/Headers.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -35,147 +35,70 @@ /** * HTTP Headers * - * This class is an abstraction of the HTTP response headers and - * provides array access to the header list while automatically - * stores and retrieves headers with lowercase canonical keys regardless - * of the input format. - * - * This class also implements the `Iterator` and `Countable` - * interfaces for even more convenient usage. - * * @package Slim * @author Josh Lockhart * @since 1.6.0 */ -class Headers implements \ArrayAccess, \Iterator, \Countable +class Headers extends \Slim\Helper\Set { - /** - * @var array HTTP headers - */ - protected $headers; + /******************************************************************************** + * Static interface + *******************************************************************************/ /** - * @var array Map canonical header name to original header name + * Special-case HTTP headers that are otherwise unidentifiable as HTTP headers. + * Typically, HTTP headers in the $_SERVER array will be prefixed with + * `HTTP_` or `X_`. These are not so we list them here for later reference. + * + * @var array */ - protected $map; - - /** - * Constructor - * @param array $headers - */ - public function __construct($headers = array()) - { - $this->merge($headers); - } + protected static $special = array( + 'CONTENT_TYPE', + 'CONTENT_LENGTH', + 'PHP_AUTH_USER', + 'PHP_AUTH_PW', + 'PHP_AUTH_DIGEST', + 'AUTH_TYPE' + ); /** - * Merge Headers - * @param array $headers + * Extract HTTP headers from an array of data (e.g. $_SERVER) + * @param array $data + * @return array */ - public function merge($headers) + public static function extract($data) { - foreach ($headers as $name => $value) { - $this[$name] = $value; + $results = array(); + foreach ($data as $key => $value) { + $key = strtoupper($key); + if (strpos($key, 'X_') === 0 || strpos($key, 'HTTP_') === 0 || in_array($key, static::$special)) { + if ($key === 'HTTP_CONTENT_TYPE' || $key === 'HTTP_CONTENT_LENGTH') { + continue; + } + $results[$key] = $value; + } } - } - - /** - * Transform header name into canonical form - * @param string $name - * @return string - */ - protected function canonical($name) - { - return strtolower(trim($name)); - } - - /** - * Array Access: Offset Exists - */ - public function offsetExists($offset) - { - return isset($this->headers[$this->canonical($offset)]); - } - - /** - * Array Access: Offset Get - */ - public function offsetGet($offset) - { - $canonical = $this->canonical($offset); - if (isset($this->headers[$canonical])) { - return $this->headers[$canonical]; - } else { - return null; - } - } - - /** - * Array Access: Offset Set - */ - public function offsetSet($offset, $value) - { - $canonical = $this->canonical($offset); - $this->headers[$canonical] = $value; - $this->map[$canonical] = $offset; - } - - /** - * Array Access: Offset Unset - */ - public function offsetUnset($offset) - { - $canonical = $this->canonical($offset); - unset($this->headers[$canonical], $this->map[$canonical]); - } - /** - * Countable: Count - */ - public function count() - { - return count($this->headers); + return $results; } - /** - * Iterator: Rewind - */ - public function rewind() - { - reset($this->headers); - } + /******************************************************************************** + * Instance interface + *******************************************************************************/ /** - * Iterator: Current - */ - public function current() - { - return current($this->headers); - } - - /** - * Iterator: Key - */ - public function key() - { - $key = key($this->headers); - - return $this->map[$key]; - } - - /** - * Iterator: Next + * Transform header name into canonical form + * @param string $key + * @return string */ - public function next() + protected function normalizeKey($key) { - return next($this->headers); - } + $key = strtolower($key); + $key = str_replace(array('-', '_'), ' ', $key); + $key = preg_replace('#^http #', '', $key); + $key = ucwords($key); + $key = str_replace(' ', '-', $key); - /** - * Iterator: Valid - */ - public function valid() - { - return current($this->headers) !== false; + return $key; } } diff --git a/Slim/Http/Request.php b/Slim/Http/Request.php index e508394ad..b7d32f985 100644 --- a/Slim/Http/Request.php +++ b/Slim/Http/Request.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -48,6 +48,7 @@ class Request const METHOD_GET = 'GET'; const METHOD_POST = 'POST'; const METHOD_PUT = 'PUT'; + const METHOD_PATCH = 'PATCH'; const METHOD_DELETE = 'DELETE'; const METHOD_OPTIONS = 'OPTIONS'; const METHOD_OVERRIDE = '_METHOD'; @@ -58,18 +59,32 @@ class Request protected static $formDataMediaTypes = array('application/x-www-form-urlencoded'); /** - * @var array + * Application Environment + * @var \Slim\Environment */ protected $env; + /** + * HTTP Headers + * @var \Slim\Http\Headers + */ + public $headers; + + /** + * HTTP Cookies + * @var \Slim\Helper\Set + */ + public $cookies; + /** * Constructor - * @param array $env - * @see \Slim\Environment + * @param \Slim\Environment $env */ - public function __construct($env) + public function __construct(\Slim\Environment $env) { $this->env = $env; + $this->headers = new \Slim\Http\Headers(\Slim\Http\Headers::extract($env)); + $this->cookies = new \Slim\Helper\Set(\Slim\Http\Util::parseCookieHeader($env['HTTP_COOKIE'])); } /** @@ -108,6 +123,15 @@ public function isPut() return $this->getMethod() === self::METHOD_PUT; } + /** + * Is this a PATCH request? + * @return bool + */ + public function isPatch() + { + return $this->getMethod() === self::METHOD_PATCH; + } + /** * Is this a DELETE request? * @return bool @@ -143,7 +167,7 @@ public function isAjax() { if ($this->params('isajax')) { return true; - } elseif (isset($this->env['X_REQUESTED_WITH']) && $this->env['X_REQUESTED_WITH'] === 'XMLHttpRequest') { + } elseif (isset($this->headers['X_REQUESTED_WITH']) && $this->headers['X_REQUESTED_WITH'] === 'XMLHttpRequest') { return true; } else { return false; @@ -172,14 +196,10 @@ public function params($key = null) { $union = array_merge($this->get(), $this->post()); if ($key) { - if (isset($union[$key])) { - return $union[$key]; - } else { - return null; - } - } else { - return $union; + return isset($union[$key]) ? $union[$key] : null; } + + return $union; } /** @@ -263,6 +283,16 @@ public function put($key = null) return $this->post($key); } + /** + * Fetch PATCH data (alias for \Slim\Http\Request::post) + * @param string $key + * @return array|mixed|null + */ + public function patch($key = null) + { + return $this->post($key); + } + /** * Fetch DELETE data (alias for \Slim\Http\Request::post) * @param string $key @@ -284,23 +314,28 @@ public function delete($key = null) */ public function cookies($key = null) { - if (!isset($this->env['slim.request.cookie_hash'])) { - $cookieHeader = isset($this->env['COOKIE']) ? $this->env['COOKIE'] : ''; - $this->env['slim.request.cookie_hash'] = Util::parseCookieHeader($cookieHeader); - } if ($key) { - if (isset($this->env['slim.request.cookie_hash'][$key])) { - return $this->env['slim.request.cookie_hash'][$key]; - } else { - return null; - } - } else { - return $this->env['slim.request.cookie_hash']; + return $this->cookies->get($key); } + + return $this->cookies; + // if (!isset($this->env['slim.request.cookie_hash'])) { + // $cookieHeader = isset($this->env['COOKIE']) ? $this->env['COOKIE'] : ''; + // $this->env['slim.request.cookie_hash'] = Util::parseCookieHeader($cookieHeader); + // } + // if ($key) { + // if (isset($this->env['slim.request.cookie_hash'][$key])) { + // return $this->env['slim.request.cookie_hash'][$key]; + // } else { + // return null; + // } + // } else { + // return $this->env['slim.request.cookie_hash']; + // } } /** - * Does the Request body contain parseable form data? + * Does the Request body contain parsed form data? * @return bool */ public function isFormData() @@ -323,24 +358,29 @@ public function isFormData() public function headers($key = null, $default = null) { if ($key) { - $key = strtoupper($key); - $key = str_replace('-', '_', $key); - $key = preg_replace('@^HTTP_@', '', $key); - if (isset($this->env[$key])) { - return $this->env[$key]; - } else { - return $default; - } - } else { - $headers = array(); - foreach ($this->env as $key => $value) { - if (strpos($key, 'slim.') !== 0) { - $headers[$key] = $value; - } - } - - return $headers; + return $this->headers->get($key, $default); } + + return $this->headers; + // if ($key) { + // $key = strtoupper($key); + // $key = str_replace('-', '_', $key); + // $key = preg_replace('@^HTTP_@', '', $key); + // if (isset($this->env[$key])) { + // return $this->env[$key]; + // } else { + // return $default; + // } + // } else { + // $headers = array(); + // foreach ($this->env as $key => $value) { + // if (strpos($key, 'slim.') !== 0) { + // $headers[$key] = $value; + // } + // } + // + // return $headers; + // } } /** @@ -354,15 +394,11 @@ public function getBody() /** * Get Content Type - * @return string + * @return string|null */ public function getContentType() { - if (isset($this->env['CONTENT_TYPE'])) { - return $this->env['CONTENT_TYPE']; - } else { - return null; - } + return $this->headers->get('CONTENT_TYPE'); } /** @@ -376,9 +412,9 @@ public function getMediaType() $contentTypeParts = preg_split('/\s*[;,]\s*/', $contentType); return strtolower($contentTypeParts[0]); - } else { - return null; } + + return null; } /** @@ -410,9 +446,9 @@ public function getContentCharset() $mediaTypeParams = $this->getMediaTypeParams(); if (isset($mediaTypeParams['charset'])) { return $mediaTypeParams['charset']; - } else { - return null; } + + return null; } /** @@ -421,11 +457,7 @@ public function getContentCharset() */ public function getContentLength() { - if (isset($this->env['CONTENT_LENGTH'])) { - return (int) $this->env['CONTENT_LENGTH']; - } else { - return 0; - } + return $this->headers->get('CONTENT_LENGTH', 0); } /** @@ -434,17 +466,17 @@ public function getContentLength() */ public function getHost() { - if (isset($this->env['HOST'])) { - if (strpos($this->env['HOST'], ':') !== false) { - $hostParts = explode(':', $this->env['HOST']); + if (isset($this->env['HTTP_HOST'])) { + if (strpos($this->env['HTTP_HOST'], ':') !== false) { + $hostParts = explode(':', $this->env['HTTP_HOST']); return $hostParts[0]; } - return $this->env['HOST']; - } else { - return $this->env['SERVER_NAME']; + return $this->env['HTTP_HOST']; } + + return $this->env['SERVER_NAME']; } /** @@ -462,7 +494,7 @@ public function getHostWithPort() */ public function getPort() { - return (int) $this->env['SERVER_PORT']; + return (int)$this->env['SERVER_PORT']; } /** @@ -554,11 +586,7 @@ public function getIp() */ public function getReferrer() { - if (isset($this->env['REFERER'])) { - return $this->env['REFERER']; - } else { - return null; - } + return $this->headers->get('HTTP_REFERER'); } /** @@ -576,10 +604,6 @@ public function getReferer() */ public function getUserAgent() { - if (isset($this->env['USER_AGENT'])) { - return $this->env['USER_AGENT']; - } else { - return null; - } + return $this->headers->get('HTTP_USER_AGENT'); } } diff --git a/Slim/Http/Response.php b/Slim/Http/Response.php index 8e95b376f..1d3b729d5 100644 --- a/Slim/Http/Response.php +++ b/Slim/Http/Response.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -51,9 +51,14 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate protected $status; /** - * @var \Slim\Http\Headers List of HTTP response headers + * @var \Slim\Http\Headers */ - protected $header; + public $headers; + + /** + * @var \Slim\Http\Cookies + */ + public $cookies; /** * @var string HTTP response body @@ -123,21 +128,30 @@ class Response implements \ArrayAccess, \Countable, \IteratorAggregate * Constructor * @param string $body The HTTP response body * @param int $status The HTTP response status - * @param \Slim\Http\Headers|array $header The HTTP response headers + * @param \Slim\Http\Headers|array $headers The HTTP response headers */ - public function __construct($body = '', $status = 200, $header = array()) + public function __construct($body = '', $status = 200, $headers = array()) { - $this->status = (int) $status; - $headers = array(); - foreach ($header as $key => $value) { - $headers[$key] = $value; - } - $this->header = new Headers(array_merge(array('Content-Type' => 'text/html'), $headers)); - $this->body = ''; + $this->setStatus($status); + $this->headers = new \Slim\Http\Headers(array('Content-Type' => 'text/html')); + $this->headers->replace($headers); + $this->cookies = new \Slim\Http\Cookies(); $this->write($body); } + public function getStatus() + { + return $this->status; + } + + public function setStatus($status) + { + $this->status = (int)$status; + } + /** + * DEPRECATION WARNING! Use `getStatus` or `setStatus` instead. + * * Get and set status * @param int|null $status * @return int @@ -152,6 +166,8 @@ public function status($status = null) } /** + * DEPRECATION WARNING! Access `headers` property directly. + * * Get and set header * @param string $name Header name * @param string|null $value Header value @@ -160,22 +176,36 @@ public function status($status = null) public function header($name, $value = null) { if (!is_null($value)) { - $this[$name] = $value; + $this->headers->set($name, $value); } - return $this[$name]; + return $this->headers->get($name); } /** + * DEPRECATION WARNING! Access `headers` property directly. + * * Get headers * @return \Slim\Http\Headers */ public function headers() { - return $this->header; + return $this->headers; + } + + public function getBody() + { + return $this->body; + } + + public function setBody($content) + { + $this->write($content, true); } /** + * DEPRECATION WARNING! use `getBody` or `setBody` instead. + * * Get and set body * @param string|null $body Content of HTTP response body * @return string @@ -189,38 +219,45 @@ public function body($body = null) return $this->body; } - /** - * Get and set length - * @param int|null $length - * @return int - */ - public function length($length = null) - { - if (!is_null($length)) { - $this->length = (int) $length; - } - - return $this->length; - } - /** * Append HTTP response body * @param string $body Content to append to the current HTTP response body * @param bool $replace Overwrite existing response body? - * @return string The updated HTTP response body + * @return string The updated HTTP response body */ public function write($body, $replace = false) { if ($replace) { $this->body = $body; } else { - $this->body .= (string) $body; + $this->body .= (string)$body; } $this->length = strlen($this->body); return $this->body; } + public function getLength() + { + return $this->length; + } + + /** + * DEPRECATION WARNING! Use `getLength` or `write` or `body` instead. + * + * Get and set length + * @param int|null $length + * @return int + */ + public function length($length = null) + { + if (!is_null($length)) { + $this->length = (int) $length; + } + + return $this->length; + } + /** * Finalize * @@ -232,16 +269,19 @@ public function write($body, $replace = false) */ public function finalize() { + // Prepare response if (in_array($this->status, array(204, 304))) { - unset($this['Content-Type'], $this['Content-Length']); - - return array($this->status, $this->header, ''); - } else { - return array($this->status, $this->header, $this->body); + $this->headers->remove('Content-Type'); + $this->headers->remove('Content-Length'); + $this->setBody(''); } + + return array($this->status, $this->headers, $this->body); } /** + * DEPRECATION WARNING! Access `cookies` property directly. + * * Set cookie * * Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie` @@ -256,10 +296,13 @@ public function finalize() */ public function setCookie($name, $value) { - Util::setCookieHeader($this->header, $name, $value); + // Util::setCookieHeader($this->header, $name, $value); + $this->cookies->set($name, $value); } /** + * DEPRECATION WARNING! Access `cookies` property directly. + * * Delete cookie * * Instead of using PHP's `setcookie()` function, Slim manually constructs the HTTP `Set-Cookie` @@ -274,12 +317,13 @@ public function setCookie($name, $value) * array, only the Cookie with the given name AND domain will be removed. The invalidating cookie * sent with this response will adopt all properties of the second argument. * - * @param string $name The name of the cookie - * @param array $value Properties for cookie including: value, expire, path, domain, secure, httponly + * @param string $name The name of the cookie + * @param array $settings Properties for cookie including: value, expire, path, domain, secure, httponly */ - public function deleteCookie($name, $value = array()) + public function deleteCookie($name, $settings = array()) { - Util::deleteCookieHeader($this->header, $name, $value); + $this->cookies->remove($name, $settings); + // Util::deleteCookieHeader($this->header, $name, $value); } /** @@ -293,8 +337,8 @@ public function deleteCookie($name, $value = array()) */ public function redirect ($url, $status = 302) { - $this->status = $status; - $this['Location'] = $url; + $this->setStatus($status); + $this->headers->set('Location', $url); } /** @@ -387,24 +431,25 @@ public function isServerError() return $this->status >= 500 && $this->status < 600; } + /** + * DEPRECATION WARNING! ArrayAccess interface will be removed from \Slim\Http\Response. + * Iterate `headers` or `cookies` properties directly. + */ + /** * Array Access: Offset Exists */ - public function offsetExists( $offset ) + public function offsetExists($offset) { - return isset($this->header[$offset]); + return isset($this->headers[$offset]); } /** * Array Access: Offset Get */ - public function offsetGet( $offset ) + public function offsetGet($offset) { - if (isset($this->header[$offset])) { - return $this->header[$offset]; - } else { - return null; - } + return $this->headers[$offset]; } /** @@ -412,7 +457,7 @@ public function offsetGet( $offset ) */ public function offsetSet($offset, $value) { - $this->header[$offset] = $value; + $this->headers[$offset] = $value; } /** @@ -420,18 +465,24 @@ public function offsetSet($offset, $value) */ public function offsetUnset($offset) { - unset($this->header[$offset]); + unset($this->headers[$offset]); } /** + * DEPRECATION WARNING! Countable interface will be removed from \Slim\Http\Response. + * Call `count` on `headers` or `cookies` properties directly. + * * Countable: Count */ public function count() { - return count($this->header); + return count($this->headers); } /** + * DEPRECATION WARNING! IteratorAggreate interface will be removed from \Slim\Http\Response. + * Iterate `headers` or `cookies` properties directly. + * * Get Iterator * * This returns the contained `\Slim\Http\Headers` instance which @@ -441,11 +492,12 @@ public function count() */ public function getIterator() { - return $this->header; + return $this->headers->getIterator(); } /** * Get message for HTTP status code + * @param int $status * @return string|null */ public static function getMessageForCode($status) diff --git a/Slim/Http/Util.php b/Slim/Http/Util.php index f7e99a962..b3a1f12d5 100644 --- a/Slim/Http/Util.php +++ b/Slim/Http/Util.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -51,14 +51,15 @@ class Util * override the magic quotes setting with either TRUE or FALSE as the send argument * to force this method to strip or not strip slashes from its input. * - * @var array|string $rawData + * @param array|string $rawData + * @param bool $overrideStripSlashes * @return array|string */ public static function stripSlashesIfMagicQuotes($rawData, $overrideStripSlashes = null) { $strip = is_null($overrideStripSlashes) ? get_magic_quotes_gpc() : $overrideStripSlashes; if ($strip) { - return self::_stripSlashes($rawData); + return self::stripSlashes($rawData); } else { return $rawData; } @@ -69,9 +70,9 @@ public static function stripSlashesIfMagicQuotes($rawData, $overrideStripSlashes * @param array|string $rawData * @return array|string */ - protected static function _stripSlashes($rawData) + protected static function stripSlashes($rawData) { - return is_array($rawData) ? array_map(array('self', '_stripSlashes'), $rawData) : stripslashes($rawData); + return is_array($rawData) ? array_map(array('self', 'stripSlashes'), $rawData) : stripslashes($rawData); } /** @@ -95,10 +96,11 @@ public static function encrypt($data, $key, $iv, $settings = array()) } //Merge settings with defaults - $settings = array_merge(array( + $defaults = array( 'algorithm' => MCRYPT_RIJNDAEL_256, 'mode' => MCRYPT_MODE_CBC - ), $settings); + ); + $settings = array_merge($defaults, $settings); //Get module $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], ''); @@ -144,10 +146,11 @@ public static function decrypt($data, $key, $iv, $settings = array()) } //Merge settings with defaults - $settings = array_merge(array( + $defaults = array( 'algorithm' => MCRYPT_RIJNDAEL_256, 'mode' => MCRYPT_MODE_CBC - ), $settings); + ); + $settings = array_merge($defaults, $settings); //Get module $module = mcrypt_module_open($settings['algorithm'], '', $settings['mode'], ''); @@ -173,6 +176,32 @@ public static function decrypt($data, $key, $iv, $settings = array()) return $res; } + /** + * Serialize Response cookies into raw HTTP header + * @param \Slim\Http\Headers $headers The Response headers + * @param \Slim\Http\Cookies $cookies The Response cookies + * @param array $config The Slim app settings + */ + public static function serializeCookies(\Slim\Http\Headers &$headers, \Slim\Http\Cookies $cookies, array $config) + { + if ($config['cookies.encrypt']) { + foreach ($cookies as $name => $settings) { + $settings['value'] = static::encodeSecureCookie( + $settings['value'], + $settings['expires'], + $config['cookies.secret_key'], + $config['cookies.cipher'], + $config['cookies.cipher_mode'] + ); + static::setCookieHeader($headers, $name, $settings); + } + } else { + foreach ($cookies as $name => $settings) { + static::setCookieHeader($headers, $name, $settings); + } + } + } + /** * Encode secure cookie value * @@ -180,21 +209,28 @@ public static function decrypt($data, $key, $iv, $settings = array()) * cookie value is encrypted and hashed so that its value is * secure and checked for integrity when read in subsequent requests. * - * @param string $value The unsecure HTTP cookie value + * @param string $value The insecure HTTP cookie value * @param int $expires The UNIX timestamp at which this cookie will expire * @param string $secret The secret key used to hash the cookie value * @param int $algorithm The algorithm to use for encryption * @param int $mode The algorithm mode to use for encryption - * @param string + * @return string */ public static function encodeSecureCookie($value, $expires, $secret, $algorithm, $mode) { $key = hash_hmac('sha1', $expires, $secret); - $iv = self::get_iv($expires, $secret); - $secureString = base64_encode(self::encrypt($value, $key, $iv, array( - 'algorithm' => $algorithm, - 'mode' => $mode - ))); + $iv = self::getIv($expires, $secret); + $secureString = base64_encode( + self::encrypt( + $value, + $key, + $iv, + array( + 'algorithm' => $algorithm, + 'mode' => $mode + ) + ) + ); $verificationString = hash_hmac('sha1', $expires . $value, $key); return implode('|', array($expires, $secureString, $verificationString)); @@ -208,11 +244,10 @@ public static function encodeSecureCookie($value, $expires, $secret, $algorithm, * secure and checked for integrity when read in subsequent requests. * * @param string $value The secure HTTP cookie value - * @param int $expires The UNIX timestamp at which this cookie will expire * @param string $secret The secret key used to hash the cookie value * @param int $algorithm The algorithm to use for encryption * @param int $mode The algorithm mode to use for encryption - * @param string + * @return bool|string */ public static function decodeSecureCookie($value, $secret, $algorithm, $mode) { @@ -220,11 +255,16 @@ public static function decodeSecureCookie($value, $secret, $algorithm, $mode) $value = explode('|', $value); if (count($value) === 3 && ((int) $value[0] === 0 || (int) $value[0] > time())) { $key = hash_hmac('sha1', $value[0], $secret); - $iv = self::get_iv($value[0], $secret); - $data = self::decrypt(base64_decode($value[1]), $key, $iv, array( - 'algorithm' => $algorithm, - 'mode' => $mode - )); + $iv = self::getIv($value[0], $secret); + $data = self::decrypt( + base64_decode($value[1]), + $key, + $iv, + array( + 'algorithm' => $algorithm, + 'mode' => $mode + ) + ); $verificationString = hash_hmac('sha1', $value[0] . $data, $key); if ($verificationString === $value[2]) { return $data; @@ -310,7 +350,7 @@ public static function setCookieHeader(&$header, $name, $value) * * @param array $header * @param string $name - * @param string $value + * @param array $value */ public static function deleteCookieHeader(&$header, $name, $value = array()) { @@ -376,9 +416,9 @@ public static function parseCookieHeader($header) * * @param int $expires The UNIX timestamp at which this cookie will expire * @param string $secret The secret key used to hash the cookie value - * @return binary string with length 40 + * @return string Hash */ - private static function get_iv($expires, $secret) + private static function getIv($expires, $secret) { $data1 = hash_hmac('sha1', 'a'.$expires.'b', $secret); $data2 = hash_hmac('sha1', 'z'.$expires.'y', $secret); diff --git a/Slim/Log.php b/Slim/Log.php index 7706b2f24..1c679d07b 100644 --- a/Slim/Log.php +++ b/Slim/Log.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -39,11 +39,15 @@ * a Log Writer in conjunction with this Log to write to various output * destinations (e.g. a file). This class provides this interface: * - * debug( mixed $object ) - * info( mixed $object ) - * warn( mixed $object ) - * error( mixed $object ) - * fatal( mixed $object ) + * debug( mixed $object, array $context ) + * info( mixed $object, array $context ) + * notice( mixed $object, array $context ) + * warning( mixed $object, array $context ) + * error( mixed $object, array $context ) + * critical( mixed $object, array $context ) + * alert( mixed $object, array $context ) + * emergency( mixed $object, array $context ) + * log( mixed $level, mixed $object, array $context ) * * This class assumes only that your Log Writer has a public `write()` method * that accepts any object as its one and only argument. The Log Writer @@ -56,21 +60,28 @@ */ class Log { - const FATAL = 0; - const ERROR = 1; - const WARN = 2; - const INFO = 3; - const DEBUG = 4; + const EMERGENCY = 1; + const ALERT = 2; + const CRITICAL = 3; + const FATAL = 3; //DEPRECATED replace with CRITICAL + const ERROR = 4; + const WARN = 5; + const NOTICE = 6; + const INFO = 7; + const DEBUG = 8; /** * @var array */ protected static $levels = array( - self::FATAL => 'FATAL', - self::ERROR => 'ERROR', - self::WARN => 'WARN', - self::INFO => 'INFO', - self::DEBUG => 'DEBUG' + self::EMERGENCY => 'EMERGENCY', + self::ALERT => 'ALERT', + self::CRITICAL => 'CRITICAL', + self::ERROR => 'ERROR', + self::WARN => 'WARNING', + self::NOTICE => 'NOTICE', + self::INFO => 'INFO', + self::DEBUG => 'DEBUG' ); /** @@ -173,65 +184,166 @@ public function isEnabled() /** * Log debug message * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled */ - public function debug($object) + public function debug($object, $context = array()) { - return $this->write($object, self::DEBUG); + return $this->log(self::DEBUG, $object, $context); } /** * Log info message * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled */ - public function info($object) + public function info($object, $context = array()) { - return $this->write($object, self::INFO); + return $this->log(self::INFO, $object, $context); } /** - * Log warn message + * Log notice message * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled */ - public function warn($object) + public function notice($object, $context = array()) { - return $this->write($object, self::WARN); + return $this->log(self::NOTICE, $object, $context); + } + + /** + * Log warning message + * @param mixed $object + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled + */ + public function warning($object, $context = array()) + { + return $this->log(self::WARN, $object, $context); + } + + /** + * DEPRECATED for function warning + * Log warning message + * @param mixed $object + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled + */ + public function warn($object, $context = array()) + { + return $this->log(self::WARN, $object, $context); } /** * Log error message * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled */ - public function error($object) + public function error($object, $context = array()) { - return $this->write($object, self::ERROR); + return $this->log(self::ERROR, $object, $context); } /** + * Log critical message + * @param mixed $object + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled + */ + public function critical($object, $context = array()) + { + return $this->log(self::CRITICAL, $object, $context); + } + + /** + * DEPRECATED for function critical * Log fatal message * @param mixed $object - * @return mixed|false What the Logger returns, or false if Logger not set or not enabled + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled + */ + public function fatal($object, $context = array()) + { + return $this->log(self::CRITICAL, $object, $context); + } + + /** + * Log alert message + * @param mixed $object + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled + */ + public function alert($object, $context = array()) + { + return $this->log(self::ALERT, $object, $context); + } + + /** + * Log emergency message + * @param mixed $object + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled */ - public function fatal($object) + public function emergency($object, $context = array()) { - return $this->write($object, self::FATAL); + return $this->log(self::EMERGENCY, $object, $context); } /** * Log message - * @param mixed The object to log - * @param int The message level - * @return int|false + * @param mixed $level + * @param mixed $object + * @param array $context + * @return mixed|bool What the Logger returns, or false if Logger not set or not enabled + * @throws \InvalidArgumentException If invalid log level */ - protected function write($object, $level) + public function log($level, $object, $context = array()) { - if ($this->enabled && $this->writer && $level <= $this->level) { - return $this->writer->write($object, $level); + if (!isset(self::$levels[$level])) { + throw new \InvalidArgumentException('Invalid log level supplied to function'); + } else if ($this->enabled && $this->writer && $level <= $this->level) { + $message = (string)$object; + if (count($context) > 0) { + if (isset($context['exception']) && $context['exception'] instanceof \Exception) { + $message .= ' - ' . $context['exception']; + unset($context['exception']); + } + $message = $this->interpolate($message, $context); + } + return $this->writer->write($message, $level); } else { return false; } } + + /** + * DEPRECATED for function log + * Log message + * @param mixed $object The object to log + * @param int $level The message level + * @return int|bool + */ + protected function write($object, $level) + { + return $this->log($level, $object); + } + + /** + * Interpolate log message + * @param mixed $message The log message + * @param array $context An array of placeholder values + * @return string The processed string + */ + protected function interpolate($message, $context = array()) + { + $replace = array(); + foreach ($context as $key => $value) { + $replace['{' . $key . '}'] = $value; + } + return strtr($message, $replace); + } } diff --git a/Slim/LogWriter.php b/Slim/LogWriter.php index f26eb0fcb..47c1f4e40 100644 --- a/Slim/LogWriter.php +++ b/Slim/LogWriter.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -66,7 +66,7 @@ public function __construct($resource) * Write message * @param mixed $message * @param int $level - * @return int|false + * @return int|bool */ public function write($message, $level = null) { diff --git a/Slim/Middleware.php b/Slim/Middleware.php index 641226a68..09ff17767 100644 --- a/Slim/Middleware.php +++ b/Slim/Middleware.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -42,7 +42,7 @@ abstract class Middleware { /** - * @var \Slim Reference to the primary application instance + * @var \Slim\Slim Reference to the primary application instance */ protected $app; @@ -57,7 +57,7 @@ abstract class Middleware * This method injects the primary Slim application instance into * this middleware. * - * @param \Slim $application + * @param \Slim\Slim $application */ final public function setApplication($application) { @@ -70,7 +70,7 @@ final public function setApplication($application) * This method retrieves the application previously injected * into this middleware. * - * @return \Slim + * @return \Slim\Slim */ final public function getApplication() { @@ -97,7 +97,7 @@ final public function setNextMiddleware($nextMiddleware) * This method retrieves the next downstream middleware * previously injected into this middleware. * - * @return \Slim|\Slim\Middleware + * @return \Slim\Slim|\Slim\Middleware */ final public function getNextMiddleware() { diff --git a/Slim/Middleware/ContentTypes.php b/Slim/Middleware/ContentTypes.php index b11e46052..809b500c0 100644 --- a/Slim/Middleware/ContentTypes.php +++ b/Slim/Middleware/ContentTypes.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -58,12 +58,13 @@ class ContentTypes extends \Slim\Middleware */ public function __construct($settings = array()) { - $this->contentTypes = array_merge(array( + $defaults = array( 'application/json' => array($this, 'parseJson'), 'application/xml' => array($this, 'parseXml'), 'text/xml' => array($this, 'parseXml'), 'text/csv' => array($this, 'parseCsv') - ), $settings); + ); + $this->contentTypes = array_merge($defaults, $settings); } /** diff --git a/Slim/Middleware/Flash.php b/Slim/Middleware/Flash.php index 510bca300..a1647665b 100644 --- a/Slim/Middleware/Flash.php +++ b/Slim/Middleware/Flash.php @@ -59,7 +59,6 @@ class Flash extends \Slim\Middleware implements \ArrayAccess, \IteratorAggregate /** * Constructor - * @param \Slim $app * @param array $settings */ public function __construct($settings = array()) diff --git a/Slim/Middleware/MethodOverride.php b/Slim/Middleware/MethodOverride.php index 6f5fffb10..f43b41f0b 100644 --- a/Slim/Middleware/MethodOverride.php +++ b/Slim/Middleware/MethodOverride.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -36,7 +36,7 @@ * HTTP Method Override * * This is middleware for a Slim application that allows traditional - * desktop browsers to submit psuedo PUT and DELETE requests by relying + * desktop browsers to submit pseudo PUT and DELETE requests by relying * on a pre-determined request parameter. Without this middleware, * desktop browsers are only able to submit GET and POST requests. * @@ -55,7 +55,6 @@ class MethodOverride extends \Slim\Middleware /** * Constructor - * @param \Slim $app * @param array $settings */ public function __construct($settings = array()) @@ -72,7 +71,6 @@ public function __construct($settings = array()) * modifies the environment settings so downstream middleware and/or the Slim * application will treat the request with the desired HTTP method. * - * @param array $env * @return array[status, header, body] */ public function call() diff --git a/Slim/Middleware/PrettyExceptions.php b/Slim/Middleware/PrettyExceptions.php index e079e30f5..b67545e19 100644 --- a/Slim/Middleware/PrettyExceptions.php +++ b/Slim/Middleware/PrettyExceptions.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -66,6 +66,7 @@ public function call() try { $this->next->call(); } catch (\Exception $e) { + $log = $this->app->getLog(); // Force Slim to append log to env if not already $env = $this->app->environment(); $env['slim.log']->error($e); $this->app->contentType('text/html'); diff --git a/Slim/Middleware/SessionCookie.php b/Slim/Middleware/SessionCookie.php index 1747a8677..d13dd94cd 100644 --- a/Slim/Middleware/SessionCookie.php +++ b/Slim/Middleware/SessionCookie.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -51,7 +51,7 @@ * session data in a database or alternative server-side cache. * * Because this class stores serialized session data in an HTTP cookie, - * you are inherently limtied to 4 Kb. If you attempt to store + * you are inherently limited to 4 Kb. If you attempt to store * more than this amount, serialization will fail. * * @package Slim @@ -72,7 +72,7 @@ class SessionCookie extends \Slim\Middleware */ public function __construct($settings = array()) { - $this->settings = array_merge(array( + $defaults = array( 'expires' => '20 minutes', 'path' => '/', 'domain' => null, @@ -82,7 +82,8 @@ public function __construct($settings = array()) 'secret' => 'CHANGE_ME', 'cipher' => MCRYPT_RIJNDAEL_256, 'cipher_mode' => MCRYPT_MODE_CBC - ), $settings); + ); + $this->settings = array_merge($defaults, $settings); if (is_string($this->settings['expires'])) { $this->settings['expires'] = strtotime($this->settings['expires']); } @@ -119,7 +120,6 @@ public function call() /** * Load session - * @param array $env */ protected function loadSession() { @@ -155,14 +155,17 @@ protected function saveSession() if (strlen($value) > 4096) { $this->app->getLog()->error('WARNING! Slim\Middleware\SessionCookie data size is larger than 4KB. Content save failed.'); } else { - $this->app->response()->setCookie($this->settings['name'], array( - 'value' => $value, - 'domain' => $this->settings['domain'], - 'path' => $this->settings['path'], - 'expires' => $this->settings['expires'], - 'secure' => $this->settings['secure'], - 'httponly' => $this->settings['httponly'] - )); + $this->app->response()->setCookie( + $this->settings['name'], + array( + 'value' => $value, + 'domain' => $this->settings['domain'], + 'path' => $this->settings['path'], + 'expires' => $this->settings['expires'], + 'secure' => $this->settings['secure'], + 'httponly' => $this->settings['httponly'] + ) + ); } session_destroy(); } diff --git a/Slim/Route.php b/Slim/Route.php index 445fbe9d5..d1f7fbf77 100644 --- a/Slim/Route.php +++ b/Slim/Route.php @@ -285,6 +285,7 @@ public function via() /** * Detect support for an HTTP method + * @param string $method * @return bool */ public function supportsHttpMethod($method) @@ -320,7 +321,7 @@ public function setMiddleware($middleware) if (is_callable($middleware)) { $this->middleware[] = $middleware; } elseif (is_array($middleware)) { - foreach($middleware as $callable) { + foreach ($middleware as $callable) { if (!is_callable($callable)) { throw new \InvalidArgumentException('All Route middleware must be callable'); } @@ -347,8 +348,11 @@ public function setMiddleware($middleware) public function matches($resourceUri) { //Convert URL params into regex patterns, construct a regex for this route, init params - $patternAsRegex = preg_replace_callback('#:([\w]+)\+?#', array($this, 'matchesCallback'), - str_replace(')', ')?', (string) $this->pattern)); + $patternAsRegex = preg_replace_callback( + '#:([\w]+)\+?#', + array($this, 'matchesCallback'), + str_replace(')', ')?', (string) $this->pattern) + ); if (substr($this->pattern, -1) === '/') { $patternAsRegex .= '?'; } @@ -372,8 +376,8 @@ public function matches($resourceUri) /** * Convert a URL parameter (e.g. ":id", ":id+") into a regular expression - * @param array URL parameters - * @return string Regular expression for URL parameter + * @param array $m URL parameters + * @return string Regular expression for URL parameter */ protected function matchesCallback($m) { @@ -413,4 +417,23 @@ public function conditions(array $conditions) return $this; } + + /** + * Dispatch route + * + * This method invokes the route object's callable. If middleware is + * registered for the route, each callable middleware is invoked in + * the order specified. + * + * @return bool + */ + public function dispatch() + { + foreach ($this->middleware as $mw) { + call_user_func_array($mw, array($this)); + } + + $return = call_user_func_array($this->getCallable(), array_values($this->getParams())); + return ($return === false)? false : true; + } } diff --git a/Slim/Router.php b/Slim/Router.php index 44979ea0a..2f47fb895 100644 --- a/Slim/Router.php +++ b/Slim/Router.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -63,12 +63,18 @@ class Router */ protected $matchedRoutes; + /** + * @var array Array containing all route groups + */ + protected $routeGroups; + /** * Constructor */ public function __construct() { $this->routes = array(); + $this->routeGroups = array(); } /** @@ -100,7 +106,7 @@ public function getMatchedRoutes($httpMethod, $resourceUri, $reload = false) if ($reload || is_null($this->matchedRoutes)) { $this->matchedRoutes = array(); foreach ($this->routes as $route) { - if (!$route->supportsHttpMethod($httpMethod)) { + if (!$route->supportsHttpMethod($httpMethod) && !$route->supportsHttpMethod("ANY")) { continue; } @@ -114,25 +120,66 @@ public function getMatchedRoutes($httpMethod, $resourceUri, $reload = false) } /** - * Map a route object to a callback function - * @param string $pattern The URL pattern (ie. "/books/:id") - * @param mixed $callable Anything that returns TRUE for is_callable() - * @return \Slim\Route + * Add a route object to the router + * @param \Slim\Route $route The Slim Route */ - public function map($pattern, $callable) + public function map(\Slim\Route $route) { - $route = new \Slim\Route($pattern, $callable); + list($groupPattern, $groupMiddleware) = $this->processGroups(); + + $route->setPattern($groupPattern . $route->getPattern()); $this->routes[] = $route; - return $route; + + foreach ($groupMiddleware as $middleware) { + $route->setMiddleware($middleware); + } + } + + /** + * A helper function for processing the group's pattern and middleware + * @return array Returns an array with the elements: pattern, middlewareArr + */ + protected function processGroups() + { + $pattern = ""; + $middleware = array(); + foreach ($this->routeGroups as $group) { + $k = key($group); + $pattern .= $k; + if (is_array($group[$k])) { + $middleware = array_merge($middleware, $group[$k]); + } + } + return array($pattern, $middleware); + } + + /** + * Add a route group to the array + * @param string $group The group pattern (ie. "/books/:id") + * @param array|null $middleware Optional parameter array of middleware + * @return int The index of the new group + */ + public function pushGroup($group, $middleware = array()) + { + return array_push($this->routeGroups, array($group => $middleware)); + } + + /** + * Removes the last route group from the array + * @return bool True if successful, else False + */ + public function popGroup() + { + return (array_pop($this->routeGroups) !== null); } /** * Get URL for named route * @param string $name The name of the route - * @param array Associative array of URL parameter names and replacement values - * @throws RuntimeException If named route not found - * @return string The URL for the given route populated with provided replacement values + * @param array $params Associative array of URL parameter names and replacement values + * @throws \RuntimeException If named route not found + * @return string The URL for the given route populated with provided replacement values */ public function urlFor($name, $params = array()) { @@ -140,8 +187,8 @@ public function urlFor($name, $params = array()) throw new \RuntimeException('Named route not found for name: ' . $name); } $search = array(); - foreach (array_keys($params) as $key) { - $search[] = '#:' . $key . '\+?(?!\w)#'; + foreach ($params as $key => $value) { + $search[] = '#:' . preg_quote($key, '#') . '\+?(?!\w)#'; } $pattern = preg_replace($search, $params, $this->getNamedRoute($name)->getPattern()); @@ -149,36 +196,11 @@ public function urlFor($name, $params = array()) return preg_replace('#\(/?:.+\)|\(|\)#', '', $pattern); } - /** - * Dispatch route - * - * This method invokes the route object's callable. If middleware is - * registered for the route, each callable middleware is invoked in - * the order specified. - * - * @param \Slim\Route $route The route object - * @return bool Was route callable invoked successfully? - */ - public function dispatch(\Slim\Route $route) - { - $this->currentRoute = $route; - - //Invoke middleware - foreach ($route->getMiddleware() as $mw) { - call_user_func_array($mw, array($route)); - } - - //Invoke callable - call_user_func_array($route->getCallable(), array_values($route->getParams())); - - return true; - } - /** * Add named route * @param string $name The route name * @param \Slim\Route $route The route object - * @throws \RuntimeException If a named route already exists with the same name + * @throws \RuntimeException If a named route already exists with the same name */ public function addNamedRoute($name, \Slim\Route $route) { diff --git a/Slim/Slim.php b/Slim/Slim.php index 5e10c6073..86f7537cd 100644 --- a/Slim/Slim.php +++ b/Slim/Slim.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -49,52 +49,22 @@ class Slim /** * @const string */ - const VERSION = '2.2.0'; + const VERSION = '2.3.0'; /** - * @var array[\Slim] + * @var \Slim\Helper\Set */ - protected static $apps = array(); - - /** - * @var string - */ - protected $name; + public $container; /** - * @var array - */ - protected $environment; - - /** - * @var \Slim\Http\Request - */ - protected $request; - - /** - * @var \Slim\Http\Response - */ - protected $response; - - /** - * @var \Slim\Router - */ - protected $router; - - /** - * @var \Slim\View - */ - protected $view; - - /** - * @var array + * @var array[\Slim] */ - protected $settings; + protected static $apps = array(); /** * @var string */ - protected $mode; + protected $name; /** * @var array @@ -173,44 +143,98 @@ public static function registerAutoloader() * Constructor * @param array $userSettings Associative array of application settings */ - public function __construct($userSettings = array()) + public function __construct(array $userSettings = array()) { - // Setup Slim application - $this->settings = array_merge(static::getDefaultSettings(), $userSettings); - $this->environment = \Slim\Environment::getInstance(); - $this->request = new \Slim\Http\Request($this->environment); - $this->response = new \Slim\Http\Response(); - $this->router = new \Slim\Router(); + // Setup IoC container + $this->container = new \Slim\Helper\Set(); + $this->container['settings'] = array_merge(static::getDefaultSettings(), $userSettings); + + // Default environment + $this->container->singleton('environment', function ($c) { + return \Slim\Environment::getInstance(); + }); + + // Default request + $this->container->singleton('request', function ($c) { + return new \Slim\Http\Request($c['environment']); + }); + + // Default response + $this->container->singleton('response', function ($c) { + return new \Slim\Http\Response(); + }); + + // Default router + $this->container->singleton('router', function ($c) { + return new \Slim\Router(); + }); + + // Default view + $this->container->singleton('view', function ($c) { + $viewClass = $c['settings']['view']; + + return ($viewClass instanceOf \Slim\View) ? $viewClass : new $viewClass; + }); + + // Default log writer + $this->container->singleton('logWriter', function ($c) { + $logWriter = $c['settings']['log.writer']; + + return is_object($logWriter) ? $logWriter : new \Slim\LogWriter($c['environment']['slim.errors']); + }); + + // Default log + $this->container->singleton('log', function ($c) { + $log = new \Slim\Log($c['logWriter']); + $log->setEnabled($c['settings']['log.enabled']); + $log->setLevel($c['settings']['log.level']); + $env = $c['environment']; + $env['slim.log'] = $log; + + return $log; + }); + + // Default mode + $this->container['mode'] = function ($c) { + $mode = $c['settings']['mode']; + + if (isset($_ENV['SLIM_MODE'])) { + $mode = $_ENV['SLIM_MODE']; + } else { + $envMode = getenv('SLIM_MODE'); + if ($envMode !== false) { + $mode = $envMode; + } + } + + return $mode; + }; + + // Define default middleware stack $this->middleware = array($this); $this->add(new \Slim\Middleware\Flash()); $this->add(new \Slim\Middleware\MethodOverride()); - // Determine application mode - $this->getMode(); - - // Setup view - $this->view($this->config('view')); - // Make default if first instance if (is_null(static::getInstance())) { $this->setName('default'); } + } - // Set default logger that writes to stderr (may be overridden with middleware) - $logWriter = $this->config('log.writer'); - if (!$logWriter) { - $logWriter = new \Slim\LogWriter($this->environment['slim.errors']); - } - $log = new \Slim\Log($logWriter); - $log->setEnabled($this->config('log.enabled')); - $log->setLevel($this->config('log.level')); - $this->environment['slim.log'] = $log; + public function __get($name) + { + return $this->container[$name]; + } + + public function __set($name, $value) + { + $this->container[$name] = $value; } /** * Get application instance by name * @param string $name The name of the Slim application - * @return \Slim|null + * @return \Slim\Slim|null */ public static function getInstance($name = 'default') { @@ -255,6 +279,7 @@ public static function getDefaultSettings() 'templates.path' => './templates', 'view' => '\Slim\View', // Cookies + 'cookies.encrypt' => false, 'cookies.lifetime' => '20 minutes', 'cookies.path' => '/', 'cookies.domain' => null, @@ -297,7 +322,9 @@ public function config($name, $value = null) return isset($this->settings[$name]) ? $this->settings[$name] : null; } } else { - $this->settings[$name] = $value; + $settings = $this->settings; + $settings[$name] = $value; + $this->settings = $settings; } } @@ -316,19 +343,6 @@ public function config($name, $value = null) */ public function getMode() { - if (!isset($this->mode)) { - if (isset($_ENV['SLIM_MODE'])) { - $this->mode = $_ENV['SLIM_MODE']; - } else { - $envMode = getenv('SLIM_MODE'); - if ($envMode !== false) { - $this->mode = $envMode; - } else { - $this->mode = $this->config('mode'); - } - } - } - return $this->mode; } @@ -361,7 +375,7 @@ public function configureMode($mode, $callable) */ public function getLog() { - return $this->environment['slim.log']; + return $this->log; } /******************************************************************************** @@ -369,7 +383,7 @@ public function getLog() *******************************************************************************/ /** - * Add GET|POST|PUT|DELETE route + * Add GET|POST|PUT|PATCH|DELETE route * * Adds a new route to the router with associated callable. This * route will only be invoked when the HTTP request's method matches @@ -402,7 +416,8 @@ protected function mapRoute($args) { $pattern = array_shift($args); $callable = array_pop($args); - $route = $this->router->map($pattern, $callable); + $route = new \Slim\Route($pattern, $callable); + $this->router->map($route); if (count($args) > 0) { $route->setMiddleware($args); } @@ -458,6 +473,18 @@ public function put() return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PUT); } + /** + * Add PATCH route + * @see mapRoute() + * @return \Slim\Route + */ + public function patch() + { + $args = func_get_args(); + + return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_PATCH); + } + /** * Add DELETE route * @see mapRoute() @@ -482,6 +509,40 @@ public function options() return $this->mapRoute($args)->via(\Slim\Http\Request::METHOD_OPTIONS); } + /** + * Route Groups + * + * This method accepts a route pattern and a callback all Route + * declarations in the callback will be prepended by the group(s) + * that it is in + * + * Accepts the same paramters as a standard route so: + * (pattern, middleware1, middleware2, ..., $callback) + */ + public function group() + { + $args = func_get_args(); + $pattern = array_shift($args); + $callable = array_pop($args); + $this->router->pushGroup($pattern, $args); + if (is_callable($callable)) { + call_user_func($callable); + } + $this->router->popGroup(); + } + + /* + * Add route for any HTTP method + * @see mapRoute() + * @return \Slim\Route + */ + public function any() + { + $args = func_get_args(); + + return $this->mapRoute($args)->via("ANY"); + } + /** * Not Found Handler * @@ -503,12 +564,13 @@ public function options() * * @param mixed $callable Anything that returns true for is_callable() */ - public function notFound( $callable = null ) { - if ( is_callable($callable) ) { + public function notFound ($callable = null) + { + if (is_callable($callable)) { $this->notFound = $callable; } else { ob_start(); - if ( is_callable($this->notFound) ) { + if (is_callable($this->notFound)) { call_user_func($this->notFound); } else { call_user_func(array($this, 'defaultNotFound')); @@ -566,7 +628,7 @@ public function error($argument = null) protected function callErrorHandler($argument = null) { ob_start(); - if ( is_callable($this->error) ) { + if (is_callable($this->error)) { call_user_func_array($this->error, array($argument)); } else { call_user_func_array(array($this, 'defaultError'), array($argument)); @@ -653,7 +715,7 @@ public function view($viewClass = null) /** * Render a template * - * Call this method within a GET, POST, PUT, DELETE, NOT FOUND, or ERROR + * Call this method within a GET, POST, PUT, PATCH, DELETE, NOT FOUND, or ERROR * callable to render a template whose output is appended to the * current HTTP response body. How the template is rendered is * delegated to the current View. @@ -692,8 +754,8 @@ public function render($template, $data = array(), $status = null) public function lastModified($time) { if (is_integer($time)) { - $this->response['Last-Modified'] = date(DATE_RFC1123, $time); - if ($time === strtotime($this->request->headers('IF_MODIFIED_SINCE'))) { + $this->response->headers->set('Last-Modified', date(DATE_RFC1123, $time)); + if ($time === strtotime($this->request->headers->get('IF_MODIFIED_SINCE'))) { $this->halt(304); } } else { @@ -726,11 +788,13 @@ public function etag($value, $type = 'strong') //Set etag value $value = '"' . $value . '"'; - if ($type === 'weak') $value = 'W/'.$value; + if ($type === 'weak') { + $value = 'W/'.$value; + } $this->response['ETag'] = $value; //Check conditional GET - if ($etagsHeader = $this->request->headers('IF_NONE_MATCH')) { + if ($etagsHeader = $this->request->headers->get('IF_NONE_MATCH')) { $etags = preg_split('@\s*,\s*@', $etagsHeader); if (in_array($value, $etags) || in_array('*', $etags)) { $this->halt(304); @@ -756,7 +820,7 @@ public function expires($time) if (is_string($time)) { $time = strtotime($time); } - $this->response['Expires'] = gmdate(DATE_RFC1123, $time); + $this->response->headers->set('Expires', gmdate(DATE_RFC1123, $time)); } /******************************************************************************** @@ -764,7 +828,7 @@ public function expires($time) *******************************************************************************/ /** - * Set unencrypted HTTP cookie + * Set HTTP cookie to be sent with the HTTP response * * @param string $name The cookie name * @param string $value The cookie value @@ -779,18 +843,19 @@ public function expires($time) */ public function setCookie($name, $value, $time = null, $path = null, $domain = null, $secure = null, $httponly = null) { - $this->response->setCookie($name, array( + $settings = array( 'value' => $value, 'expires' => is_null($time) ? $this->config('cookies.lifetime') : $time, 'path' => is_null($path) ? $this->config('cookies.path') : $path, 'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain, 'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure, 'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly - )); + ); + $this->response->cookies->set($name, $settings); } /** - * Get value of unencrypted HTTP cookie + * Get value of HTTP cookie from the current HTTP request * * Return the value of a cookie from the current HTTP request, * or return NULL if cookie does not exist. Cookies created during @@ -799,12 +864,30 @@ public function setCookie($name, $value, $time = null, $path = null, $domain = n * @param string $name * @return string|null */ - public function getCookie($name) + public function getCookie($name, $deleteIfInvalid = true) { - return $this->request->cookies($name); + // Get cookie value + $value = $this->request->cookies->get($name); + + // Decode if encrypted + if ($this->config('cookies.encrypt')) { + $value = \Slim\Http\Util::decodeSecureCookie( + $value, + $this->config('cookies.secret_key'), + $this->config('cookies.cipher'), + $this->config('cookies.cipher_mode') + ); + if ($value === false && $deleteIfInvalid) { + $this->deleteCookie($name); + } + } + + return $value; } /** + * DEPRECATION WARNING! Use `setCookie` with the `cookies.encrypt` app setting set to `true`. + * * Set encrypted HTTP cookie * * @param string $name The cookie name @@ -818,23 +901,14 @@ public function getCookie($name) * HTTPS connection from the client * @param bool $httponly When TRUE the cookie will be made accessible only through the HTTP protocol */ - public function setEncryptedCookie($name, $value, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null) + public function setEncryptedCookie($name, $value, $expires = null, $path = null, $domain = null, $secure = false, $httponly = false) { - $expires = is_null($expires) ? $this->config('cookies.lifetime') : $expires; - if (is_string($expires)) { - $expires = strtotime($expires); - } - $secureValue = \Slim\Http\Util::encodeSecureCookie( - $value, - $expires, - $this->config('cookies.secret_key'), - $this->config('cookies.cipher'), - $this->config('cookies.cipher_mode') - ); - $this->setCookie($name, $secureValue, $expires, $path, $domain, $secure, $httponly); + $this->setCookie($name, $value, $expires, $path, $domain, $secure, $httponly); } /** + * DEPRECATION WARNING! Use `getCookie` with the `cookies.encrypt` app setting set to `true`. + * * Get value of encrypted HTTP cookie * * Return the value of an encrypted cookie from the current HTTP request, @@ -842,21 +916,12 @@ public function setEncryptedCookie($name, $value, $expires = null, $path = null, * the current request will not be available until the next request. * * @param string $name - * @return string|false + * @param bool $deleteIfInvalid + * @return string|bool */ public function getEncryptedCookie($name, $deleteIfInvalid = true) { - $value = \Slim\Http\Util::decodeSecureCookie( - $this->request->cookies($name), - $this->config('cookies.secret_key'), - $this->config('cookies.cipher'), - $this->config('cookies.cipher_mode') - ); - if ($value === false && $deleteIfInvalid) { - $this->deleteCookie($name); - } - - return $value; + return $this->getCookie($name, $deleteIfInvalid); } /** @@ -877,12 +942,13 @@ public function getEncryptedCookie($name, $deleteIfInvalid = true) */ public function deleteCookie($name, $path = null, $domain = null, $secure = null, $httponly = null) { - $this->response->deleteCookie($name, array( + $settings = array( 'domain' => is_null($domain) ? $this->config('cookies.domain') : $domain, 'path' => is_null($path) ? $this->config('cookies.path') : $path, 'secure' => is_null($secure) ? $this->config('cookies.secure') : $secure, 'httponly' => is_null($httponly) ? $this->config('cookies.httponly') : $httponly - )); + ); + $this->response->cookies->remove($name, $settings); } /******************************************************************************** @@ -968,16 +1034,16 @@ public function pass() */ public function contentType($type) { - $this->response['Content-Type'] = $type; + $this->response->headers->set('Content-Type', $type); } /** * Set the HTTP response status code - * @param int $status The HTTP response status code + * @param int $code The HTTP response status code */ public function status($code) { - $this->response->status($code); + $this->response->setStatus($code); } /** @@ -1071,7 +1137,7 @@ public function hook($name, $callable, $priority = 10) /** * Invoke hook * @param string $name The hook name - * @param mixed $hookArgs (Optional) Argument for hooked functions + * @param mixed $hookArg (Optional) Argument for hooked functions */ public function applyHook($name, $hookArg = null) { @@ -1174,7 +1240,10 @@ public function run() $this->middleware[0]->call(); //Fetch status, header, and body - list($status, $header, $body) = $this->response->finalize(); + list($status, $headers, $body) = $this->response->finalize(); + + // Serialize cookies (with optional encryption) + \Slim\Http\Util::serializeCookies($headers, $this->response->cookies, $this->settings); //Send headers if (headers_sent() === false) { @@ -1186,7 +1255,7 @@ public function run() } //Send headers - foreach ($header as $name => $value) { + foreach ($headers as $name => $value) { $hValues = explode("\n", $value); foreach ($hValues as $hVal) { header("$name: $hVal", false); @@ -1219,7 +1288,7 @@ public function call() foreach ($matchedRoutes as $route) { try { $this->applyHook('slim.before.dispatch'); - $dispatched = $this->router->dispatch($route); + $dispatched = $route->dispatch(); $this->applyHook('slim.after.dispatch'); if ($dispatched) { break; @@ -1229,7 +1298,7 @@ public function call() } } if (!$dispatched) { - $this->notFound(); + $this->notFound(); } $this->applyHook('slim.after.router'); $this->stop(); @@ -1264,16 +1333,16 @@ public function call() * @param string $errstr The error message * @param string $errfile The absolute path to the affected file * @param int $errline The line number of the error in the affected file - * @return true + * @return bool * @throws \ErrorException */ public static function handleErrors($errno, $errstr = '', $errfile = '', $errline = '') { - if (error_reporting() & $errno) { - throw new \ErrorException($errstr, $errno, 0, $errfile, $errline); + if (!($errno & error_reporting())) { + return; } - return true; + throw new \ErrorException($errstr, $errno, 0, $errfile, $errline); } /** diff --git a/Slim/View.php b/Slim/View.php index 0ce956899..48f2dcb2d 100644 --- a/Slim/View.php +++ b/Slim/View.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * @package Slim * * MIT LICENSE @@ -50,132 +50,177 @@ class View { /** - * @var string Absolute or relative filesystem path to a specific template - * - * DEPRECATION WARNING! - * This variable will be removed in the near future - */ - protected $templatePath = ''; - - /** - * @var array Associative array of template variables + * Data available to the view templates + * @var \Slim\Helper\Set */ - protected $data = array(); + protected $data; /** - * @var string Absolute or relative path to the application's templates directory + * Path to templates base directory (without trailing slash) + * @var string */ protected $templatesDirectory; /** * Constructor - * - * This is empty but may be implemented in a subclass */ public function __construct() { + $this->data = new \Slim\Helper\Set(); + } + + /******************************************************************************** + * Data methods + *******************************************************************************/ + + /** + * Does view data have value with key? + * @param string $key + * @return boolean + */ + public function has($key) + { + return $this->data->has($key); + } + + /** + * Return view data value with key + * @param string $key + * @return mixed + */ + public function get($key) + { + return $this->data->get($key); + } + + /** + * Set view data value with key + * @param string $key + * @param mixed $value + */ + public function set($key, $value) + { + $this->data->set($key, $value); + } + /** + * Return view data + * @return array + */ + public function all() + { + return $this->data->all(); + } + + /** + * Replace view data + * @param array $data + */ + public function replace(array $data) + { + $this->data->replace($data); } /** - * Get data - * @param string|null $key - * @return mixed If key is null, array of template data; - * If key exists, value of datum with key; - * If key does not exist, null; + * Clear view data + */ + public function clear() + { + $this->data->clear(); + } + + /******************************************************************************** + * Legacy data methods + *******************************************************************************/ + + /** + * DEPRECATION WARNING! This method will be removed in the next major point release + * + * Get data from view */ public function getData($key = null) { if (!is_null($key)) { return isset($this->data[$key]) ? $this->data[$key] : null; } else { - return $this->data; + return $this->data->all(); } } /** - * Set data - * - * If two arguments: - * A single datum with key is assigned value; + * DEPRECATION WARNING! This method will be removed in the next major point release * - * $view->setData('color', 'red'); - * - * If one argument: - * Replace all data with provided array keys and values; - * - * $view->setData(array('color' => 'red', 'number' => 1)); - * - * @param mixed - * @param mixed - * @throws InvalidArgumentException If incorrect method signature + * Set data for view */ public function setData() { $args = func_get_args(); if (count($args) === 1 && is_array($args[0])) { - $this->data = $args[0]; + $this->data->replace($args[0]); } elseif (count($args) === 2) { - $this->data[(string) $args[0]] = $args[1]; + $this->data->set($args[0], $args[1]); } else { throw new \InvalidArgumentException('Cannot set View data with provided arguments. Usage: `View::setData( $key, $value );` or `View::setData([ key => value, ... ]);`'); } } /** - * Append new data to existing template data - * @param array - * @throws InvalidArgumentException If not given an array argument + * DEPRECATION WARNING! This method will be removed in the next major point release + * + * Append data to view + * @param array $data */ public function appendData($data) { if (!is_array($data)) { throw new \InvalidArgumentException('Cannot append view data. Expected array argument.'); } - $this->data = array_merge($this->data, $data); + $this->data->replace($data); } + /******************************************************************************** + * Resolve template paths + *******************************************************************************/ + /** - * Get templates directory - * @return string|null Path to templates directory without trailing slash; - * Returns null if templates directory not set; + * Set the base directory that contains view templates + * @param string $directory + * @throws \InvalidArgumentException If directory is not a directory */ - public function getTemplatesDirectory() + public function setTemplatesDirectory($directory) { - return $this->templatesDirectory; + $this->templatesDirectory = rtrim($directory, DIRECTORY_SEPARATOR); } /** - * Set templates directory - * @param string $dir + * Get templates base directory + * @return string */ - public function setTemplatesDirectory($dir) + public function getTemplatesDirectory() { - $this->templatesDirectory = rtrim($dir, '/'); + return $this->templatesDirectory; } /** - * Set template - * @param string $template - * @throws RuntimeException If template file does not exist - * - * DEPRECATION WARNING! - * This method will be removed in the near future. + * Get fully qualified path to template file using templates base directory + * @param string $file The template file pathname relative to templates base directory + * @return string */ - public function setTemplate($template) + public function getTemplatePathname($file) { - $this->templatePath = $this->getTemplatesDirectory() . '/' . ltrim($template, '/'); - if (!file_exists($this->templatePath)) { - throw new \RuntimeException('View cannot render template `' . $this->templatePath . '`. Template does not exist.'); - } + return $this->templatesDirectory . DIRECTORY_SEPARATOR . ltrim($file, DIRECTORY_SEPARATOR); } + /******************************************************************************** + * Rendering + *******************************************************************************/ + /** * Display template * * This method echoes the rendered template to the current output buffer * - * @param string $template Pathname of template file relative to templates directoy + * @param string $template Pathname of template file relative to templates directory */ public function display($template) { @@ -183,12 +228,9 @@ public function display($template) } /** - * Fetch rendered template - * - * This method returns the rendered template - * - * @param string $template Pathname of template file relative to templates directory - * @return string + * Return the contents of a rendered template file + * @var string $template The template pathname, relative to the template base directory + * @return string The rendered template */ public function fetch($template) { @@ -196,20 +238,23 @@ public function fetch($template) } /** - * Render template + * Render a template file * - * @param string $template Pathname of template file relative to templates directory - * @return string + * NOTE: This method should be overridden by custom view subclasses * - * DEPRECATION WARNING! - * Use `\Slim\View::fetch` to return a rendered template instead of `\Slim\View::render`. + * @var string $template The template pathname, relative to the template base directory + * @return string The rendered template + * @throws \RuntimeException If resolved template pathname is not a valid file */ - public function render($template) + protected function render($template) { - $this->setTemplate($template); - extract($this->data); + $templatePathname = $this->getTemplatePathname($template); + if (!is_file($templatePathname)) { + throw new \RuntimeException("View cannot render `$template` because the template does not exist"); + } + extract($this->data->all()); ob_start(); - require $this->templatePath; + require $templatePathname; return ob_get_clean(); } diff --git a/composer.json b/composer.json index a49c05ec2..656de3297 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ } ], "require": { - "php": ">=5.3.0" + "php": ">=5.3.0", + "ext-mcrypt": "*" }, "autoload": { "psr-0": { "Slim": "." } diff --git a/index.php b/index.php index c3e67454c..9bfd62d98 100644 --- a/index.php +++ b/index.php @@ -26,13 +26,15 @@ * * Here we define several Slim application routes that respond * to appropriate HTTP request methods. In this example, the second - * argument for `Slim::get`, `Slim::post`, `Slim::put`, and `Slim::delete` + * argument for `Slim::get`, `Slim::post`, `Slim::put`, `Slim::patch`, and `Slim::delete` * is an anonymous function. */ // GET route -$app->get('/', function () { - $template = <<get( + '/', + function () { + $template = << @@ -125,23 +127,38 @@ EOT; - echo $template; -}); + echo $template; + } +); // POST route -$app->post('/post', function () { - echo 'This is a POST route'; -}); +$app->post( + '/post', + function () { + echo 'This is a POST route'; + } +); // PUT route -$app->put('/put', function () { - echo 'This is a PUT route'; +$app->put( + '/put', + function () { + echo 'This is a PUT route'; + } +); + +// PATCH route +$app->patch('/patch', function () { + echo 'This is a PATCH route'; }); // DELETE route -$app->delete('/delete', function () { - echo 'This is a DELETE route'; -}); +$app->delete( + '/delete', + function () { + echo 'This is a DELETE route'; + } +); /** * Step 4: Run the Slim application diff --git a/tests/EnvironmentTest.php b/tests/EnvironmentTest.php index 91505b8ce..e86b24ac5 100644 --- a/tests/EnvironmentTest.php +++ b/tests/EnvironmentTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -292,7 +292,7 @@ public function testSetsSpecialHeaders() $env = \Slim\Environment::getInstance(true); $this->assertEquals('text/csv', $env['CONTENT_TYPE']); $this->assertEquals('100', $env['CONTENT_LENGTH']); - $this->assertEquals('XmlHttpRequest', $env['X_REQUESTED_WITH']); + $this->assertEquals('XmlHttpRequest', $env['HTTP_X_REQUESTED_WITH']); } /** diff --git a/tests/Helper/SetTest.php b/tests/Helper/SetTest.php new file mode 100644 index 000000000..674e2b167 --- /dev/null +++ b/tests/Helper/SetTest.php @@ -0,0 +1,183 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 2.3.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +class SetTest extends PHPUnit_Framework_TestCase +{ + protected $bag; + protected $property; + + public function setUp() + { + $this->bag = new \Slim\Helper\Set(); + $this->property = new \ReflectionProperty($this->bag, 'data'); + $this->property->setAccessible(true); + } + + public function testSet() + { + $this->bag->set('foo', 'bar'); + $this->assertArrayHasKey('foo', $this->property->getValue($this->bag)); + $bag = $this->property->getValue($this->bag); + $this->assertEquals('bar', $bag['foo']); + } + + public function testGet() + { + $this->property->setValue($this->bag, array('foo' => 'bar')); + $this->assertEquals('bar', $this->bag->get('foo')); + } + + public function testGetNotExists() + { + $this->property->setValue($this->bag, array('foo' => 'bar')); + $this->assertEquals('default', $this->bag->get('abc', 'default')); + } + + public function testAdd() + { + $this->bag->replace(array( + 'abc' => '123', + 'foo' => 'bar' + )); + $this->assertArrayHasKey('abc', $this->property->getValue($this->bag)); + $this->assertArrayHasKey('foo', $this->property->getValue($this->bag)); + $bag = $this->property->getValue($this->bag); + $this->assertEquals('123', $bag['abc']); + $this->assertEquals('bar', $bag['foo']); + } + + public function testAll() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->assertEquals($data, $this->bag->all()); + } + + public function testKeys() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->assertEquals(array('abc', 'foo'), $this->bag->keys()); + } + + public function testRemove() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->bag->remove('foo'); + $this->assertEquals(array('abc' => '123'), $this->property->getValue($this->bag)); + } + + public function testClear() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->bag->clear(); + $this->assertEquals(array(), $this->property->getValue($this->bag)); + } + + public function testArrayAccessGet() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->assertEquals('bar', $this->bag['foo']); + } + + public function testArrayAccessSet() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->bag['foo'] = 'changed'; + $bag = $this->property->getValue($this->bag); + $this->assertEquals('changed', $bag['foo']); + } + + public function testArrayAccessExists() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->assertTrue(isset($this->bag['foo'])); + $this->assertFalse(isset($this->bag['bar'])); + } + + public function testArrayAccessUnset() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + unset($this->bag['foo']); + $this->assertEquals(array('abc' => '123'), $this->property->getValue($this->bag)); + } + + public function testCount() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->assertEquals(2, count($this->bag)); + } + + public function testGetIterator() + { + $data = array( + 'abc' => '123', + 'foo' => 'bar' + ); + $this->property->setValue($this->bag, $data); + $this->assertInstanceOf('\ArrayIterator', $this->bag->getIterator()); + } +} diff --git a/tests/Http/CookiesTest.php b/tests/Http/CookiesTest.php new file mode 100644 index 000000000..33868cef0 --- /dev/null +++ b/tests/Http/CookiesTest.php @@ -0,0 +1,92 @@ + + * @copyright 2011 Josh Lockhart + * @link http://www.slimframework.com + * @license http://www.slimframework.com/license + * @version 2.3.0 + * + * MIT LICENSE + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +class CookiesTest extends PHPUnit_Framework_TestCase +{ + public function testSetWithStringValue() + { + $c = new \Slim\Http\Cookies(); + $c->set('foo', 'bar'); + $this->assertAttributeEquals( + array( + 'foo' => array( + 'value' => 'bar', + 'expires' => null, + 'domain' => null, + 'path' => null, + 'secure' => false, + 'httponly' => false + ) + ), + 'data', + $c + ); + } + + public function testSetWithArrayValue() + { + $now = time(); + $c = new \Slim\Http\Cookies(); + $c->set('foo', array( + 'value' => 'bar', + 'expires' => $now + 86400, + 'domain' => '.example.com', + 'path' => '/', + 'secure' => true, + 'httponly' => true + )); + $this->assertAttributeEquals( + array( + 'foo' => array( + 'value' => 'bar', + 'expires' => $now + 86400, + 'domain' => '.example.com', + 'path' => '/', + 'secure' => true, + 'httponly' => true + ) + ), + 'data', + $c + ); + } + + public function testRemove() + { + $c = new \Slim\Http\Cookies(); + $c->remove('foo'); + $prop = new \ReflectionProperty($c, 'data'); + $prop->setAccessible(true); + $cValue = $prop->getValue($c); + $this->assertEquals('', $cValue['foo']['value']); + $this->assertLessThan(time(), $cValue['foo']['expires']); + } +} diff --git a/tests/Http/HeadersTest.php b/tests/Http/HeadersTest.php index 0b06d3ab1..e5fe17fe1 100644 --- a/tests/Http/HeadersTest.php +++ b/tests/Http/HeadersTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -32,112 +32,28 @@ class HeadersTest extends PHPUnit_Framework_TestCase { - /** - * Test constructor without args - */ - public function testConstructorWithoutArgs() + public function testNormalizesKey() { $h = new \Slim\Http\Headers(); - $this->assertEquals(0, count($h)); + $h->set('Http_Content_Type', 'text/html'); + $prop = new \ReflectionProperty($h, 'data'); + $prop->setAccessible(true); + $this->assertArrayHasKey('Content-Type', $prop->getValue($h)); } - /** - * Test constructor with args - */ - public function testConstructorWithArgs() + public function testExtractHeaders() { - $h = new \Slim\Http\Headers(array('Content-Type' => 'text/html')); - $this->assertEquals(1, count($h)); - } - - /** - * Test get and set header - */ - public function testSetAndGetHeader() - { - $h = new \Slim\Http\Headers(); - $h['Content-Type'] = 'text/html'; - $this->assertEquals('text/html', $h['Content-Type']); - $this->assertEquals('text/html', $h['Content-type']); - $this->assertEquals('text/html', $h['content-type']); - } - - /** - * Test get non-existent header - */ - public function testGetNonExistentHeader() - { - $h = new \Slim\Http\Headers(); - $this->assertNull($h['foo']); - } - - /** - * Test isset header - */ - public function testHeaderIsSet() - { - $h = new \Slim\Http\Headers(); - $h['Content-Type'] = 'text/html'; - $this->assertTrue(isset($h['Content-Type'])); - $this->assertTrue(isset($h['Content-type'])); - $this->assertTrue(isset($h['content-type'])); - $this->assertFalse(isset($h['foo'])); - } - - /** - * Test unset header - */ - public function testUnsetHeader() - { - $h = new \Slim\Http\Headers(); - $h['Content-Type'] = 'text/html'; - $this->assertEquals(1, count($h)); - unset($h['Content-Type']); - $this->assertEquals(0, count($h)); - } - - /** - * Test merge headers - */ - public function testMergeHeaders() - { - $h = new \Slim\Http\Headers(); - $h['Content-Type'] = 'text/html'; - $this->assertEquals(1, count($h)); - $h->merge(array('Content-type' => 'text/csv', 'content-length' => 10)); - $this->assertEquals(2, count($h)); - $this->assertEquals('text/csv', $h['content-type']); - $this->assertEquals(10, $h['Content-length']); - } - - /** - * Test iteration - */ - public function testIteration() - { - $h = new \Slim\Http\Headers(); - $h['One'] = 'Foo'; - $h['Two'] = 'Bar'; - $output = ''; - foreach ($h as $key => $value) { - $output .= $key . $value; - } - $this->assertEquals('OneFooTwoBar', $output); - } - - /** - * Test outputs header name in original form, not canonical form - */ - public function testOutputsOriginalNotCanonicalName() - { - $h = new \Slim\Http\Headers(); - $h['X-Powered-By'] = 'Slim'; - $h['Content-Type'] = 'text/csv'; - $keys = array(); - foreach ($h as $name => $value) { - $keys[] = $name; - } - $this->assertContains('X-Powered-By', $keys); - $this->assertContains('Content-Type', $keys); + $test = array( + 'HTTP_HOST' => 'foo.com', + 'SERVER_NAME' => 'foo', + 'CONTENT_TYPE' => 'text/html', + 'X_FORWARDED_FOR' => '127.0.0.1' + ); + $results = \Slim\Http\Headers::extract($test); + $this->assertEquals(array( + 'HTTP_HOST' => 'foo.com', + 'CONTENT_TYPE' => 'text/html', + 'X_FORWARDED_FOR' => '127.0.0.1' + ), $results); } } diff --git a/tests/Http/RequestTest.php b/tests/Http/RequestTest.php index b2d3def74..7a837a7d0 100644 --- a/tests/Http/RequestTest.php +++ b/tests/Http/RequestTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -54,11 +54,6 @@ public function testIsGet() )); $req = new \Slim\Http\Request($env); $this->assertTrue($req->isGet()); - $this->assertFalse($req->isPost()); - $this->assertFalse($req->isPut()); - $this->assertFalse($req->isDelete()); - $this->assertFalse($req->isOptions()); - $this->assertFalse($req->isHead()); } /** @@ -70,12 +65,7 @@ public function testIsPost() 'REQUEST_METHOD' => 'POST', )); $req = new \Slim\Http\Request($env); - $this->assertFalse($req->isGet()); $this->assertTrue($req->isPost()); - $this->assertFalse($req->isPut()); - $this->assertFalse($req->isDelete()); - $this->assertFalse($req->isOptions()); - $this->assertFalse($req->isHead()); } /** @@ -87,12 +77,7 @@ public function testIsPut() 'REQUEST_METHOD' => 'PUT', )); $req = new \Slim\Http\Request($env); - $this->assertFalse($req->isGet()); - $this->assertFalse($req->isPost()); $this->assertTrue($req->isPut()); - $this->assertFalse($req->isDelete()); - $this->assertFalse($req->isOptions()); - $this->assertFalse($req->isHead()); } /** @@ -104,12 +89,7 @@ public function testIsDelete() 'REQUEST_METHOD' => 'DELETE', )); $req = new \Slim\Http\Request($env); - $this->assertFalse($req->isGet()); - $this->assertFalse($req->isPost()); - $this->assertFalse($req->isPut()); $this->assertTrue($req->isDelete()); - $this->assertFalse($req->isOptions()); - $this->assertFalse($req->isHead()); } /** @@ -121,12 +101,7 @@ public function testIsOptions() 'REQUEST_METHOD' => 'OPTIONS', )); $req = new \Slim\Http\Request($env); - $this->assertFalse($req->isGet()); - $this->assertFalse($req->isPost()); - $this->assertFalse($req->isPut()); - $this->assertFalse($req->isDelete()); $this->assertTrue($req->isOptions()); - $this->assertFalse($req->isHead()); } /** @@ -138,14 +113,21 @@ public function testIsHead() 'REQUEST_METHOD' => 'HEAD', )); $req = new \Slim\Http\Request($env); - $this->assertFalse($req->isGet()); - $this->assertFalse($req->isPost()); - $this->assertFalse($req->isPut()); - $this->assertFalse($req->isDelete()); - $this->assertFalse($req->isOptions()); $this->assertTrue($req->isHead()); } + /** + * Test HTTP PATCH method detection + */ + public function testIsPatch() + { + $env = \Slim\Environment::mock(array( + 'REQUEST_METHOD' => 'PATCH', + )); + $req = new \Slim\Http\Request($env); + $this->assertTrue($req->isPatch()); + } + /** * Test AJAX method detection w/ header */ @@ -165,7 +147,7 @@ public function testIsAjaxWithHeader() public function testIsAjaxWithQueryParameter() { $env = \Slim\Environment::mock(array( - 'QUERY_STRING' => 'one=1&two=2&three=3&isajax=1', + 'QUERY_STRING' => 'isajax=1', )); $req = new \Slim\Http\Request($env); $this->assertTrue($req->isAjax()); @@ -173,7 +155,7 @@ public function testIsAjaxWithQueryParameter() } /** - * Test AJAX method detection wihtout header or query paramter + * Test AJAX method detection without header or query parameter */ public function testIsAjaxWithoutHeaderOrQueryParameter() { @@ -339,6 +321,24 @@ public function testPut() $this->assertNull($req->put('xyz')); } + /** + * Test fetch PATCH params + */ + public function testPatch() + { + $env = \Slim\Environment::mock(array( + 'REQUEST_METHOD' => 'PATCH', + 'slim.input' => 'foo=bar&abc=123', + 'CONTENT_TYPE' => 'application/x-www-form-urlencoded', + 'CONTENT_LENGTH' => 15 + )); + $req = new \Slim\Http\Request($env); + $this->assertEquals(2, count($req->patch())); + $this->assertEquals('bar', $req->patch('foo')); + $this->assertEquals('bar', $req->params('foo')); + $this->assertNull($req->patch('xyz')); + } + /** * Test fetch DELETE params */ @@ -363,7 +363,7 @@ public function testDelete() public function testCookies() { $env = \Slim\Environment::mock(array( - 'COOKIE' => 'foo=bar; abc=123' + 'HTTP_COOKIE' => 'foo=bar; abc=123' )); $req = new \Slim\Http\Request($env); $this->assertEquals(2, count($req->cookies())); @@ -431,12 +431,11 @@ public function testIsNotFormData() public function testHeaders() { $env = \Slim\Environment::mock(array( - 'ACCEPT_ENCODING' => 'gzip' + 'HTTP_ACCEPT_ENCODING' => 'gzip' )); $req = new \Slim\Http\Request($env); $headers = $req->headers(); - $this->assertTrue(is_array($headers)); - $this->assertArrayHasKey('ACCEPT_ENCODING', $headers); + $this->assertInstanceOf('\Slim\Http\Headers', $headers); $this->assertEquals('gzip', $req->headers('HTTP_ACCEPT_ENCODING')); $this->assertEquals('gzip', $req->headers('HTTP-ACCEPT-ENCODING')); $this->assertEquals('gzip', $req->headers('http_accept_encoding')); @@ -632,7 +631,7 @@ public function testGetHost() { $env = \Slim\Environment::mock(array( 'SERVER_NAME' => 'slim', - 'HOST' => 'slimframework.com' + 'HTTP_HOST' => 'slimframework.com' )); $req = new \Slim\Http\Request($env); $this->assertEquals('slimframework.com', $req->getHost()); //Uses HTTP_HOST if available @@ -645,7 +644,7 @@ public function testGetHostAndStripPort() { $env = \Slim\Environment::mock(array( 'SERVER_NAME' => 'slim', - 'HOST' => 'slimframework.com:80' + 'HTTP_HOST' => 'slimframework.com:80' )); $req = new \Slim\Http\Request($env); $this->assertEquals('slimframework.com', $req->getHost()); //Uses HTTP_HOST if available @@ -658,9 +657,9 @@ public function testGetHostWhenNotExists() { $env = \Slim\Environment::mock(array( 'SERVER_NAME' => 'slim', - 'HOST' => 'slimframework.com' + 'HTTP_HOST' => 'slimframework.com' )); - unset($env['HOST']); + unset($env['HTTP_HOST']); $req = new \Slim\Http\Request($env); $this->assertEquals('slim', $req->getHost()); //Uses SERVER_NAME as backup } @@ -671,7 +670,7 @@ public function testGetHostWhenNotExists() public function testGetHostWithPort() { $env = \Slim\Environment::mock(array( - 'HOST' => 'slimframework.com', + 'HTTP_HOST' => 'slimframework.com', 'SERVER_NAME' => 'slim', 'SERVER_PORT' => 80, 'slim.url_scheme' => 'http' @@ -681,12 +680,12 @@ public function testGetHostWithPort() } /** - * Test get host with port doesn't dulplicate port numbers + * Test get host with port doesn't duplicate port numbers */ public function testGetHostDoesntDulplicatePort() { $env = \Slim\Environment::mock(array( - 'HOST' => 'slimframework.com:80', + 'HTTP_HOST' => 'slimframework.com:80', 'SERVER_NAME' => 'slim', 'SERVER_PORT' => 80, 'slim.url_scheme' => 'http' @@ -806,7 +805,7 @@ public function testAppPathsInRootDirectoryWithHtaccess() public function testGetUrl() { $env = \Slim\Environment::mock(array( - 'HOST' => 'slimframework.com', + 'HTTP_HOST' => 'slimframework.com', 'SERVER_NAME' => 'slim', 'SERVER_PORT' => 80, 'slim.url_scheme' => 'http' @@ -821,7 +820,7 @@ public function testGetUrl() public function testGetUrlWithCustomPort() { $env = \Slim\Environment::mock(array( - 'HOST' => 'slimframework.com', + 'HTTP_HOST' => 'slimframework.com', 'SERVER_NAME' => 'slim', 'SERVER_PORT' => 8080, 'slim.url_scheme' => 'http' @@ -836,7 +835,7 @@ public function testGetUrlWithCustomPort() public function testGetUrlWithHttps() { $env = \Slim\Environment::mock(array( - 'HOST' => 'slimframework.com', + 'HTTP_HOST' => 'slimframework.com', 'SERVER_NAME' => 'slim', 'SERVER_PORT' => 443, 'slim.url_scheme' => 'https' @@ -890,7 +889,7 @@ public function testGetIpWithForwardedFor() public function testGetReferrer() { $env = \Slim\Environment::mock(array( - 'REFERER' => 'http://foo.com' + 'HTTP_REFERER' => 'http://foo.com' )); $req = new \Slim\Http\Request($env); $this->assertEquals('http://foo.com', $req->getReferrer()); @@ -914,7 +913,7 @@ public function testGetReferrerWhenNotExists() public function testGetUserAgent() { $env = \Slim\Environment::mock(array( - 'USER_AGENT' => 'user-agent-string' + 'HTTP_USER_AGENT' => 'user-agent-string' )); $req = new \Slim\Http\Request($env); $this->assertEquals('user-agent-string', $req->getUserAgent()); @@ -926,7 +925,7 @@ public function testGetUserAgent() public function testGetUserAgentWhenNotExists() { $env = \Slim\Environment::mock(); - unset($env['USER_AGENT']); + unset($env['HTTP_USER_AGENT']); $req = new \Slim\Http\Request($env); $this->assertNull($req->getUserAgent()); } diff --git a/tests/Http/ResponseTest.php b/tests/Http/ResponseTest.php index 2321e613c..85b857f35 100644 --- a/tests/Http/ResponseTest.php +++ b/tests/Http/ResponseTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -32,541 +32,238 @@ class ResponseTest extends PHPUnit_Framework_TestCase { - /** - * Test constructor without args - */ - public function testConstructorWithoutArgs() - { - $r = new \Slim\Http\Response(); - $this->assertEquals('', $r->body()); - $this->assertEquals(200, $r->status()); - $this->assertEquals(0, $r->length()); - $this->assertEquals('text/html', $r['Content-Type']); - } - - /** - * Test constructor with args - */ - public function testConstructorWithArgs() - { - $r = new \Slim\Http\Response('Page Not Found', 404, array('Content-Type' => 'application/json', 'X-Created-By' => 'Slim')); - $this->assertEquals('Page Not Found', $r->body()); - $this->assertEquals(404, $r->status()); - $this->assertEquals(14, $r->length()); - $this->assertEquals('application/json', $r['Content-Type']); - $this->assertEquals('Slim', $r['X-Created-By']); - } - - /** - * Test get status - */ - public function testGetStatus() + public function testConstructWithoutArgs() { - $r = new \Slim\Http\Response(); - $this->assertEquals(200, $r->status()); - } + $res = new \Slim\Http\Response(); - /** - * Test set status - */ - public function testSetStatus() - { - $r = new \Slim\Http\Response(); - $r->status(500); - $this->assertEquals(500, $r->status()); + $this->assertAttributeEquals(200, 'status', $res); + $this->assertAttributeEquals('', 'body', $res); } - /** - * Test get headers - */ - public function testGetHeaders() + public function testConstructWithArgs() { - $r = new \Slim\Http\Response(); - $headers = $r->headers(); - $this->assertEquals(1, count($headers)); - $this->assertEquals('text/html', $headers['Content-Type']); - } + $res = new \Slim\Http\Response('Foo', 201); - /** - * Test get and set header (without Array Access) - */ - public function testGetAndSetHeader() - { - $r = new \Slim\Http\Response(); - $r->header('X-Foo', 'Bar'); - $this->assertEquals('Bar', $r->header('X-Foo')); + $this->assertAttributeEquals(201, 'status', $res); + $this->assertAttributeEquals('Foo', 'body', $res); } - /** - * Test get body - */ - public function testGetBody() + public function testGetStatus() { - $r = new \Slim\Http\Response('Foo'); - $this->assertEquals('Foo', $r->body()); - } + $res = new \Slim\Http\Response(); - /** - * Test set body - */ - public function testSetBody() - { - $r = new \Slim\Http\Response(); - $r->body('Foo'); - $this->assertEquals('Foo', $r->body()); + $this->assertEquals(200, $res->getStatus()); } - /** - * Test get length - */ - public function testGetLength() + public function testSetStatus() { - $r = new \Slim\Http\Response('Foo'); - $this->assertEquals(3, $r->length()); - } + $res = new \Slim\Http\Response(); + $res->setStatus(301); - /** - * Test set length - */ - public function testSetLength() - { - $r = new \Slim\Http\Response(); - $r->length(3); - $this->assertEquals(3, $r->length()); + $this->assertAttributeEquals(301, 'status', $res); } /** - * Test write for appending + * DEPRECATION WARNING! */ - public function testWriteAppend() + public function testStatusGetOld() { - $r = new \Slim\Http\Response('Foo'); - $r->write('Bar'); - $this->assertEquals('FooBar', $r->body()); + $res = new \Slim\Http\Response('', 201); + $this->assertEquals(201, $res->status()); } /** - * Test write for replacing + * DEPRECATION WARNING! */ - public function testWriteReplace() + public function testStatusSetOld() { - $r = new \Slim\Http\Response('Foo'); - $r->write('Bar', true); - $this->assertEquals('Bar', $r->body()); - } + $res = new \Slim\Http\Response(); + $prop = new \ReflectionProperty($res, 'status'); + $prop->setAccessible(true); + $res->status(301); - /** - * Test finalize - */ - public function testFinalize() - { - $r = new \Slim\Http\Response(); - $r->status(404); - $r['Content-Type'] = 'application/json'; - $r->write('Foo'); - $result = $r->finalize(); - $this->assertEquals(3, count($result)); - $this->assertEquals(404, $result[0]); - $this->assertFalse(is_null($result[1]['Content-Type'])); + $this->assertEquals(301, $prop->getValue($res)); } - /** - * Test finalize - */ - public function testFinalizeWithoutBody() + public function testGetBody() { - $r = new \Slim\Http\Response(); - $r->status(204); - $r['Content-Type'] = 'application/json'; - $r->write('Foo'); - $result = $r->finalize(); - $this->assertEquals(3, count($result)); - $this->assertEquals('', $result[2]); - } + $res = new \Slim\Http\Response(); + $property = new \ReflectionProperty($res, 'body'); + $property->setAccessible(true); + $property->setValue($res, 'foo'); - /** - * Test set cookie with only name and value - */ - public function testSetCookieWithNameAndValue() - { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', 'bar'); - $this->assertEquals('foo=bar', $r['Set-Cookie']); + $this->assertEquals('foo', $res->getBody()); } - /** - * Test set multiple cookies with only name and value - */ - public function testSetMultipleCookiesWithNameAndValue() + public function testSetBody() { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', 'bar'); - $r->setCookie('abc', '123'); - $this->assertEquals("foo=bar\nabc=123", $r['Set-Cookie']); - } + $res = new \Slim\Http\Response('bar'); + $res->setBody('foo'); // <-- Should replace body - /** - * Test set cookie only name and value and expires (as int) - */ - public function testSetMultipleCookiesWithNameAndValueAndExpiresAsInt() - { - $now = time(); - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'expires' => $now - )); - $this->assertEquals("foo=bar; expires=" . gmdate('D, d-M-Y H:i:s e', $now), $r['Set-Cookie']); + $this->assertAttributeEquals('foo', 'body', $res); } - /** - * Test set cookie with only name and value and expires (as string) - */ - public function testSetMultipleCookiesWithNameAndValueAndExpiresAsString() + public function testWrite() { - $expires = 'next Tuesday'; - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'expires' => $expires - )); - $this->assertEquals("foo=bar; expires=" . gmdate('D, d-M-Y H:i:s e', strtotime($expires)), $r['Set-Cookie']); - } + $res = new \Slim\Http\Response(); + $property = new \ReflectionProperty($res, 'body'); + $property->setAccessible(true); + $property->setValue($res, 'foo'); + $res->write('bar'); // <-- Should append to body - /** - * Test set cookie with name, value, domain - */ - public function testSetCookieWithNameAndValueAndDomain() - { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'domain' => '.slimframework.com' - )); - $this->assertEquals('foo=bar; domain=.slimframework.com', $r['Set-Cookie']); + $this->assertAttributeEquals('foobar', 'body', $res); } - /** - * Test set cookie with name, value, domain, path - */ - public function testSetCookieWithNameAndValueAndDomainAndPath() + public function testLength() { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'domain' => '.slimframework.com', - 'path' => '/foo' - )); - $this->assertEquals($r['Set-Cookie'], 'foo=bar; domain=.slimframework.com; path=/foo'); - } + $res = new \Slim\Http\Response('foo'); // <-- Sets body and length - /** - * Test set cookie with only name and value and secure flag - */ - public function testSetCookieWithNameAndValueAndSecureFlag() - { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'secure' => true - )); - $this->assertEquals('foo=bar; secure', $r['Set-Cookie']); + $this->assertEquals(3, $res->getLength()); } - /** - * Test set cookie with only name and value and secure flag (as non-truthy) - */ - public function testSetCookieWithNameAndValueAndSecureFlagAsNonTruthy() - { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'secure' => 0 - )); - $this->assertEquals('foo=bar', $r['Set-Cookie']); - } - - /** - * Test set cookie with only name and value and httponly flag - */ - public function testSetCookieWithNameAndValueAndHttpOnlyFlag() - { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'httponly' => true - )); - $this->assertEquals('foo=bar; HttpOnly', $r['Set-Cookie']); - } - - /** - * Test set cookie with only name and value and httponly flag (as non-truthy) - */ - public function testSetCookieWithNameAndValueAndHttpOnlyFlagAsNonTruthy() - { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'httponly' => 0 - )); - $this->assertEquals('foo=bar', $r['Set-Cookie']); - } - - /* - * Test delete cookie by name - */ - public function testDeleteCookieByName() + public function testFinalize() { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', 'bar'); - $r->setCookie('abc', '123'); - $r->deleteCookie('foo'); - $this->assertEquals(1, preg_match("@abc=123\nfoo=; expires=@", $r['Set-Cookie'])); - } + $res = new \Slim\Http\Response(); + $resFinal = $res->finalize(); - /* - * Test delete cookie by name and domain - */ - public function testDeleteCookieByNameAndDomain1() - { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', 'bar'); //Note: This does not have domain associated with it - $r->setCookie('abc', '123'); - $r->deleteCookie('foo', array('domain' => '.slimframework.com')); //This SHOULD NOT remove the `foo` cookie - $this->assertEquals(1, preg_match("@foo=bar\nabc=123\nfoo=; domain=.slimframework.com; expires=@", $r['Set-Cookie'])); + $this->assertTrue(is_array($resFinal)); + $this->assertEquals(3, count($resFinal)); + $this->assertEquals(200, $resFinal[0]); + $this->assertInstanceOf('\Slim\Http\Headers', $resFinal[1]); + $this->assertEquals('', $resFinal[2]); } - /* - * Test delete cookie by name and domain - */ - public function testDeleteCookieByNameAndDomain2() + public function testFinalizeWithEmptyBody() { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', array( - 'value' => 'bar', - 'domain' => '.slimframework.com' //Note: This does have domain associated with it - )); - $r->setCookie('abc', '123'); - $r->deleteCookie('foo', array('domain' => '.slimframework.com')); //This SHOULD remove the `foo` cookie - $this->assertEquals(1, preg_match("@abc=123\nfoo=; domain=.slimframework.com; expires=@", $r['Set-Cookie'])); - } + $res = new \Slim\Http\Response('Foo', 304); + $resFinal = $res->finalize(); - /** - * Test delete cookie by name and custom props - */ - public function testDeleteCookieByNameAndCustomProps() - { - $r = new \Slim\Http\Response(); - $r->setCookie('foo', 'bar'); - $r->setCookie('abc', '123'); - $r->deleteCookie('foo', array( - 'secure' => true, - 'httponly' => true - )); - $this->assertEquals(1, preg_match("@abc=123\nfoo=; expires=.*; secure; HttpOnly@", $r['Set-Cookie'])); + $this->assertEquals('', $resFinal[2]); } - /** - * Test redirect - */ public function testRedirect() { - $r = new \Slim\Http\Response(); - $r->redirect('/foo'); - $this->assertEquals(302, $r->status()); - $this->assertEquals('/foo', $r['Location']); - } + $res = new \Slim\Http\Response(); + $res->redirect('/foo'); - /** - * Test redirect with custom status - */ - public function testRedirectWithCustomStatus() - { - $r = new \Slim\Http\Response(); - $r->redirect('/foo', 307); - $this->assertEquals(307, $r->status()); - $this->assertEquals('/foo', $r['Location']); + $pStatus = new \ReflectionProperty($res, 'status'); + $pStatus->setAccessible(true); + + $this->assertEquals(302, $pStatus->getValue($res)); + $this->assertEquals('/foo', $res->headers['Location']); } - /** - * Test isEmpty - */ public function testIsEmpty() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); - $r1->status(404); - $r2->status(201); + $r1->setStatus(404); + $r2->setStatus(201); $this->assertFalse($r1->isEmpty()); $this->assertTrue($r2->isEmpty()); } - /** - * Test isClientError - */ public function testIsClientError() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); - $r1->status(404); - $r2->status(500); + $r1->setStatus(404); + $r2->setStatus(500); $this->assertTrue($r1->isClientError()); $this->assertFalse($r2->isClientError()); } - /** - * Test isForbidden - */ public function testIsForbidden() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); - $r1->status(403); - $r2->status(500); + $r1->setStatus(403); + $r2->setStatus(500); $this->assertTrue($r1->isForbidden()); $this->assertFalse($r2->isForbidden()); } - /** - * Test isInformational - */ public function testIsInformational() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); - $r1->status(100); - $r2->status(200); + $r1->setStatus(100); + $r2->setStatus(200); $this->assertTrue($r1->isInformational()); $this->assertFalse($r2->isInformational()); } - /** - * Test isInformational - */ public function testIsNotFound() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); - $r1->status(404); - $r2->status(200); + $r1->setStatus(404); + $r2->setStatus(200); $this->assertTrue($r1->isNotFound()); $this->assertFalse($r2->isNotFound()); } - /** - * Test isOk - */ public function testIsOk() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); - $r1->status(200); - $r2->status(201); + $r1->setStatus(200); + $r2->setStatus(201); $this->assertTrue($r1->isOk()); $this->assertFalse($r2->isOk()); } - /** - * Test isSuccessful - */ public function testIsSuccessful() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); $r3 = new \Slim\Http\Response(); - $r1->status(200); - $r2->status(201); - $r3->status(302); + $r1->setStatus(200); + $r2->setStatus(201); + $r3->setStatus(302); $this->assertTrue($r1->isSuccessful()); $this->assertTrue($r2->isSuccessful()); $this->assertFalse($r3->isSuccessful()); } - /** - * Test isRedirect - */ public function testIsRedirect() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); - $r1->status(307); - $r2->status(304); + $r1->setStatus(307); + $r2->setStatus(304); $this->assertTrue($r1->isRedirect()); $this->assertFalse($r2->isRedirect()); } - /** - * Test isRedirection - */ public function testIsRedirection() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); $r3 = new \Slim\Http\Response(); - $r1->status(307); - $r2->status(304); - $r3->status(200); + $r1->setStatus(307); + $r2->setStatus(304); + $r3->setStatus(200); $this->assertTrue($r1->isRedirection()); $this->assertTrue($r2->isRedirection()); $this->assertFalse($r3->isRedirection()); } - /** - * Test isServerError - */ public function testIsServerError() { $r1 = new \Slim\Http\Response(); $r2 = new \Slim\Http\Response(); - $r1->status(500); - $r2->status(400); + $r1->setStatus(500); + $r2->setStatus(400); $this->assertTrue($r1->isServerError()); $this->assertFalse($r2->isServerError()); } - /** - * Test offset exists and offset get - */ - public function testOffsetExistsAndGet() - { - $r = new \Slim\Http\Response(); - $this->assertFalse(empty($r['Content-Type'])); - $this->assertNull($r['foo']); - } - - /** - * Test iteration - */ - public function testIteration() - { - $h = new \Slim\Http\Response(); - $output = ''; - foreach ($h as $key => $value) { - $output .= $key . $value; - } - $this->assertEquals('Content-Typetext/html', $output); - } - - /** - * Test countable - */ - public function testCountable() - { - $r1 = new \Slim\Http\Response(); - $this->assertEquals(1, count($r1)); //Content-Type - } - - /** - * Test message for code when message exists - */ public function testMessageForCode() { $this->assertEquals('200 OK', \Slim\Http\Response::getMessageForCode(200)); } - /** - * Test message for code when message exists - */ public function testMessageForCodeWithInvalidCode() { $this->assertNull(\Slim\Http\Response::getMessageForCode(600)); diff --git a/tests/Http/UtilTest.php b/tests/Http/UtilTest.php index 4eb0b4d30..9d665da0e 100644 --- a/tests/Http/UtilTest.php +++ b/tests/Http/UtilTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * diff --git a/tests/LogTest.php b/tests/LogTest.php index d390e45c4..d56299f1f 100644 --- a/tests/LogTest.php +++ b/tests/LogTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -96,6 +96,21 @@ public function testLogInfo() } public function testLogInfoExcludedByLevel() + { + $log = new \Slim\Log(new MyWriter()); + $log->setLevel(\Slim\Log::NOTICE); + $this->assertFalse($log->info('Info')); + } + + public function testLogNotice() + { + $this->expectOutputString('Notice'); + $log = new \Slim\Log(new MyWriter()); + $result = $log->notice('Notice'); + $this->assertTrue($result); + } + + public function testLogNoticeExcludedByLevel() { $log = new \Slim\Log(new MyWriter()); $log->setLevel(\Slim\Log::WARN); @@ -106,7 +121,7 @@ public function testLogWarn() { $this->expectOutputString('Warn'); $log = new \Slim\Log(new MyWriter()); - $result = $log->warn('Warn'); + $result = $log->warning('Warn'); $this->assertTrue($result); } @@ -114,7 +129,7 @@ public function testLogWarnExcludedByLevel() { $log = new \Slim\Log(new MyWriter()); $log->setLevel(\Slim\Log::ERROR); - $this->assertFalse($log->warn('Warn')); + $this->assertFalse($log->warning('Warn')); } public function testLogError() @@ -128,15 +143,56 @@ public function testLogError() public function testLogErrorExcludedByLevel() { $log = new \Slim\Log(new MyWriter()); - $log->setLevel(\Slim\Log::FATAL); + $log->setLevel(\Slim\Log::CRITICAL); $this->assertFalse($log->error('Error')); } - public function testLogFatal() + public function testLogCritical() + { + $this->expectOutputString('Critical'); + $log = new \Slim\Log(new MyWriter()); + $result = $log->critical('Critical'); + $this->assertTrue($result); + } + + public function testLogCriticalExcludedByLevel() + { + $log = new \Slim\Log(new MyWriter()); + $log->setLevel(\Slim\Log::ALERT); + $this->assertFalse($log->critical('Critical')); + } + + public function testLogAlert() + { + $this->expectOutputString('Alert'); + $log = new \Slim\Log(new MyWriter()); + $result = $log->alert('Alert'); + $this->assertTrue($result); + } + + public function testLogAlertExcludedByLevel() + { + $log = new \Slim\Log(new MyWriter()); + $log->setLevel(\Slim\Log::EMERGENCY); + $this->assertFalse($log->alert('Alert')); + } + + public function testLogEmergency() + { + $this->expectOutputString('Emergency'); + $log = new \Slim\Log(new MyWriter()); + $result = $log->emergency('Emergency'); + $this->assertTrue($result); + } + + public function testInterpolateMessage() { - $this->expectOutputString('Fatal'); + $this->expectOutputString('Hello Slim !'); $log = new \Slim\Log(new MyWriter()); - $result = $log->fatal('Fatal'); + $result = $log->debug( + 'Hello {framework} !', + array('framework' => "Slim") + ); $this->assertTrue($result); } diff --git a/tests/LogWriterTest.php b/tests/LogWriterTest.php index 3c30d8039..839f09f6e 100644 --- a/tests/LogWriterTest.php +++ b/tests/LogWriterTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * diff --git a/tests/Middleware/ContentTypesTest.php b/tests/Middleware/ContentTypesTest.php index 3857b3428..6f9fcfe87 100644 --- a/tests/Middleware/ContentTypesTest.php +++ b/tests/Middleware/ContentTypesTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * diff --git a/tests/Middleware/FlashTest.php b/tests/Middleware/FlashTest.php index 09da70a8a..7b8f7961d 100644 --- a/tests/Middleware/FlashTest.php +++ b/tests/Middleware/FlashTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -86,7 +86,7 @@ public function testKeepFlashFromPreviousRequest() } /** - * Test flash messages from preivous request do not persist to next request + * Test flash messages from previous request do not persist to next request */ public function testFlashMessagesFromPreviousRequestDoNotPersist() { diff --git a/tests/Middleware/MethodOverrideTest.php b/tests/Middleware/MethodOverrideTest.php index 55484883c..c2c5857f3 100644 --- a/tests/Middleware/MethodOverrideTest.php +++ b/tests/Middleware/MethodOverrideTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -98,7 +98,7 @@ public function testDoesNotOverrideMethodIfNotPost() } /** - * Test does not override method if no method ovveride parameter + * Test does not override method if no method override parameter */ public function testDoesNotOverrideMethodAsPostWithoutParameter() { diff --git a/tests/Middleware/PrettyExceptionsTest.php b/tests/Middleware/PrettyExceptionsTest.php index 4f41b2d52..28dabcc2f 100644 --- a/tests/Middleware/PrettyExceptionsTest.php +++ b/tests/Middleware/PrettyExceptionsTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * diff --git a/tests/Middleware/SessionCookieTest.php b/tests/Middleware/SessionCookieTest.php index 8ef12ef0c..472c01c01 100644 --- a/tests/Middleware/SessionCookieTest.php +++ b/tests/Middleware/SessionCookieTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -48,7 +48,7 @@ public function setUp() * 1) That the HTTP cookie is added to the `Set-Cookie:` response header; * 2) That the HTTP cookie is constructed in the expected format; */ - public function testSessionCookieIsCreatedAndEncrypted() + public function testSessionCookieIsCreated() { \Slim\Environment::mock(array( 'SCRIPT_NAME' => '/index.php', @@ -63,9 +63,7 @@ public function testSessionCookieIsCreatedAndEncrypted() $mw->setNextMiddleware($app); $mw->call(); list($status, $header, $body) = $app->response()->finalize(); - $matches = array(); - preg_match_all('@^slim_session=.+|.+|.+; expires=@', $header['Set-Cookie'], $matches, PREG_SET_ORDER); - $this->assertEquals(1, count($matches)); + $this->assertTrue($app->response->cookies->has('slim_session')); } /** @@ -80,7 +78,7 @@ public function testSessionIsPopulatedFromCookie() \Slim\Environment::mock(array( 'SCRIPT_NAME' => '/index.php', 'PATH_INFO' => '/foo', - 'COOKIE' => 'slim_session=1644004961%7CLKkYPwqKIMvBK7MWl6D%2BxeuhLuMaW4quN%2F512ZAaVIY%3D%7Ce0f007fa852c7101e8224bb529e26be4d0dfbd63', + 'HTTP_COOKIE' => 'slim_session=1644004961%7CLKkYPwqKIMvBK7MWl6D%2BxeuhLuMaW4quN%2F512ZAaVIY%3D%7Ce0f007fa852c7101e8224bb529e26be4d0dfbd63', )); $app = new \Slim\Slim(); $app->get('/foo', function () { diff --git a/tests/MiddlewareTest.php b/tests/MiddlewareTest.php index db78918c3..a95036a12 100644 --- a/tests/MiddlewareTest.php +++ b/tests/MiddlewareTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -30,57 +30,50 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -class My_Middleware extends \Slim\Middleware +class MyMiddleware extends \Slim\Middleware { - public function call() - { - echo "Before"; - $this->next->call(); - echo "After"; - } + public function call() {} } -class My_Application +class MiddlewareTest extends PHPUnit_Framework_TestCase { - public function call() + public function testSetApplication() { - echo "Application"; + $app = new stdClass(); + $mw = new MyMiddleware(); + $mw->setApplication($app); + + $this->assertAttributeSame($app, 'app', $mw); } -} -class MiddlewareTest extends PHPUnit_Framework_TestCase -{ - /** - * Get and set application - */ - public function testGetAndSetApplication() + public function testGetApplication() { - $app = new My_Application(); - $mw = new My_Middleware(); - $mw->setApplication($app); + $app = new stdClass(); + $mw = new MyMiddleware(); + $property = new \ReflectionProperty($mw, 'app'); + $property->setAccessible(true); + $property->setValue($mw, $app); + $this->assertSame($app, $mw->getApplication()); } - /** - * Get and set next middleware - */ - public function testGetAndSetNextMiddleware() + public function testSetNextMiddleware() { - $mw1 = new My_Middleware(); - $mw2 = new My_Middleware(); + $mw1 = new MyMiddleware(); + $mw2 = new MyMiddleware(); $mw1->setNextMiddleware($mw2); - $this->assertSame($mw2, $mw1->getNextMiddleware()); + + $this->assertAttributeSame($mw2, 'next', $mw1); } - /** - * Test call - */ - public function testCall() + public function testGetNextMiddleware() { - $this->expectOutputString('BeforeApplicationAfter'); - $app = new My_Application(); - $mw = new My_Middleware(); - $mw->setNextMiddleware($app); - $mw->call(); + $mw1 = new MyMiddleware(); + $mw2 = new MyMiddleware(); + $property = new \ReflectionProperty($mw1, 'next'); + $property->setAccessible(true); + $property->setValue($mw1, $mw2); + + $this->assertSame($mw2, $mw1->getNextMiddleware()); } } diff --git a/tests/RouteTest.php b/tests/RouteTest.php index decec7d4f..dec3f9efa 100644 --- a/tests/RouteTest.php +++ b/tests/RouteTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -29,292 +29,185 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - -// Used for passing callable via string -function testCallable() {} - class RouteTest extends PHPUnit_Framework_TestCase { - /** - * Route should set name - */ - public function testRouteSetsName() + public function testGetPattern() { - $route = new \Slim\Route('/foo/bar', function () {}); - $route->name('foo'); - $this->assertEquals('foo', $route->getName()); - } + $route = new \Slim\Route('/foo', function () {}); - /** - * Route should set pattern - */ - public function testRouteSetsPattern() - { - $route1 = new \Slim\Route('/foo/bar', function () {}); - $this->assertEquals('/foo/bar', $route1->getPattern()); + $this->assertEquals('/foo', $route->getPattern()); } - /** - * Route sets pattern with params - */ - public function testRouteSetsPatternWithParams() + public function testGetName() { - $route = new \Slim\Route('/hello/:first/:last', function () {}); - $this->assertEquals('/hello/:first/:last', $route->getPattern()); + $route = new \Slim\Route('/foo', function () {}); + + $property = new \ReflectionProperty($route, 'name'); + $property->setAccessible(true); + $property->setValue($route, 'foo'); + + $this->assertEquals('foo', $route->getName()); } - /** - * Route sets custom pattern that overrides pattern - */ - public function testRouteSetsCustomTemplate() + public function testSetName() { - $route = new \Slim\Route('/hello/*', function () {}); - $route->setPattern('/hello/:name'); - $this->assertEquals('/hello/:name', $route->getPattern()); + $route = new \Slim\Route('/foo', function () {}); + $route->name('foo'); // <-- Alias for `setName()` + + $this->assertAttributeEquals('foo', 'name', $route); } - /** - * Route should store a reference to the callable - * anonymous function. - */ - public function testRouteSetsCallableAsFunction() + public function testGetCallable() { - $callable = function () { echo "Foo!"; }; - $route = new \Slim\Route('/foo/bar', $callable); + $callable = function () { + echo 'Foo'; + }; + $route = new \Slim\Route('/foo', $callable); + $this->assertSame($callable, $route->getCallable()); } - /** - * Route should store a reference to the callable - * regular function (for PHP 5 < 5.3) - */ - public function testRouteSetsCallableAsString() + public function testSetCallable() { - $route = new \Slim\Route('/foo/bar', 'testCallable'); - $this->assertEquals('testCallable', $route->getCallable()); - } + $callable = function () { + echo 'Foo'; + }; + $route = new \Slim\Route('/foo', $callable); // <-- Called inside __construct() - /** - * Route should throw exception when creating with an invalid callable - */ - public function testRouteThrowsExecptionWithInvalidCallable() - { - $this->setExpectedException('InvalidArgumentException'); - $route = new \Slim\Route('/foo/bar', 'fnDoesNotExist'); + $this->assertAttributeSame($callable, 'callable', $route); } - /** - * Route should throw exception when setting an invalid callable - */ - public function testRouteThrowsExecptionWhenSettingInvalidCallable() + public function testSetCallableWithInvalidArgument() { - $route = new \Slim\Route('/foo/bar', function () {}); - try - { - $route->setCallable('fnDoesNotExist'); - $this->fail('Did not catch InvalidArgumentException when setting invalid callable'); - } catch(\InvalidArgumentException $e) {} + $this->setExpectedException('\InvalidArgumentException'); + $route = new \Slim\Route('/foo', 'doesNotExist'); // <-- Called inside __construct() } - /** - * Test gets all params - */ - public function testGetRouteParams() + public function testGetParams() { - // Prepare route - $requestUri = '/hello/mr/anderson'; $route = new \Slim\Route('/hello/:first/:last', function () {}); + $route->matches('/hello/mr/anderson'); // <-- Parses params from argument - // Parse route params - $this->assertTrue($route->matches($requestUri)); - - // Get params - $params = $route->getParams(); - $this->assertEquals(2, count($params)); - $this->assertEquals('mr', $params['first']); - $this->assertEquals('anderson', $params['last']); + $this->assertEquals(array( + 'first' => 'mr', + 'last' => 'anderson' + ), $route->getParams()); } - /** - * Test sets all params - */ - public function testSetRouteParams() + public function testSetParams() { - // Prepare route - $requestUri = '/hello/mr/anderson'; $route = new \Slim\Route('/hello/:first/:last', function () {}); - - // Parse route params - $this->assertTrue($route->matches($requestUri)); - - // Get params - $params = $route->getParams(); - $this->assertEquals(2, count($params)); - $this->assertEquals('mr', $params['first']); - $this->assertEquals('anderson', $params['last']); - - // Replace params + $route->matches('/hello/mr/anderson'); // <-- Parses params from argument $route->setParams(array( - 'first' => 'john', + 'first' => 'agent', 'last' => 'smith' )); - // Get new params - $params = $route->getParams(); - $this->assertEquals(2, count($params)); - $this->assertEquals('john', $params['first']); - $this->assertEquals('smith', $params['last']); + $this->assertAttributeEquals(array( + 'first' => 'agent', + 'last' => 'smith' + ), 'params', $route); } - /** - * Test gets param when exists - */ - public function testGetRouteParamWhenExists() + public function testGetParam() { - // Prepare route - $requestUri = '/hello/mr/anderson'; $route = new \Slim\Route('/hello/:first/:last', function () {}); - // Parse route params - $this->assertTrue($route->matches($requestUri)); + $property = new \ReflectionProperty($route, 'params'); + $property->setAccessible(true); + $property->setValue($route, array( + 'first' => 'foo', + 'last' => 'bar' + )); - // Get param - $this->assertEquals('anderson', $route->getParam('last')); + $this->assertEquals('foo', $route->getParam('first')); } - /** - * Test gets param when not exists - */ - public function testGetRouteParamWhenNotExists() + public function testGetParamThatDoesNotExist() { - // Prepare route - $requestUri = '/hello/mr/anderson'; + $this->setExpectedException('InvalidArgumentException'); + $route = new \Slim\Route('/hello/:first/:last', function () {}); - // Parse route params - $this->assertTrue($route->matches($requestUri)); + $property = new \ReflectionProperty($route, 'params'); + $property->setAccessible(true); + $property->setValue($route, array( + 'first' => 'foo', + 'last' => 'bar' + )); - // Get param - try { - $param = $route->getParam('foo'); - $this->fail('Did not catch expected InvalidArgumentException'); - } catch ( \InvalidArgumentException $e ) {} + $route->getParam('middle'); } - /** - * Test sets param when exists - */ - public function testSetRouteParamWhenExists() + public function testSetParam() { - // Prepare route - $requestUri = '/hello/mr/anderson'; $route = new \Slim\Route('/hello/:first/:last', function () {}); - - // Parse route params - $this->assertTrue($route->matches($requestUri)); - - // Get param - $this->assertEquals('anderson', $route->getParam('last')); - - // Set param + $route->matches('/hello/mr/anderson'); // <-- Parses params from argument $route->setParam('last', 'smith'); - // Get new param - $this->assertEquals('smith', $route->getParam('last')); + $this->assertAttributeEquals(array( + 'first' => 'mr', + 'last' => 'smith' + ), 'params', $route); } - /** - * Test sets param when not exists - */ - public function testSetRouteParamWhenNotExists() + public function testSetParamThatDoesNotExist() { - // Prepare route - $requestUri = '/hello/mr/anderson'; - $route = new \Slim\Route('/hello/:first/:last', function () {}); - - // Parse route params - $this->assertTrue($route->matches($requestUri)); + $this->setExpectedException('InvalidArgumentException'); - // Get param - try { - $param = $route->setParam('foo', 'bar'); - $this->fail('Did not catch expected InvalidArgumentException'); - } catch ( \InvalidArgumentException $e ) {} + $route = new \Slim\Route('/hello/:first/:last', function () {}); + $route->matches('/hello/mr/anderson'); // <-- Parses params from argument + $route->setParam('middle', 'smith'); // <-- Should trigger InvalidArgumentException } - /** - * If route matches a resource URI, param should be extracted. - */ - public function testRouteMatchesAndParamExtracted() + public function testMatches() { - $resource = '/hello/Josh'; $route = new \Slim\Route('/hello/:name', function () {}); - $result = $route->matches($resource); - $this->assertTrue($result); - $this->assertEquals(array('name' => 'Josh'), $route->getParams()); + + $this->assertTrue($route->matches('/hello/josh')); } - /** - * If route matches a resource URI, multiple params should be extracted. - */ - public function testRouteMatchesAndMultipleParamsExtracted() + public function testMatchesIsFalse() { - $resource = '/hello/Josh/and/John'; - $route = new \Slim\Route('/hello/:first/and/:second', function () {}); - $result = $route->matches($resource); - $this->assertTrue($result); - $this->assertEquals(array('first' => 'Josh', 'second' => 'John'), $route->getParams()); + $route = new \Slim\Route('/foo', function () {}); + + $this->assertFalse($route->matches('/bar')); } - /** - * If route does not match a resource URI, params remain an empty array - */ - public function testRouteDoesNotMatchAndParamsNotExtracted() + public function testMatchesPatternWithTrailingSlash() { - $resource = '/foo/bar'; - $route = new \Slim\Route('/hello/:name', function () {}); - $result = $route->matches($resource); - $this->assertFalse($result); - $this->assertEquals(array(), $route->getParams()); + $route = new \Slim\Route('/foo/', function () {}); + + $this->assertTrue($route->matches('/foo/')); + $this->assertTrue($route->matches('/foo')); } - /** - * Route matches URI with trailing slash - * - */ - public function testRouteMatchesWithTrailingSlash() + public function testMatchesPatternWithoutTrailingSlash() { - $resource1 = '/foo/bar/'; - $resource2 = '/foo/bar'; - $route = new \Slim\Route('/foo/:one/', function () {}); - $this->assertTrue($route->matches($resource1)); - $this->assertTrue($route->matches($resource2)); + $route = new \Slim\Route('/foo', function () {}); + + $this->assertFalse($route->matches('/foo/')); + $this->assertTrue($route->matches('/foo')); } - /** - * Route matches URI with conditions - */ - public function testRouteMatchesResourceWithConditions() + public function testMatchesWithConditions() { - $resource = '/hello/Josh/and/John'; $route = new \Slim\Route('/hello/:first/and/:second', function () {}); - $route->conditions(array('first' => '[a-zA-Z]{3,}')); - $result = $route->matches($resource); - $this->assertTrue($result); - $this->assertEquals(array('first' => 'Josh', 'second' => 'John'), $route->getParams()); + $route->conditions(array( + 'first' => '[a-zA-Z]{3,}' + )); + + $this->assertTrue($route->matches('/hello/Josh/and/John')); } - /** - * Route does not match URI with conditions - */ - public function testRouteDoesNotMatchResourceWithConditions() + public function testMatchesWithConditionsIsFalse() { - $resource = '/hello/Josh/and/John'; $route = new \Slim\Route('/hello/:first/and/:second', function () {}); - $route->conditions(array('first' => '[a-z]{3,}')); - $result = $route->matches($resource); - $this->assertFalse($result); - $this->assertEquals(array(), $route->getParams()); + $route->conditions(array( + 'first' => '[a-z]{3,}' + )); + + $this->assertFalse($route->matches('/hello/Josh/and/John')); } /* @@ -324,14 +217,12 @@ public function testRouteDoesNotMatchResourceWithConditions() * * Excludes "+" which is valid but decodes into a space character */ - public function testRouteMatchesResourceWithValidRfc2396PathComponent() + public function testMatchesWithValidRfc2396PathComponent() { $symbols = ':@&=$,'; - $resource = '/rfc2386/' . $symbols; $route = new \Slim\Route('/rfc2386/:symbols', function () {}); - $result = $route->matches($resource); - $this->assertTrue($result); - $this->assertEquals(array('symbols' => $symbols), $route->getParams()); + + $this->assertTrue($route->matches('/rfc2386/' . $symbols)); } /* @@ -339,221 +230,289 @@ public function testRouteMatchesResourceWithValidRfc2396PathComponent() * * "Uniform Resource Identifiers (URI): Generic Syntax" http://www.ietf.org/rfc/rfc2396.txt */ - public function testRouteMatchesResourceWithUnreservedMarks() + public function testMatchesWithUnreservedMarks() { $marks = "-_.!~*'()"; - $resource = '/marks/' . $marks; $route = new \Slim\Route('/marks/:marks', function () {}); - $result = $route->matches($resource); - $this->assertTrue($result); - $this->assertEquals(array('marks' => $marks), $route->getParams()); + + $this->assertTrue($route->matches('/marks/' . $marks)); } - /** - * Route optional parameters - * - * Pre-conditions: - * Route pattern requires :year, optionally accepts :month and :day - * - * Post-conditions: - * All: Year is 2010 - * Case A: Month and day default values are used - * Case B: Month is "05" and day default value is used - * Case C: Month is "05" and day is "13" - */ - public function testRouteOptionalParameters() + public function testMatchesOptionalParameters() { $pattern = '/archive/:year(/:month(/:day))'; - //Case A - $routeA = new \Slim\Route($pattern, function () {}); - $resourceA = '/archive/2010'; - $resultA = $routeA->matches($resourceA); - $this->assertTrue($resultA); - $this->assertEquals(array('year' => '2010'), $routeA->getParams()); + $route1 = new \Slim\Route($pattern, function () {}); + $this->assertTrue($route1->matches('/archive/2010')); + $this->assertEquals(array('year' => '2010'), $route1->getParams()); - //Case B - $routeB = new \Slim\Route($pattern, function () {}); - $resourceB = '/archive/2010/05'; - $resultB = $routeB->matches($resourceB); - $this->assertTrue($resultB); - $this->assertEquals(array('year' => '2010', 'month' => '05'), $routeB->getParams()); + $route2 = new \Slim\Route($pattern, function () {}); + $this->assertTrue($route2->matches('/archive/2010/05')); + $this->assertEquals(array('year' => '2010', 'month' => '05'), $route2->getParams()); - //Case C - $routeC = new \Slim\Route($pattern, function () {}); - $resourceC = '/archive/2010/05/13'; - $resultC = $routeC->matches($resourceC); - $this->assertTrue($resultC); - $this->assertEquals(array('year' => '2010', 'month' => '05', 'day' => '13'), $routeC->getParams()); + $route3 = new \Slim\Route($pattern, function () {}); + $this->assertTrue($route3->matches('/archive/2010/05/13')); + $this->assertEquals(array('year' => '2010', 'month' => '05', 'day' => '13'), $route3->getParams()); } - /** - * Test route default conditions - * - * Pre-conditions: - * Route class has default conditions; - * - * Post-conditions: - * Case A: Route instance has default conditions; - * Case B: Route instance has newly merged conditions; - */ - public function testRouteDefaultConditions() + public function testGetConditions() { - \Slim\Route::setDefaultConditions(array('id' => '\d+')); - $r = new \Slim\Route('/foo', function () {}); - //Case A - $this->assertEquals(\Slim\Route::getDefaultConditions(), $r->getConditions()); - //Case B - $r->conditions(array('name' => '[a-z]{2,5}')); - $c = $r->getConditions(); - $this->assertArrayHasKey('id', $c); - $this->assertArrayHasKey('name', $c); + $route = new \Slim\Route('/foo', function () {}); + + $property = new \ReflectionProperty($route, 'conditions'); + $property->setAccessible(true); + $property->setValue($route, array('foo' => '\d{3}')); + + $this->assertEquals(array('foo' => '\d{3}'), $route->getConditions()); } - /** - * Route matches URI with wildcard - */ - public function testRouteMatchesResourceWithWildcard() + public function testSetDefaultConditions() + { + \Slim\Route::setDefaultConditions(array( + 'id' => '\d+' + )); + + $property = new \ReflectionProperty('\Slim\Route', 'defaultConditions'); + $property->setAccessible(true); + + $this->assertEquals(array( + 'id' => '\d+' + ), $property->getValue()); + } + + public function testGetDefaultConditions() + { + $property = new \ReflectionProperty('\Slim\Route', 'defaultConditions'); + $property->setAccessible(true); + $property->setValue(array( + 'id' => '\d+' + )); + + $this->assertEquals(array( + 'id' => '\d+' + ), \Slim\Route::getDefaultConditions()); + } + + public function testDefaultConditionsAssignedToInstance() + { + $staticProperty = new \ReflectionProperty('\Slim\Route', 'defaultConditions'); + $staticProperty->setAccessible(true); + $staticProperty->setValue(array( + 'id' => '\d+' + )); + $route = new \Slim\Route('/foo', function () {}); + + $this->assertAttributeEquals(array( + 'id' => '\d+' + ), 'conditions', $route); + } + + public function testMatchesWildcard() { - $resource = '/hello/foo/bar/world'; $route = new \Slim\Route('/hello/:path+/world', function () {}); - $result = $route->matches($resource); - $this->assertTrue($result); - $this->assertEquals(array('path'=>array('foo', 'bar')), $route->getParams()); + + $this->assertTrue($route->matches('/hello/foo/bar/world')); + $this->assertAttributeEquals(array( + 'path' => array('foo', 'bar') + ), 'params', $route); } - /** - * Route matches URI with more than one wildcard - */ - public function testRouteMatchesResourceWithMultipleWildcards() + public function testMatchesMultipleWildcards() { - $resource = '/hello/foo/bar/world/2012/03/10'; $route = new \Slim\Route('/hello/:path+/world/:date+', function () {}); - $result = $route->matches($resource); - $this->assertTrue($result); - $this->assertEquals(array('path'=>array('foo', 'bar'), 'date'=>array('2012', '03', '10')), $route->getParams()); + + $this->assertTrue($route->matches('/hello/foo/bar/world/2012/03/10')); + $this->assertAttributeEquals(array( + 'path' => array('foo', 'bar'), + 'date' => array('2012', '03', '10') + ), 'params', $route); } - /** - * Route matches URI with wildcards and parameters - */ - public function testRouteMatchesResourceWithWildcardsAndParams() + public function testMatchesParamsAndWildcards() { - $resource = '/hello/foo/bar/world/2012/03/10/first/second'; $route = new \Slim\Route('/hello/:path+/world/:year/:month/:day/:path2+', function () {}); - $result = $route->matches($resource); - $this->assertTrue($result); - $this->assertEquals(array('path'=>array('foo', 'bar'), 'year'=>'2012', 'month'=>'03', 'day'=>'10', 'path2'=>array('first', 'second')), $route->getParams()); + + $this->assertTrue($route->matches('/hello/foo/bar/world/2012/03/10/first/second')); + $this->assertAttributeEquals(array( + 'path' => array('foo', 'bar'), + 'year' => '2012', + 'month' => '03', + 'day' => '10', + 'path2' => array('first', 'second') + ), 'params', $route); } - /** - * Route matches URI with optional wildcard and parameter - */ - public function testRouteMatchesResourceWithOptionalWildcardsAndParams() + public function testMatchesParamsWithOptionalWildcard() + { + $route = new \Slim\Route('/hello(/:foo(/:bar+))', function () {}); + + $this->assertTrue($route->matches('/hello')); + $this->assertTrue($route->matches('/hello/world')); + $this->assertTrue($route->matches('/hello/world/foo')); + $this->assertTrue($route->matches('/hello/world/foo/bar')); + } + + public function testSetMiddleware() + { + $route = new \Slim\Route('/foo', function () {}); + $mw = function () { + echo 'Foo'; + }; + $route->setMiddleware($mw); + + $this->assertAttributeContains($mw, 'middleware', $route); + } + + public function testSetMiddlewareMultipleTimes() + { + $route = new \Slim\Route('/foo', function () {}); + $mw1 = function () { + echo 'Foo'; + }; + $mw2 = function () { + echo 'Bar'; + }; + $route->setMiddleware($mw1); + $route->setMiddleware($mw2); + + $this->assertAttributeContains($mw1, 'middleware', $route); + $this->assertAttributeContains($mw2, 'middleware', $route); + } + + public function testSetMiddlewareWithArray() { - $resourceA = '/hello/world/foo/bar'; - $routeA = new \Slim\Route('/hello(/:world(/:path+))', function () {}); - $this->assertTrue($routeA->matches($resourceA)); - $this->assertEquals(array('world'=>'world', 'path'=>array('foo', 'bar')), $routeA->getParams()); + $route = new \Slim\Route('/foo', function () {}); + $mw1 = function () { + echo 'Foo'; + }; + $mw2 = function () { + echo 'Bar'; + }; + $route->setMiddleware(array($mw1, $mw2)); + + $this->assertAttributeContains($mw1, 'middleware', $route); + $this->assertAttributeContains($mw2, 'middleware', $route); + } + + public function testSetMiddlewareWithInvalidArgument() + { + $this->setExpectedException('InvalidArgumentException'); + + $route = new \Slim\Route('/foo', function () {}); + $route->setMiddleware('doesNotExist'); // <-- Should throw InvalidArgumentException + } + + public function testSetMiddlewareWithArrayWithInvalidArgument() + { + $this->setExpectedException('InvalidArgumentException'); + + $route = new \Slim\Route('/foo', function () {}); + $route->setMiddleware(array('doesNotExist')); + } + + public function testGetMiddleware() + { + $route = new \Slim\Route('/foo', function () {}); + + $property = new \ReflectionProperty($route, 'middleware'); + $property->setAccessible(true); + $property->setValue($route, array('foo' => 'bar')); + + $this->assertEquals(array('foo' => 'bar'), $route->getMiddleware()); + } + + public function testSetHttpMethods() + { + $route = new \Slim\Route('/foo', function () {}); + $route->setHttpMethods('GET', 'POST'); + + $this->assertAttributeEquals(array('GET', 'POST'), 'methods', $route); + } + + public function testGetHttpMethods() + { + $route = new \Slim\Route('/foo', function () {}); - $resourceB = '/hello/world'; - $routeB = new \Slim\Route('/hello(/:world(/:path))', function () {}); - $this->assertTrue($routeB->matches($resourceB)); - $this->assertEquals(array('world'=>'world'), $routeB->getParams()); + $property = new \ReflectionProperty($route, 'methods'); + $property->setAccessible(true); + $property->setValue($route, array('GET', 'POST')); + + $this->assertEquals(array('GET', 'POST'), $route->getHttpMethods()); + } + + public function testAppendHttpMethods() + { + $route = new \Slim\Route('/foo', function () {}); + + $property = new \ReflectionProperty($route, 'methods'); + $property->setAccessible(true); + $property->setValue($route, array('GET', 'POST')); + + $route->appendHttpMethods('PUT'); + + $this->assertAttributeEquals(array('GET', 'POST', 'PUT'), 'methods', $route); + } + + public function testAppendHttpMethodsWithVia() + { + $route = new \Slim\Route('/foo', function () {}); + $route->via('PUT'); + + $this->assertAttributeContains('PUT', 'methods', $route); + } + + public function testSupportsHttpMethod() + { + $route = new \Slim\Route('/foo', function () {}); + + $property = new \ReflectionProperty($route, 'methods'); + $property->setAccessible(true); + $property->setValue($route, array('POST')); + + $this->assertTrue($route->supportsHttpMethod('POST')); + $this->assertFalse($route->supportsHttpMethod('PUT')); } /** - * Route does not match URI with wildcard + * Test dispatch with params */ - public function testRouteDoesNotMatchResourceWithWildcard() + public function testDispatch() { - $resource = '/hello'; - $route = new \Slim\Route('/hello/:path+', function () {}); - $result = $route->matches($resource); - $this->assertFalse($result); - $this->assertEquals(array(), $route->getParams()); + $this->expectOutputString('Hello josh'); + $route = new \Slim\Route('/hello/:name', function ($name) { echo "Hello $name"; }); + $route->matches('/hello/josh'); //<-- Extracts params from resource URI + $route->dispatch(); } /** - * Test route sets and gets middleware - * - * Pre-conditions: - * Route instantiated - * - * Post-conditions: - * Case A: Middleware set as callable, not array - * Case B: Middleware set after other middleware already set - * Case C: Middleware set as array of callables - * Case D: Middleware set as a callable array - * Case E: Middleware is invalid; throws InvalidArgumentException - * Case F: Middleware is an array with one invalid callable; throws InvalidArgumentException + * Test dispatch with middleware */ - public function testRouteMiddleware() - { - $callable1 = function () {}; - $callable2 = function () {}; - //Case A - $r1 = new \Slim\Route('/foo', function () {}); - $r1->setMiddleware($callable1); - $mw = $r1->getMiddleware(); - $this->assertInternalType('array', $mw); - $this->assertEquals(1, count($mw)); - //Case B - $r1->setMiddleware($callable2); - $mw = $r1->getMiddleware(); - $this->assertEquals(2, count($mw)); - //Case C - $r2 = new \Slim\Route('/foo', function () {}); - $r2->setMiddleware(array($callable1, $callable2)); - $mw = $r2->getMiddleware(); - $this->assertInternalType('array', $mw); - $this->assertEquals(2, count($mw)); - //Case D - $r3 = new \Slim\Route('/foo', function () {}); - $r3->setMiddleware(array($this, 'callableTestFunction')); - $mw = $r3->getMiddleware(); - $this->assertInternalType('array', $mw); - $this->assertEquals(1, count($mw)); - //Case E - try { - $r3->setMiddleware('sdjfsoi788'); - $this->fail('Did not catch InvalidArgumentException when setting invalid route middleware'); - } catch ( \InvalidArgumentException $e ) {} - //Case F - try { - $r3->setMiddleware(array($callable1, $callable2, 'sdjfsoi788')); - $this->fail('Did not catch InvalidArgumentException when setting an array with one invalid route middleware'); - } catch ( \InvalidArgumentException $e ) {} - } - - public function callableTestFunction() {} + public function testDispatchWithMiddlware() + { + $this->expectOutputString('First! Second! Hello josh'); + $route = new \Slim\Route('/hello/:name', function ($name) { echo "Hello $name"; }); + $route->setMiddleware(function () { + echo "First! "; + }); + $route->setMiddleware(function () { + echo "Second! "; + }); + $route->matches('/hello/josh'); //<-- Extracts params from resource URI + $route->dispatch(); + } /** - * Test that a Route manages the HTTP methods that it supports - * - * Case A: Route initially supports no HTTP methods - * Case B: Route can set its supported HTTP methods - * Case C: Route can append supported HTTP methods - * Case D: Route can test if it supports an HTTP method - * Case E: Route can lazily declare supported HTTP methods with `via` + * Test middleware with arguments */ - public function testHttpMethods() - { - //Case A - $r = new \Slim\Route('/foo', function () {}); - $this->assertEmpty($r->getHttpMethods()); - //Case B - $r->setHttpMethods('GET'); - $this->assertEquals(array('GET'), $r->getHttpMethods()); - //Case C - $r->appendHttpMethods('POST', 'PUT'); - $this->assertEquals(array('GET', 'POST', 'PUT'), $r->getHttpMethods()); - //Case D - $this->assertTrue($r->supportsHttpMethod('GET')); - $this->assertFalse($r->supportsHttpMethod('DELETE')); - //Case E - $viaResult = $r->via('DELETE'); - $this->assertTrue($viaResult instanceof \Slim\Route); - $this->assertTrue($r->supportsHttpMethod('DELETE')); + public function testRouteMiddlwareArguments() + { + $this->expectOutputString('foobar'); + $route = new \Slim\Route('/foo', function () { echo "bar"; }); + $route->setName('foo'); + $route->setMiddleware(function ($route) { + echo $route->getName(); + }); + $route->matches('/foo'); //<-- Extracts params from resource URI + $route->dispatch(); } } diff --git a/tests/RouterTest.php b/tests/RouterTest.php index cb5cc33f1..4f61c4b33 100644 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -32,571 +32,211 @@ class RouterTest extends PHPUnit_Framework_TestCase { - protected $env; - protected $req; - protected $res; - - public function setUp() - { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/bar', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $this->env = \Slim\Environment::getInstance(); - $this->req = new \Slim\Http\Request($this->env); - $this->res = new \Slim\Http\Response(); - } - /** - * Router::urlFor should return a full route pattern - * even if no params data is provided. + * Constructor should initialize routes as empty array */ - public function testUrlForNamedRouteWithoutParams() + public function testConstruct() { $router = new \Slim\Router(); - $route = $router->map('/foo/bar', function () {})->via('GET'); - $router->addNamedRoute('foo', $route); - $this->assertEquals('/foo/bar', $router->urlFor('foo')); - } - /** - * Router::urlFor should return a full route pattern if - * param data is provided. - */ - public function testUrlForNamedRouteWithParams() - { - $router = new \Slim\Router(); - $route = $router->map('/foo/:one/and/:two', function ($one, $two) {})->via('GET'); - $router->addNamedRoute('foo', $route); - $this->assertEquals('/foo/Josh/and/John', $router->urlFor('foo', array('one' => 'Josh', 'two' => 'John'))); + $this->assertAttributeEquals(array(), 'routes', $router); } /** - * Router::urlFor should throw an exception if Route with name - * does not exist. - * @expectedException \RuntimeException + * Map should set and return instance of \Slim\Route */ - public function testUrlForNamedRouteThatDoesNotExist() + public function testMap() { $router = new \Slim\Router(); - $route = $router->map('/foo/bar', function () {})->via('GET'); - $router->addNamedRoute('bar', $route); - $router->urlFor('foo'); + $route = new \Slim\Route('/foo', function() {}); + $router->map($route); + + $this->assertAttributeContains($route, 'routes', $router); } /** - * Router::addNamedRoute should throw an exception if named Route - * with same name already exists. + * Named route should be added and indexed by name */ - public function testNamedRouteWithExistingName() + public function testAddNamedRoute() { - $this->setExpectedException('\RuntimeException'); $router = new \Slim\Router(); - $route1 = $router->map('/foo/bar', function () {})->via('GET'); - $route2 = $router->map('/foo/bar/2', function () {})->via('GET'); - $router->addNamedRoute('bar', $route1); - $router->addNamedRoute('bar', $route2); + $route = new \Slim\Route('/foo', function () {}); + $router->addNamedRoute('foo', $route); + + $property = new \ReflectionProperty($router, 'namedRoutes'); + $property->setAccessible(true); + + $rV = $property->getValue($router); + $this->assertSame($route, $rV['foo']); } /** - * Test if named route exists - * - * Pre-conditions: - * Slim app instantiated; - * Named route created; - * - * Post-conditions: - * Named route found to exist; - * Non-existant route found not to exist; + * Named route should have unique name */ - public function testHasNamedRoute() + public function testAddNamedRouteWithDuplicateKey() { + $this->setExpectedException('RuntimeException'); + $router = new \Slim\Router(); - $route = $router->map('/foo', function () {})->via('GET'); + $route = new \Slim\Route('/foo', function () {}); + $router->addNamedRoute('foo', $route); $router->addNamedRoute('foo', $route); - $this->assertTrue($router->hasNamedRoute('foo')); - $this->assertFalse($router->hasNamedRoute('bar')); } /** - * Test Router gets named route - * - * Pre-conditions; - * Slim app instantiated; - * Named route created; - * - * Post-conditions: - * Named route fetched by named; - * NULL is returned if named route does not exist; + * Router should return named route by name, or null if not found */ public function testGetNamedRoute() { $router = new \Slim\Router(); - $route1 = $router->map('/foo', function () {})->via('GET'); - $router->addNamedRoute('foo', $route1); - $this->assertSame($route1, $router->getNamedRoute('foo')); + $route = new \Slim\Route('/foo', function () {}); + + $property = new \ReflectionProperty($router, 'namedRoutes'); + $property->setAccessible(true); + $property->setValue($router, array('foo' => $route)); + + $this->assertSame($route, $router->getNamedRoute('foo')); $this->assertNull($router->getNamedRoute('bar')); } /** - * Test external iterator for Router's named routes - * - * Pre-conditions: - * Slim app instantiated; - * Named routes created; - * - * Post-conditions: - * Array iterator returned for named routes; + * Router should determine named routes and cache results */ public function testGetNamedRoutes() { $router = new \Slim\Router(); - $route1 = $router->map('/foo', function () {})->via('GET'); - $route2 = $router->map('/bar', function () {})->via('POST'); - $router->addNamedRoute('foo', $route1); - $router->addNamedRoute('bar', $route2); - $namedRoutesIterator = $router->getNamedRoutes(); - $this->assertInstanceOf('ArrayIterator', $namedRoutesIterator); - $this->assertEquals(2, $namedRoutesIterator->count()); - } + $route1 = new \Slim\Route('/foo', function () {}); + $route2 = new \Slim\Route('/bar', function () {}); - /** - * Router considers HEAD requests as GET requests - */ - public function testRouterConsidersHeadAsGet() - { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'HEAD', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/bar', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $this->env = \Slim\Environment::getInstance(); - $this->req = new \Slim\Http\Request($this->env); - $this->res = new \Slim\Http\Response(); - $router = new \Slim\Router(); - $route = $router->map('/bar', function () {})->via('GET', 'HEAD'); - $numberOfMatchingRoutes = count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri())); - $this->assertEquals(1, $numberOfMatchingRoutes); + // Init router routes to array + $propertyRouterRoutes = new \ReflectionProperty($router, 'routes'); + $propertyRouterRoutes->setAccessible(true); + $propertyRouterRoutes->setValue($router, array($route1, $route2)); + + // Init router named routes to null + $propertyRouterNamedRoutes = new \ReflectionProperty($router, 'namedRoutes'); + $propertyRouterNamedRoutes->setAccessible(true); + $propertyRouterNamedRoutes->setValue($router, null); + + // Init route name + $propertyRouteName = new \ReflectionProperty($route2, 'name'); + $propertyRouteName->setAccessible(true); + $propertyRouteName->setValue($route2, 'bar'); + + $namedRoutes = $router->getNamedRoutes(); + $this->assertCount(1, $namedRoutes); + $this->assertSame($route2, $namedRoutes['bar']); } /** - * Router::urlFor + * Router should detect presence of a named route by name */ - public function testRouterUrlFor() + public function testHasNamedRoute() { $router = new \Slim\Router(); - $route1 = $router->map('/foo/bar', function () {})->via('GET'); - $route2 = $router->map('/foo/:one/:two', function () {})->via('GET'); - $route3 = $router->map('/foo/:one(/:two)', function () {})->via('GET'); - $route4 = $router->map('/foo/:one/(:two/)', function () {})->via('GET'); - $route5 = $router->map('/foo/:one/(:two/(:three/))', function () {})->via('GET'); - $route6 = $router->map('/foo/:path+/bar', function (){})->via('GET'); - $route7 = $router->map('/foo/:path+/:path2+/bar', function (){})->via('GET'); - $route8 = $router->map('/foo/:path+', function (){})->via('GET'); - $route9 = $router->map('/foo/:var/:var2', function (){})->via('GET'); - $route1->setName('route1'); - $route2->setName('route2'); - $route3->setName('route3'); - $route4->setName('route4'); - $route5->setName('route5'); - $route6->setName('route6'); - $route7->setName('route7'); - $route8->setName('route8'); - $route9->setName('route9'); - //Route - $this->assertEquals('/foo/bar', $router->urlFor('route1')); - //Route with params - $this->assertEquals('/foo/foo/bar', $router->urlFor('route2', array('one' => 'foo', 'two' => 'bar'))); - $this->assertEquals('/foo/foo/:two', $router->urlFor('route2', array('one' => 'foo'))); - $this->assertEquals('/foo/:one/bar', $router->urlFor('route2', array('two' => 'bar'))); - //Route with params and optional segments - $this->assertEquals('/foo/foo/bar', $router->urlFor('route3', array('one' => 'foo', 'two' => 'bar'))); - $this->assertEquals('/foo/foo', $router->urlFor('route3', array('one' => 'foo'))); - $this->assertEquals('/foo/:one/bar', $router->urlFor('route3', array('two' => 'bar'))); - $this->assertEquals('/foo/:one', $router->urlFor('route3')); - //Route with params and optional segments - $this->assertEquals('/foo/foo/bar/', $router->urlFor('route4', array('one' => 'foo', 'two' => 'bar'))); - $this->assertEquals('/foo/foo/', $router->urlFor('route4', array('one' => 'foo'))); - $this->assertEquals('/foo/:one/bar/', $router->urlFor('route4', array('two' => 'bar'))); - $this->assertEquals('/foo/:one/', $router->urlFor('route4')); - //Route with params and optional segments - $this->assertEquals('/foo/foo/bar/what/', $router->urlFor('route5', array('one' => 'foo', 'two' => 'bar', 'three' => 'what'))); - $this->assertEquals('/foo/foo/', $router->urlFor('route5', array('one' => 'foo'))); - $this->assertEquals('/foo/:one/bar/', $router->urlFor('route5', array('two' => 'bar'))); - $this->assertEquals('/foo/:one/bar/what/', $router->urlFor('route5', array('two' => 'bar', 'three' => 'what'))); - $this->assertEquals('/foo/:one/', $router->urlFor('route5')); - //Route with wildcard params - $this->assertEquals('/foo/bar/bar', $router->urlFor('route6', array('path'=>'bar'))); - $this->assertEquals('/foo/foo/bar/bar', $router->urlFor('route7', array('path'=>'foo', 'path2'=>'bar'))); - $this->assertEquals('/foo/bar', $router->urlFor('route8', array('path'=>'bar'))); - //Route with similar param names, test greedy matching - $this->assertEquals('/foo/1/2', $router->urlFor('route9', array('var2'=>'2', 'var'=>'1'))); - $this->assertEquals('/foo/1/2', $router->urlFor('route9', array('var'=>'1', 'var2'=>'2'))); + $route = new \Slim\Route('/foo', function () {}); + + $property = new \ReflectionProperty($router, 'namedRoutes'); + $property->setAccessible(true); + $property->setValue($router, array('foo' => $route)); + + $this->assertTrue($router->hasNamedRoute('foo')); + $this->assertFalse($router->hasNamedRoute('bar')); } /** - * Test that router returns no matches when neither HTTP method nor URI match. + * Router should return current route if set during iteration */ - public function testRouterMatchesRoutesNone() + public function testGetCurrentRoute() { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $this->env = \Slim\Environment::getInstance(); - $this->req = new \Slim\Http\Request($this->env); - $this->res = new \Slim\Http\Response(); $router = new \Slim\Router(); - $router->map('/bar', function () {})->via('POST'); - $router->map('/foo', function () {})->via('POST'); - $router->map('/foo', function () {})->via('PUT'); - $router->map('/foo/bar/xyz', function () {})->via('DELETE'); - $this->assertEquals(0, count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri()))); + $route = new \Slim\Route('/foo', function () {}); + + $property = new \ReflectionProperty($router, 'currentRoute'); + $property->setAccessible(true); + $property->setValue($router, $route); + + $this->assertSame($route, $router->getCurrentRoute()); } /** - * Test that router returns no matches when HTTP method matches but URI does not. + * Router should return first matching route if current route not set yet by iteration */ - public function testRouterMatchesRoutesNoneWhenMethodMatchesUriDoesNot() + public function testGetCurrentRouteIfMatchedRoutes() { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $this->env = \Slim\Environment::getInstance(); - $this->req = new \Slim\Http\Request($this->env); - $this->res = new \Slim\Http\Response(); $router = new \Slim\Router(); - $router->map('/fooNOMATCH', function () {})->via('GET'); - $router->map('/foo', function () {})->via('POST'); - $router->map('/foo', function () {})->via('PUT'); - $router->map('/foo/bar/xyz', function () {})->via('DELETE'); - $this->assertEquals(0, count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri()))); + $route = new \Slim\Route('/foo', function () {}); + + $propertyMatchedRoutes = new \ReflectionProperty($router, 'matchedRoutes'); + $propertyMatchedRoutes->setAccessible(true); + $propertyMatchedRoutes->setValue($router, array($route)); + + $propertyCurrentRoute = new \ReflectionProperty($router, 'currentRoute'); + $propertyCurrentRoute->setAccessible(true); + $propertyCurrentRoute->setValue($router, null); + + $this->assertSame($route, $router->getCurrentRoute()); } /** - * Test that router returns no matches when HTTP method does not match but URI does. + * Router should return `null` if current route not set yet and there are no matching routes */ - public function testRouterMatchesRoutesNoneWhenMethodDoesNotMatchUriMatches() + public function testGetCurrentRouteIfNoMatchedRoutes() { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $this->env = \Slim\Environment::getInstance(); - $this->req = new \Slim\Http\Request($this->env); - $this->res = new \Slim\Http\Response(); $router = new \Slim\Router(); - $router->map('/foo', function () {})->via('OPTIONS'); - $router->map('/foo', function () {})->via('POST'); - $router->map('/foo', function () {})->via('PUT'); - $router->map('/foo/bar/xyz', function () {})->via('DELETE'); - $this->assertEquals(0, count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri()))); + + $propertyMatchedRoutes = new \ReflectionProperty($router, 'matchedRoutes'); + $propertyMatchedRoutes->setAccessible(true); + $propertyMatchedRoutes->setValue($router, array()); + + $propertyCurrentRoute = new \ReflectionProperty($router, 'currentRoute'); + $propertyCurrentRoute->setAccessible(true); + $propertyCurrentRoute->setValue($router, null); + + $this->assertNull($router->getCurrentRoute()); } - /** - * Test that router returns matched routes based on HTTP method and URI. - */ - public function testRouterMatchesRoutes() + public function testGetMatchedRoutes() { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $this->env = \Slim\Environment::getInstance(); - $this->req = new \Slim\Http\Request($this->env); - $this->res = new \Slim\Http\Response(); $router = new \Slim\Router(); - $router->map('/foo', function () {})->via('GET'); - $router->map('/foo', function () {})->via('POST'); - $router->map('/foo', function () {})->via('PUT'); - $router->map('/foo/bar/xyz', function () {})->via('DELETE'); - $this->assertEquals(1, count($router->getMatchedRoutes($this->req->getMethod(), $this->req->getResourceUri()))); - } - /** - * Test get current route - */ - public function testGetCurrentRoute() - { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo' //<-- Virtual - )); - $app = new \Slim\Slim(); - $route1 = $app->get('/bar', function () { - echo "Bar"; - }); - $route2 = $app->get('/foo', function () { - echo "Foo"; - }); - $app->call(); - $this->assertSame($route2, $app->router()->getCurrentRoute()); - } + $route1 = new \Slim\Route('/foo', function () {}); + $route1 = $route1->via('GET'); - /** - * Test calling get current route before routing doesn't cause errors - */ - public function testGetCurrentRouteBeforeRoutingDoesntError() - { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo' //<-- Virtual - )); - $app = new \Slim\Slim(); - $route1 = $app->get('/bar', function () { - echo "Bar"; - }); - $route2 = $app->get('/foo', function () { - echo "Foo"; - }); - - $app->router()->getCurrentRoute(); - - $app->call(); - } + $route2 = new \Slim\Route('/foo', function () {}); + $route2 = $route2->via('POST'); - /** - * Test get current route before routing returns null - */ - public function testGetCurrentRouteBeforeRoutingReturnsNull() - { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo' //<-- Virtual - )); - $app = new \Slim\Slim(); - $route1 = $app->get('/bar', function () { - echo "Bar"; - }); - $route2 = $app->get('/foo', function () { - echo "Foo"; - }); - - $this->assertSame(null, $app->router()->getCurrentRoute()); - } + $route3 = new \Slim\Route('/bar', function () {}); + $route3 = $route3->via('PUT'); - /** - * Test get current route during slim.before.dispatch hook - */ - public function testGetCurrentRouteDuringBeforeDispatchHook() - { - $route = null; - - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo' //<-- Virtual - )); - $app = new \Slim\Slim(); - $app->hook('slim.before.dispatch', function() use(&$route, $app) { - $route = $app->router()->getCurrentRoute(); - }); - $route1 = $app->get('/bar', function () { - echo "Bar"; - }); - $route2 = $app->get('/foo', function () { - echo "Foo"; - }); - - $app->call(); - $this->assertSame($route2, $route); - } + $routes = new \ReflectionProperty($router, 'routes'); + $routes->setAccessible(true); + $routes->setValue($router, array($route1, $route2, $route3)); - /** - * Test get current route during routing - */ - public function testGetCurrentRouteDuringRouting() - { - $route = null; - - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo' //<-- Virtual - )); - $app = new \Slim\Slim(); - $route1 = $app->get('/bar', function () { - echo "Bar"; - }); - $route2 = $app->get('/foo', function () use (&$route, $app) { - echo "Foo"; - $route = $app->router()->getCurrentRoute(); - }); - - $app->call(); - $this->assertSame($route2, $route); + $matchedRoutes = $router->getMatchedRoutes('GET', '/foo'); + $this->assertSame($route1, $matchedRoutes[0]); } - /** - * Test get current route after routing - */ - public function testGetCurrentRouteAfterRouting() - { - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo' //<-- Virtual - )); - $app = new \Slim\Slim(); - $route1 = $app->get('/bar', function () { - echo "Bar"; - }); - $route2 = $app->get('/foo', function () { - echo "Foo"; - }); - $app->call(); - $this->assertSame($route2, $app->router()->getCurrentRoute()); - } + // Test url for named route - public function testDispatch() + public function testUrlFor() { - $this->expectOutputString('Hello josh'); - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/hello/josh', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $env = \Slim\Environment::getInstance(); - $req = new \Slim\Http\Request($env); $router = new \Slim\Router(); - $route = new \Slim\Route('/hello/:name', function ($name) { echo "Hello $name"; }); - $route->matches($req->getResourceUri()); //<-- Extracts params from resource URI - $router->dispatch($route); - } + $route1 = new \Slim\Route('/hello/:first/:last', function () {}); + $route1 = $route1->via('GET')->name('hello'); - public function testDispatchWithMiddlware() - { - $this->expectOutputString('First! Second! Hello josh'); - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/hello/josh', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $env = \Slim\Environment::getInstance(); - $req = new \Slim\Http\Request($env); - $router = new \Slim\Router(); - $route = new \Slim\Route('/hello/:name', function ($name) { echo "Hello $name"; }); - $route->setMiddleware(function () { - echo "First! "; - }); - $route->setMiddleware(function () { - echo "Second! "; - }); - $route->matches($req->getResourceUri()); //<-- Extracts params from resource URI - $router->dispatch($route); - } + $routes = new \ReflectionProperty($router, 'namedRoutes'); + $routes->setAccessible(true); + $routes->setValue($router, array('hello' => $route1)); - public function testRouteMiddlwareArguments() - { - $this->expectOutputString('foobar'); - \Slim\Environment::mock(array( - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/foo' //<-- Virtual - )); - $env = \Slim\Environment::getInstance(); - $req = new \Slim\Http\Request($env); - $router = new \Slim\Router(); - $route = new \Slim\Route('/foo', function () { echo "bar"; }); - $route->setName('foo'); - $route->setMiddleware(function ($route) { - echo $route->getName(); - }); - $route->matches($req->getResourceUri()); //<-- Extracts params from resource URI - $router->dispatch($route); + $this->assertEquals('/hello/Josh/Lockhart', $router->urlFor('hello', array('first' => 'Josh', 'last' => 'Lockhart'))); } - public function testDispatchWithoutCallable() + public function testUrlForIfNoSuchRoute() { - $this->setExpectedException('InvalidArgumentException'); - \Slim\Environment::mock(array( - 'REQUEST_METHOD' => 'GET', - 'REMOTE_ADDR' => '127.0.0.1', - 'SCRIPT_NAME' => '', //<-- Physical - 'PATH_INFO' => '/hello/josh', //<-- Virtual - 'QUERY_STRING' => 'one=1&two=2&three=3', - 'SERVER_NAME' => 'slim', - 'SERVER_PORT' => 80, - 'slim.url_scheme' => 'http', - 'slim.input' => '', - 'slim.errors' => fopen('php://stderr', 'w'), - 'HTTP_HOST' => 'slim' - )); - $env = \Slim\Environment::getInstance(); - $req = new \Slim\Http\Request($env); + $this->setExpectedException('RuntimeException'); + $router = new \Slim\Router(); - $route = new \Slim\Route('/hello/:name', 'foo'); // <-- Fail fast + $router->urlFor('foo', array('abc' => '123')); } } diff --git a/tests/SlimTest.php b/tests/SlimTest.php index a1aa3ad76..638da7c84 100644 --- a/tests/SlimTest.php +++ b/tests/SlimTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -371,6 +371,27 @@ public function testPutRoute() $this->assertSame($callable, $route->getCallable()); } + /** + * Test PATCH route + */ + public function testPatchRoute() + { + \Slim\Environment::mock(array( + 'REQUEST_METHOD' => 'PATCH', + 'SCRIPT_NAME' => '/foo', //<-- Physical + 'PATH_INFO' => '/bar', //<-- Virtual + )); + $s = new \Slim\Slim(); + $mw1 = function () { echo "foo"; }; + $mw2 = function () { echo "bar"; }; + $callable = function () { echo "xyz"; }; + $route = $s->patch('/bar', $mw1, $mw2, $callable); + $s->call(); + $this->assertEquals('foobarxyz', $s->response()->body()); + $this->assertEquals('/bar', $route->getPattern()); + $this->assertSame($callable, $route->getCallable()); + } + /** * Test DELETE route */ @@ -413,6 +434,51 @@ public function testOptionsRoute() $this->assertSame($callable, $route->getCallable()); } + /** + * Test route groups + */ + public function testRouteGroups() + { + \Slim\Environment::mock(array( + 'REQUEST_METHOD' => 'GET', + 'SCRIPT_NAME' => '/foo', //<-- Physical + 'PATH_INFO' => '/bar/baz', //<-- Virtual' + )); + $s = new \Slim\Slim(); + $mw1 = function () { echo "foo"; }; + $mw2 = function () { echo "bar"; }; + $callable = function () { echo "xyz"; }; + $s->group('/bar', $mw1, function () use ($s, $mw2, $callable) { + $s->get('/baz', $mw2, $callable); + }); + $s->call(); + $this->assertEquals('foobarxyz', $s->response()->body()); + } + + /* + * Test ANY route + */ + public function testAnyRoute() + { + $mw1 = function () { echo "foo"; }; + $mw2 = function () { echo "bar"; }; + $callable = function () { echo "xyz"; }; + $methods = array('GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'); + foreach ($methods as $i => $method) { + \Slim\Environment::mock(array( + 'REQUEST_METHOD' => $method, + 'SCRIPT_NAME' => '/foo', //<-- Physical + 'PATH_INFO' => '/bar', //<-- Virtual + )); + $s = new \Slim\Slim(); + $route = $s->any('/bar', $mw1, $mw2, $callable); + $s->call(); + $this->assertEquals('foobarxyz', $s->response()->body()); + $this->assertEquals('/bar', $route->getPattern()); + $this->assertSame($callable, $route->getCallable()); + } + } + /** * Test if route does NOT expect trailing slash and URL has one */ @@ -545,7 +611,7 @@ public function testLastModifiedMatch() 'REQUEST_METHOD' => 'GET', 'SCRIPT_NAME' => '/foo', //<-- Physical 'PATH_INFO' => '/bar', //<-- Virtual - 'IF_MODIFIED_SINCE' => 'Sun, 03 Oct 2010 17:00:52 -0400', + 'HTTP_IF_MODIFIED_SINCE' => 'Sun, 03 Oct 2010 17:00:52 -0400', )); $s = new \Slim\Slim(); $s->get('/bar', function () use ($s) { @@ -595,7 +661,7 @@ public function testEtagMatches() \Slim\Environment::mock(array( 'SCRIPT_NAME' => '/foo', //<-- Physical 'PATH_INFO' => '/bar', //<-- Virtual - 'IF_NONE_MATCH' => '"abc123"', + 'HTTP_IF_NONE_MATCH' => '"abc123"', )); $s = new \Slim\Slim(); $s->get('/bar', function () use ($s) { @@ -706,11 +772,11 @@ public function testSetCookie() $s->setCookie('foo1', 'bar1', '2 days'); }); $s->call(); - list($status, $header, $body) = $s->response()->finalize(); - $cookies = explode("\n", $header['Set-Cookie']); - $this->assertEquals(2, count($cookies)); - $this->assertEquals(1, preg_match('@foo=bar@', $cookies[0])); - $this->assertEquals(1, preg_match('@foo1=bar1@', $cookies[1])); + $cookie1 = $s->response->cookies->get('foo'); + $cookie2 = $s->response->cookies->get('foo1'); + $this->assertEquals(2, count($s->response->cookies)); + $this->assertEquals('bar', $cookie1['value']); + $this->assertEquals('bar1', $cookie2['value']); } /** @@ -730,7 +796,7 @@ public function testGetCookie() 'QUERY_STRING' => 'one=foo&two=bar', 'SERVER_NAME' => 'slimframework.com', 'SERVER_PORT' => 80, - 'COOKIE' => 'foo=bar; foo2=bar2', + 'HTTP_COOKIE' => 'foo=bar; foo2=bar2', 'slim.url_scheme' => 'http', 'slim.input' => '', 'slim.errors' => @fopen('php://stderr', 'w') @@ -773,60 +839,10 @@ public function testDeleteCookie() $s->deleteCookie('foo'); }); $s->call(); - list($status, $header, $body) = $s->response()->finalize(); - $cookies = explode("\n", $header['Set-Cookie']); - $this->assertEquals(1, count($cookies)); - $this->assertEquals(1, preg_match('@^foo=;@', $cookies[0])); - } - - /** - * Test set encrypted cookie - * - * This method ensures that the `Set-Cookie:` HTTP request - * header is set. The implementation is tested in a separate file. - */ - public function testSetEncryptedCookie() - { - $s = new \Slim\Slim(); - $s->setEncryptedCookie('foo', 'bar'); - $r = $s->response(); - $this->assertEquals(1, preg_match("@^foo=.+%7C.+%7C.+@", $r['Set-Cookie'])); //<-- %7C is a url-encoded pipe - } - - /** - * Test get encrypted cookie - * - * This only tests that this method runs without error. The implementation of - * fetching the encrypted cookie is tested separately. - */ - public function testGetEncryptedCookieAndDeletingIt() - { - \Slim\Environment::mock(array( - 'SCRIPT_NAME' => '/foo', //<-- Physical - 'PATH_INFO' => '/bar', //<-- Virtual - )); - $s = new \Slim\Slim(); - $r = $s->response(); - $this->assertFalse($s->getEncryptedCookie('foo')); - $this->assertEquals(1, preg_match("@foo=;.*@", $r['Set-Cookie'])); - } - - /** - * Test get encrypted cookie WITHOUT deleting it - * - * This only tests that this method runs without error. The implementation of - * fetching the encrypted cookie is tested separately. - */ - public function testGetEncryptedCookieWithoutDeletingIt() - { - \Slim\Environment::mock(array( - 'SCRIPT_NAME' => '/foo', //<-- Physical - 'PATH_INFO' => '/bar', //<-- Virtual - )); - $s = new \Slim\Slim(); - $r = $s->response(); - $this->assertFalse($s->getEncryptedCookie('foo', false)); - $this->assertEquals(0, preg_match("@foo=;.*@", $r['Set-Cookie'])); + $cookie = $s->response->cookies->get('foo'); + $this->assertEquals(1, count($s->response->cookies)); + $this->assertEquals('', $cookie['value']); + $this->assertLessThan(time(), $cookie['expires']); } /************************************************ @@ -1215,13 +1231,13 @@ public function testSlimError() public function testDefaultHandlerLogsTheErrorWhenDebugIsFalse() { $s = new \Slim\Slim(array('debug' => false)); + $s->container->singleton('log', function ($c) { + return new EchoErrorLogger(); + }); $s->get('/bar', function () use ($s) { throw new \InvalidArgumentException('my specific error message'); }); - $env = $s->environment(); - $env['slim.log'] = new EchoErrorLogger(); // <-- inject the fake logger - ob_start(); $s->run(); $output = ob_get_clean(); @@ -1323,22 +1339,20 @@ public function testHandleErrors() { $defaultErrorReporting = error_reporting(); - // Assert Slim ignores E_NOTICE errors + // Test 1 error_reporting(E_ALL ^ E_NOTICE); // <-- Report all errors EXCEPT notices try { - $this->assertTrue(\Slim\Slim::handleErrors(E_NOTICE, 'test error', 'Slim.php', 119)); + \Slim\Slim::handleErrors(E_NOTICE, 'test error', 'Slim.php', 119); } catch (\ErrorException $e) { $this->fail('Slim::handleErrors reported a disabled error level.'); } - // Assert Slim reports E_STRICT errors + // Test 2 error_reporting(E_ALL | E_STRICT); // <-- Report all errors, including E_STRICT try { \Slim\Slim::handleErrors(E_STRICT, 'test error', 'Slim.php', 119); $this->fail('Slim::handleErrors didn\'t report a enabled error level'); - } catch (\ErrorException $e) { - $this->assertEquals('test error', $e->getMessage()); - } + } catch (\ErrorException $e) {} error_reporting($defaultErrorReporting); } diff --git a/tests/ViewTest.php b/tests/ViewTest.php index d7f501cef..2f1b1e9e3 100644 --- a/tests/ViewTest.php +++ b/tests/ViewTest.php @@ -6,7 +6,7 @@ * @copyright 2011 Josh Lockhart * @link http://www.slimframework.com * @license http://www.slimframework.com/license - * @version 2.2.0 + * @version 2.3.0 * * MIT LICENSE * @@ -32,164 +32,137 @@ class ViewTest extends PHPUnit_Framework_TestCase { - public function setUp() + public function testGetDataAll() { - $this->view = new \Slim\View(); + $view = new \Slim\View(); + $prop = new \ReflectionProperty($view, 'data'); + $prop->setAccessible(true); + $prop->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar'))); + + $this->assertSame(array('foo' => 'bar'), $view->getData()); } - public function generateTestData() + public function testGetDataKeyExists() { - return array('a' => 1, 'b' => 2, 'c' => 3); + $view = new \Slim\View(); + $prop = new \ReflectionProperty($view, 'data'); + $prop->setAccessible(true); + $prop->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar'))); + + $this->assertEquals('bar', $view->getData('foo')); } - /** - * Test initial View data is an empty array - * - * Pre-conditions: - * None - * - * Post-conditions: - * The View object's data attribute is an empty array - */ - public function testViewIsConstructedWithDataArray() + public function testGetDataKeyNotExists() { - $this->assertEquals(array(), $this->view->getData()); + $view = new \Slim\View(); + $prop = new \ReflectionProperty($view, 'data'); + $prop->setAccessible(true); + $prop->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar'))); + + $this->assertNull($view->getData('abc')); } - /** - * Test View sets and gets data - * - * Pre-conditions: - * Case A: Set view data key/value - * Case B: Set view data as array - * Case C: Set view data with one argument that is not an array - * - * Post-conditions: - * Case A: Data key/value are set - * Case B: Data is set to array - * Case C: An InvalidArgumentException is thrown - */ - public function testViewSetAndGetData() + public function testSetDataKeyValue() { - //Case A - $this->view->setData('one', 1); - $this->assertEquals(1, $this->view->getData('one')); - - //Case B - $data = array('foo' => 'bar', 'a' => 'A'); - $this->view->setData($data); - $this->assertSame($data, $this->view->getData()); - - //Case C - try { - $this->view->setData('foo'); - $this->fail('Setting View data with non-array single argument did not throw exception'); - } catch ( \InvalidArgumentException $e ) {} + $view = new \Slim\View(); + $prop = new \ReflectionProperty($view, 'data'); + $prop->setAccessible(true); + $view->setData('foo', 'bar'); + + $this->assertEquals(array('foo' => 'bar'), $prop->getValue($view)->all()); } - /** - * Test View appends data - * - * Pre-conditions: - * Case A: Append data to View several times - * Case B: Append view data which is not an array - * - * Post-conditions: - * Case A: The View data contains all appended data - * Case B: An InvalidArgumentException is thrown - */ - public function testViewAppendsData() + public function testSetDataArray() { - //Case A - $this->view->appendData(array('a' => 'A')); - $this->view->appendData(array('b' => 'B')); - $this->assertEquals(array('a' => 'A', 'b' => 'B'), $this->view->getData()); + $view = new \Slim\View(); + $prop = new \ReflectionProperty($view, 'data'); + $prop->setAccessible(true); + $view->setData(array('foo' => 'bar')); + + $this->assertEquals(array('foo' => 'bar'), $prop->getValue($view)->all()); + } + + public function testSetDataInvalidArgument() + { + $this->setExpectedException('InvalidArgumentException'); + + $view = new \Slim\View(); + $view->setData('foo'); + } + + public function testAppendData() + { + $view = new \Slim\View(); + $prop = new \ReflectionProperty($view, 'data'); + $prop->setAccessible(true); + $view->appendData(array('foo' => 'bar')); + + $this->assertEquals(array('foo' => 'bar'), $prop->getValue($view)->all()); + } - //Case B - try { - $this->view->appendData('not an array'); - $this->fail('Appending View data with non-array argument did not throw exception'); - } catch ( \InvalidArgumentException $e ) {} + public function testAppendDataOverwrite() + { + $view = new \Slim\View(); + $prop = new \ReflectionProperty($view, 'data'); + $prop->setAccessible(true); + $prop->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar'))); + $view->appendData(array('foo' => '123')); + $this->assertEquals(array('foo' => '123'), $prop->getValue($view)->all()); } - /** - * Test View templates directory - * - * Pre-conditions: - * View templates directory is set to an existing directory - * - * Post-conditions: - * The templates directory is set correctly. - */ - public function testSetsTemplatesDirectory() + public function testAppendDataInvalidArgument() { - $templatesDirectory = dirname(__FILE__) . '/templates'; - $this->view->setTemplatesDirectory($templatesDirectory); - $this->assertEquals($templatesDirectory, $this->view->getTemplatesDirectory()); + $this->setExpectedException('InvalidArgumentException'); + + $view = new \Slim\View(); + $view->appendData('foo'); } - /** - * Test View templates directory may have a trailing slash when set - * - * Pre-conditions: - * View templates directory is set to an existing directory with a trailing slash - * - * Post-conditions: - * The View templates directory is set correctly without a trailing slash - */ - public function testTemplatesDirectoryWithTrailingSlash() + public function testGetTemplatesDirectory() { - $this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates/'); - $this->assertEquals(dirname(__FILE__) . '/templates', $this->view->getTemplatesDirectory()); + $view = new \Slim\View(); + $property = new \ReflectionProperty($view, 'templatesDirectory'); + $property->setAccessible(true); + $property->setValue($view, 'templates'); + + $this->assertEquals('templates', $view->getTemplatesDirectory()); } - /** - * Test View renders template - * - * Pre-conditions: - * View templates directory is set to an existing directory. - * View data is set without errors - * Case A: View renders an existing template - * Case B: View renders a non-existing template - * - * Post-conditions: - * Case A: The rendered template is returned as a string - * Case B: A RuntimeException is thrown - */ - public function testRendersTemplateWithData() + public function testSetTemplatesDirectory() { - $this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates'); - $this->view->setData(array('foo' => 'bar')); - - //Case A - $output = $this->view->render('test.php'); - $this->assertEquals('test output bar', $output); - - //Case B - try { - $output = $this->view->render('foo.php'); - $this->fail('Rendering non-existent template did not throw exception'); - } catch ( \RuntimeException $e ) {} + $view = new \Slim\View(); + $view->setTemplatesDirectory('templates/'); // <-- Should strip trailing slash + + $this->assertAttributeEquals('templates', 'templatesDirectory', $view); } - /** - * Test View displays template - * - * Pre-conditions: - * View templates directory is set to an existing directory. - * View data is set without errors - * View is displayed - * - * Post-conditions: - * The output buffer contains the rendered template - */ - public function testDisplaysTemplateWithData() + public function testDisplay() { $this->expectOutputString('test output bar'); - $this->view->setTemplatesDirectory(dirname(__FILE__) . '/templates'); - $this->view->setData(array('foo' => 'bar')); - $this->view->display('test.php'); + + $view = new \Slim\View(); + $prop1 = new \ReflectionProperty($view, 'data'); + $prop1->setAccessible(true); + $prop1->setValue($view, new \Slim\Helper\Set(array('foo' => 'bar'))); + + $prop2 = new \ReflectionProperty($view, 'templatesDirectory'); + $prop2->setAccessible(true); + $prop2->setValue($view, dirname(__FILE__) . '/templates'); + + $view->display('test.php'); } + public function testDisplayTemplateThatDoesNotExist() + { + $this->setExpectedException('\RuntimeException'); + + $view = new \Slim\View(); + + $prop2 = new \ReflectionProperty($view, 'templatesDirectory'); + $prop2->setAccessible(true); + $prop2->setValue($view, dirname(__FILE__) . '/templates'); + + $view->display('foo.php'); + } }