diff --git a/build/logs/clover.xml b/build/logs/clover.xml
new file mode 100644
index 0000000..be5d2d5
--- /dev/null
+++ b/build/logs/clover.xml
@@ -0,0 +1,1033 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/composer.json b/composer.json
index 905b3f6..566205c 100644
--- a/composer.json
+++ b/composer.json
@@ -13,7 +13,8 @@
}
],
"require": {
- "php": "^5.5 || ^7.0"
+ "php": "^5.5 || ^7.0",
+ "psr/http-message": "^1.0"
},
"autoload": {
"psr-4": {
@@ -22,7 +23,7 @@
},
"autoload-dev": {
"psr-4": {
- "Meek\\Http\\Tests\\": "tests/"
+ "Meek\\Http\\": "tests/"
}
}
}
diff --git a/composer.lock b/composer.lock
index 85fdabb..c131715 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,22 +4,72 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "hash": "e4ff31f88f296def832bcba893987421",
- "content-hash": "43eeb7252a4843aeb62e9437eccbb9fd",
- "packages": [],
+ "hash": "816ebaa9f67650dd19dc156d64aea6cc",
+ "content-hash": "5101cd47e42b08eebfa86ecf5291954b",
+ "packages": [
+ {
+ "name": "psr/http-message",
+ "version": "1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
+ "reference": "85d63699f0dbedb190bbd4b0d2b9dc707ea4c298",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "time": "2015-05-04 20:22:00"
+ }
+ ],
"packages-dev": [
{
"name": "codeclimate/php-test-reporter",
- "version": "v0.3.0",
+ "version": "v0.3.2",
"source": {
"type": "git",
"url": "https://github.com/codeclimate/php-test-reporter.git",
- "reference": "770e5a274608505e9c59f4699ca26f6840acbfa4"
+ "reference": "3a2d3ebdc1df5acf372458c15041af240a6fc016"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/codeclimate/php-test-reporter/zipball/770e5a274608505e9c59f4699ca26f6840acbfa4",
- "reference": "770e5a274608505e9c59f4699ca26f6840acbfa4",
+ "url": "https://api.github.com/repos/codeclimate/php-test-reporter/zipball/3a2d3ebdc1df5acf372458c15041af240a6fc016",
+ "reference": "3a2d3ebdc1df5acf372458c15041af240a6fc016",
"shasum": ""
},
"require": {
@@ -38,7 +88,7 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "0.2.x-dev"
+ "dev-master": "0.3.x-dev"
}
},
"autoload": {
@@ -64,7 +114,7 @@
"codeclimate",
"coverage"
],
- "time": "2016-02-16 14:55:26"
+ "time": "2016-04-19 16:54:33"
},
{
"name": "doctrine/instantiator",
diff --git a/example/FictionalRouter.php b/example/FictionalRouter.php
deleted file mode 100644
index fc52531..0000000
--- a/example/FictionalRouter.php
+++ /dev/null
@@ -1,14 +0,0 @@
-getBody()->write($body);
+
+ return $response->prepare(get_current_request())->send();
+}
diff --git a/example/index.php b/example/index.php
index 391c9d2..630535e 100644
--- a/example/index.php
+++ b/example/index.php
@@ -6,69 +6,15 @@
// include external libraries
require_once '../vendor/autoload.php';
-require_once 'FictionalRouter.php';
-
-// use statements
-use Meek\Http\Request;
-use Meek\Http\Session\PdoStorageDriver;
-use Meek\Http\Session;
-use Meek\Http\Response;
-use Meek\Http\RedirectedResponse;
-use Meek\Http\Exception as HttpException;
-
-// get the current request
-$request = Request::createFromGlobals();
-
-// manipulate the current URI
-$uri = $request->getUri();
-$uri->setPath(substr($uri->getPath(), strlen('/meek-http')));
-
-// initialize a session
-$dbh = new PDO('mysql:host=localhost;dbname=test', 'root', '', [
- PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
-]);
-$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
-$handler = new PdoStorageDriver($dbh);
-$session = new Session($handler);
-
-$request->setSession($session);
-$request->session->start();
-
-// setup and initalize our application's routes
-$router = new FictionalRouter();
-
-// basic usage
-$router->map('GET', '/', function ($request) {
- $username = $request->session->get('username', 'World');
-
- return new Response(sprintf('Hello, %s!', $username));
-});
-
-// working with headers and redirections
-$router->map('GET', '/login', function ($request) {
- $username = $request->server->get('PHP_AUTH_USER');
- $password = $request->server->get('PHP_AUTH_PW');
-
- if ($username === 'admin' && $password === 'password') {
- $request->session->set('username', $username);
- return new RedirectedResponse('/meek-http/account');
- }
-
- return new Response('Please sign in.', 401, [
- 'WWW-Authenticate' => sprintf('Basic realm=%s', 'site_login')
- ]);
-});
-
-// working with JSON responses
-$router->map('GET', '/api/v1', function () {
- $user = new stdClass();
- $user->name = 'Nathan';
- $user->dob = '24-02-1991';
-
- return new Meek\Http\JsonResponse($user);
-});
-
-// save session to store and dispatch request
-$request->session->save();
-$router->dispatch($request);
+require_once 'functions.php';
+
+// dispatch a response based on the current request
+send_response('
+
+
+
+
+
+
+
+');
diff --git a/src/Collections/FileList.php b/src/Collections/FileList.php
deleted file mode 100644
index 71ee029..0000000
--- a/src/Collections/FileList.php
+++ /dev/null
@@ -1,10 +0,0 @@
-all() as $key => $value) {
- if (static::hasPrefix($key, static::$headerPrefix)) {
- $headers[substr($key, strlen(static::$headerPrefix))] = $value;
- } else if (in_array($key, static::$nonPrefixedHeaders)) {
- $headers[$key] = $value;
- }
- }
-
- return $headers;
- }
-
- protected static function hasPrefix($string, $prefix)
- {
- return strpos($string, $prefix) === 0;
- }
-}
diff --git a/src/Cookie.php b/src/Cookie.php
index b8a766f..018b392 100644
--- a/src/Cookie.php
+++ b/src/Cookie.php
@@ -6,56 +6,127 @@
use DateTime;
use DateTimeInterface;
+/**
+ * A simple class for working with HTTP "cookies".
+ *
+ * @see https://tools.ietf.org/html/rfc6265
+ * @version 0.1.0
+ * @author Nathan Bishop (nbish11)
+ * @copyright 2016 Nathan Bishop
+ * @license MIT
+ */
class Cookie
{
+ /**
+ * [$name description]
+ *
+ * @var string
+ */
protected $name;
+
+ /**
+ * [$value description]
+ *
+ * @var string
+ */
protected $value;
+
+ /**
+ * [$expire description]
+ *
+ * @var integer
+ */
protected $expire;
+
+ /**
+ * [$path description]
+ *
+ * @var string
+ */
protected $path;
+
+ /**
+ * [$domain description]
+ *
+ * @var string
+ */
protected $domain;
+
+ /**
+ * [$secure description]
+ *
+ * @var boolean
+ */
protected $secure;
+
+ /**
+ * [$httpOnly description]
+ *
+ * @var boolean
+ */
protected $httpOnly;
-
- public function __construct(
- $name,
- $value = '',
- $expire = 0,
- $path = '',
- $domain = '',
- $secure = false,
- $httpOnly = true
- ) {
+
+ /**
+ * [__construct description]
+ *
+ * @param string $name [description]
+ * @param string $value [description]
+ * @param integer $expire [description]
+ * @param string $path [description]
+ * @param string $domain [description]
+ */
+ public function __construct($name, $value = '', $expire = 0, $path = '', $domain = '')
+ {
$this->setName($name);
$this->setValue($value);
$this->setExpire($expire);
$this->setPath($path);
$this->setDomain($domain);
- $this->setSecure($secure);
- $this->setHttpOnly($httpOnly);
+ $this->setSecure(false);
+ $this->setHttpOnly(true);
}
+ /**
+ * [setName description]
+ *
+ * @param string $name [description]
+ *
+ * @return self
+ */
public function setName($name)
{
+ if (empty($name)) {
+ throw new InvalidArgumentException('The cookie name cannot be empty.');
+ }
+
if (preg_match("/[=,; \t\r\n\013\014]/", $name)) {
throw new InvalidArgumentException(
sprintf('The cookie name "%s" contains invalid characters.', $name)
);
}
- if (empty($name)) {
- throw new InvalidArgumentException('The cookie name cannot be empty.');
- }
-
$this->name = $name;
return $this;
}
+ /**
+ * [getName description]
+ *
+ * @return string [description]
+ */
public function getName()
{
return $this->name;
}
+ /**
+ * [setValue description]
+ *
+ * @param string $value [description]
+ *
+ * @return self
+ */
public function setValue($value)
{
$this->value = $value;
@@ -63,21 +134,35 @@ public function setValue($value)
return $this;
}
+ /**
+ * [getValue description]
+ *
+ * @return string [description]
+ */
public function getValue()
{
return $this->value;
}
+ /**
+ * [setExpire description]
+ *
+ * @param string|DateTimeInterface|integer $expire [description]
+ *
+ * @return self
+ */
public function setExpire($expire)
{
- if ($expire instanceof DateTime || $expire instanceof DateTimeInterface) {
+ if (is_string($expire)) {
+ $expire = new DateTime($expire);
+ }
+
+ if ($expire instanceof DateTimeInterface) {
$expire = $expire->format('U');
- } else if (!is_numeric($expire)) {
- $expire = strtotime($expire);
+ }
- if ($expire === false || $expire === -1) {
- throw new InvalidArgumentException('The cookie expiration time is not valid.');
- }
+ if ($expire === -1 || !is_integer($expire)) {
+ throw new InvalidArgumentException('The cookie expiration time is not valid.');
}
$this->expire = $expire;
@@ -85,11 +170,23 @@ public function setExpire($expire)
return $this;
}
+ /**
+ * [getExpire description]
+ *
+ * @return integer [description]
+ */
public function getExpire()
{
return $this->expire;
}
+ /**
+ * [setPath description]
+ *
+ * @param string $path [description]
+ *
+ * @return self
+ */
public function setPath($path)
{
$this->path = empty($path) ? '/' : $path;
@@ -97,11 +194,23 @@ public function setPath($path)
return $this;
}
+ /**
+ * [getPath description]
+ *
+ * @return string [description]
+ */
public function getPath()
{
return $this->path;
}
+ /**
+ * [setDomain description]
+ *
+ * @param string $domain [description]
+ *
+ * @return self
+ */
public function setDomain($domain)
{
$this->domain = $domain;
@@ -109,11 +218,23 @@ public function setDomain($domain)
return $this;
}
+ /**
+ * [getDomain description]
+ *
+ * @return string [description]
+ */
public function getDomain()
{
return $this->domain;
}
+ /**
+ * [setSecure description]
+ *
+ * @param boolean $secure [description]
+ *
+ * @return self
+ */
public function setSecure($secure)
{
$this->secure = (boolean) $secure;
@@ -121,6 +242,13 @@ public function setSecure($secure)
return $this;
}
+ /**
+ * [setHttpOnly description]
+ *
+ * @param boolean $httpOnly [description]
+ *
+ * @return self
+ */
public function setHttpOnly($httpOnly)
{
$this->httpOnly = (boolean) $httpOnly;
@@ -128,16 +256,31 @@ public function setHttpOnly($httpOnly)
return $this;
}
+ /**
+ * [isSecure description]
+ *
+ * @return boolean [description]
+ */
public function isSecure()
{
return $this->secure;
}
+ /**
+ * [isHttpOnly description]
+ *
+ * @return boolean [description]
+ */
public function isHttpOnly()
{
return $this->httpOnly;
}
+ /**
+ * [__toString description]
+ *
+ * @return string [description]
+ */
public function __toString()
{
$output = '';
@@ -146,7 +289,7 @@ public function __toString()
$output .= urlencode($this->getName()) . '=';
if (empty($this->getValue())) {
- $output .= 'deleted; expires=' . gmdate('D, d-M-Y H:i:s T', time() - 31536001);
+ $output .= 'deleted; expires=' . gmdate('D, d-M-Y H:i:s T', time() + 1);
} else {
$output .= urlencode($this->getValue());
diff --git a/src/CookieCollection.php b/src/CookieCollection.php
deleted file mode 100644
index 3da52a0..0000000
--- a/src/CookieCollection.php
+++ /dev/null
@@ -1,34 +0,0 @@
-replace($cookies);
- }
-
- public function set($key, $value)
- {
- if (!$value instanceof Cookie) {
- $value = new Cookie($key, $value);
- }
-
- return parent::set($key, $value);
- }
-
- public function replace(array $cookies)
- {
- $this->clear();
-
- foreach ($cookies as $key => $value) {
- $this->set($key, $value);
- }
-
- return $this;
- }
-}
diff --git a/src/DataCollection.php b/src/DataCollection.php
deleted file mode 100644
index 89da115..0000000
--- a/src/DataCollection.php
+++ /dev/null
@@ -1,150 +0,0 @@
-data = $data;
- }
-
- public function get($key, $default = null)
- {
- if ($this->exists($key)) {
- return $this->data[$key];
- }
-
- return $default;
- }
-
- public function set($key, $value)
- {
- $this->data[$key] = $value;
-
- return $this;
- }
-
- public function exists($key)
- {
- return array_key_exists($key, $this->data);
- }
-
- public function remove($key)
- {
- if ($this->exists($key)) {
- unset($this->data[$key]);
- }
-
- return $this;
- }
-
- public function clear()
- {
- $this->data = [];
-
- return $this;
- }
-
- public function replace(array $data)
- {
- $this->data = $data;
-
- return $this;
- }
-
- public function merge(array $data, $hard = false)
- {
- if ($hard) {
- $this->data = array_replace($this->data, $data);
- } else {
- $this->data = array_merge($this->data, $data);
- }
-
- return $this;
- }
-
- public function all()
- {
- return $this->data;
- }
-
- public function keys()
- {
- return array_keys($this->data);
- }
-
- public function values()
- {
- return array_values($this->data);
- }
-
- public function map($callback, $userData = null)
- {
- array_walk_recursive($this->data, $callback, $userdata);
-
- return $this;
- }
-
- public function isEmpty()
- {
- return empty($this->data);
- }
-
- public function __get($key)
- {
- return $this->get($key);
- }
-
- public function __set($key, $value)
- {
- $this->set($key, $value);
- }
-
- public function __isset($key)
- {
- return $this->exists($key);
- }
-
- public function __unset($key)
- {
- $this->remove($key);
- }
-
- public function offsetGet($key)
- {
- return $this->get($key);
- }
-
- public function offsetSet($key, $value)
- {
- $this->set($key, $value);
- }
-
- public function offsetExists($key)
- {
- return $this->exists($key);
- }
-
- public function offsetUnset($key)
- {
- $this->remove($key);
- }
-
- public function getIterator()
- {
- return new ArrayIterator($this->data);
- }
-
- public function count()
- {
- return count($this->data);
- }
-}
diff --git a/src/FileResponse.php b/src/FileResponse.php
deleted file mode 100644
index fcbec74..0000000
--- a/src/FileResponse.php
+++ /dev/null
@@ -1,52 +0,0 @@
-setPath($path);
- }
-
- public function open()
- {
-
- }
-
- public function close()
- {
-
- }
-
- public function setPath($path)
- {
- if (!file_exists($path)) {
- throw new FileNotFoundException('');
- }
-
- $this->path = $path;
- $this->headers['Content-Type'] = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path);
- $this->headers['Content-Length'] = filesize($path);
- $this->headers['Content-Disposition'] = 'attachment; filename="' . basename($path) . '"';
-
- return $this;
- }
-
- public function send()
- {
- parent::send();
- readfile($this->path);
-
- return $this;
- }
-}
diff --git a/src/HeaderCollection.php b/src/HeaderCollection.php
deleted file mode 100644
index e4f0be8..0000000
--- a/src/HeaderCollection.php
+++ /dev/null
@@ -1,131 +0,0 @@
-setNormalization($normalization);
- $this->replace($headers);
- }
-
- public function get($key, $default = null)
- {
- $key = $this->normalize($key);
-
- return parent::get($key, $default);
- }
-
- public function set($key, $value)
- {
- $key = $this->normalize($key);
-
- return parent::set($key, $value);
- }
-
- public function exists($key)
- {
- $key = $this->normalize($key);
-
- return parent::exists($key);
- }
-
- public function remove($key)
- {
- $key = $this->normalize($key);
-
- return parent::remove($key);
- }
-
- public function replace(array $headers)
- {
- $this->clear();
-
- foreach ($headers as $key => $value) {
- $this->set($key, $value);
- }
-
- return $this;
- }
-
- public function getNormalization()
- {
- return $this->normalization;
- }
-
- public function setNormalization($normalization)
- {
- $this->normalization = (integer) $normalization;
-
- return $this;
- }
-
- public function send()
- {
- if (!headers_sent()) {
- foreach ($this->all() as $key => $value) {
- header(sprintf('%s: %s', $key, $value));
- }
- }
-
- return $this;
- }
-
- public function __toString()
- {
- $output = '';
-
- foreach ($this->all() as $key => $value) {
- $output .= sprintf("%s: %s\r\n", $key, $value);
- }
-
- return $output;
- }
-
- public function __invoke()
- {
- return $this->send();
- }
-
- protected function normalize($key)
- {
- if ($this->normalization & static::NORMALIZE_TRIM) {
- $key = trim($key);
- }
-
- if ($this->normalization & static::NORMALIZE_DELIMITERS) {
- $key = str_replace([' ', '_'], '-', $key);
- }
-
- if ($this->normalization & static::NORMALIZE_CASE) {
- $key = strtolower($key);
- }
-
- if ($this->normalization & static::NORMALIZE_CANONICAL) {
- $key = static::canonicalize($key);
- }
-
- return $key;
- }
-
- protected static function canonicalize($key)
- {
- return implode('-', array_map('ucfirst', explode('-', $key)));
- }
-}
diff --git a/src/Message.php b/src/Message.php
new file mode 100644
index 0000000..84bb98b
--- /dev/null
+++ b/src/Message.php
@@ -0,0 +1,225 @@
+
+ * @copyright Copyright (c) 2016, Nathan Bishop
+ * @package Meek\Http
+ * @license MIT
+ */
+trait Message
+{
+ /**
+ * The protocol version used by the request/response.
+ *
+ * @var string
+ */
+ private $version = '1.1';
+
+ /**
+ * The headers used by the request/response, but with the
+ * header names formatted to lower case.
+ *
+ * @var array
+ */
+ private $headers = [];
+
+ /**
+ * Mapping of normalized header names to original, user
+ * provided header names; where key is the normalized header
+ * name and value was the original header name.
+ *
+ * @var array
+ */
+ private $originalHeaderNames = [];
+
+ /**
+ * The request/response body.
+ *
+ * @var PsrHttpStream
+ */
+ private $body;
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getProtocolVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withProtocolVersion($version)
+ {
+ if (!in_array($version, ['1.0', '1.1', '2'], true)) {
+ throw new InvalidArgumentException('A valid protocol version was not provided.');
+ }
+
+ $instance = clone $this;
+ $instance->version = $version;
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeaders()
+ {
+ return $this->headers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasHeader($name)
+ {
+ return array_key_exists(strtolower($name), $this->originalHeaderNames);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeader($name)
+ {
+ if (!$this->hasHeader($name)) {
+ return [];
+ }
+
+ $original = $this->originalHeaderNames[strtolower($name)];
+ $value = $this->headers[$original];
+
+ return is_array($value) ? $value : [$value];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getHeaderLine($name)
+ {
+ return implode(', ', $this->getHeader($name));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withHeader($name, $value)
+ {
+ if (is_string($value)) {
+ $value = [$value];
+ }
+
+ if (!is_array($value) || count(array_filter($value, 'is_string')) !== count($value)) {
+ throw new InvalidArgumentException('Header value must be a string or an array of strings.');
+ }
+
+ $normalized = strtolower($name);
+ $instance = clone $this;
+
+ // remove the original header name and set to the new one
+ if ($instance->hasHeader($name)) {
+ unset($instance->headers[$instance->originalHeaderNames[$normalized]]);
+ }
+
+ $instance->headers[$name] = $value;
+ $instance->originalHeaderNames[$normalized] = $name;
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withAddedHeader($name, $value)
+ {
+ if (is_string($value)) {
+ $value = [$value];
+ }
+
+ if (!is_array($value) || count(array_filter($value, 'is_string')) !== count($value)) {
+ throw new InvalidArgumentException('Header value must be a string or an array of strings.');
+ }
+
+ if (!$this->hasHeader($name)) {
+ return $this->withHeader($name, $value);
+ }
+
+ $original = $this->originalHeaderNames[strtolower($name)];
+ $instance = clone $this;
+ $instance->headers[$original] = array_merge($this->headers[$original], $value);
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withoutHeader($name)
+ {
+ if (!$this->hasHeader($name)) {
+ return clone $this;
+ }
+
+ $name = strtolower($name);
+ $instance = clone $this;
+ unset($instance->headers[$this->originalHeaderNames[$name]]);
+ unset($instance->originalHeaderNames[$name]);
+
+ return $instance;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getBody()
+ {
+ return $this->body;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withBody(PsrHttpStream $body)
+ {
+ $instance = clone $this;
+ $instance->body = $body;
+
+ return $this;
+ }
+
+ /**
+ * Allows for bulk-setting the headers without worring about
+ * immutability. Should only be used during first time class
+ * instantiation.
+ *
+ * @param array $headers An associative array of the message's
+ * headers. Each key must be a header
+ * name, and each value can either be a
+ * string for the header or an array of
+ * strings for the header.
+ */
+ private function setHeaders(array $headers)
+ {
+ $this->headers = [];
+ $this->originalHeaderNames = [];
+
+ foreach ($headers as $name => $values) {
+ if (!is_array($values)) {
+ $values = [$values];
+ }
+
+ foreach ($values as $value) {
+ $this->headers[$name][] = $value;
+ $this->originalHeaderNames[strtolower($name)] = $name;
+ }
+ }
+ }
+}
diff --git a/src/Request.php b/src/Request.php
index c41605b..5fe94f7 100644
--- a/src/Request.php
+++ b/src/Request.php
@@ -2,6 +2,9 @@
namespace Meek\Http;
+use Meek\Http\Message;
+use Psr\Http\Message\RequestInterface as PsrHttpRequest;
+use Psr\Http\Message\UriInterface as PsrHttpUri;
use Meek\Http\DataCollection;
use Meek\Http\CookieCollection;
use Meek\Http\Collections\ServerData as ServerDataCollection;
@@ -9,245 +12,95 @@
use Meek\Http\Collections\FileList as FileListCollection;
use Meek\Http\Uri;
use Meek\Http\Session;
+use InvalidArgumentException;
-class Request
+class Request implements PsrHttpRequest
{
- private $get;
- private $post;
- private $cookies;
- public $server;
- private $headers;
- private $files;
- private $body;
- private $attributes;
- private $uri;
- public $session;
+ use Message;
- /**
- * [__construct description]
- * @param array $get [description]
- * @param array $post [description]
- * @param array $cookies [description]
- * @param array $server [description]
- * @param array $files [description]
- * @param string $body [description]
- */
- public function __construct(
- array $get = [],
- array $post = [],
- array $cookies = [],
- array $server = [],
- array $files = [],
- $body = null
- ) {
- $this->get = new DataCollection($get);
- $this->post = new DataCollection($post);
- $this->cookies = new CookieCollection($cookies);
- $this->server = new ServerDataCollection($server);
- $this->headers = new HeaderCollection($this->server->getHeaders());
- $this->files = new FileListCollection($files);
- $this->body = $body;
- $this->attributes = new DataCollection();
- }
+ protected $uri;
+ protected $method;
+ protected $protocol;
+ protected $requestTarget;
- /**
- * [create description]
- * @param string $uri [description]
- * @param string $method [description]
- * @param array $cookies [description]
- * @param array $files [description]
- * @param array $server [description]
- * @param string $body [description]
- * @return [type] [description]
- */
- public static function create(
- $uri,
- $method = 'GET',
- array $cookies = [],
- array $files = [],
- array $server = [],
- $body = null
- ) {
-
- }
-
- /**
- * [createFromGlobals description]
- * @return Request [description]
- */
- public static function createFromGlobals()
+ public function __construct($uri, $method = 'GET', $headers = [], $body = '', $protocol = '1.1')
{
- return new static($_GET, $_POST, $_COOKIE, $_SERVER, $_FILES, null);
- }
+ $this->uri = $uri instanceof $uri ? $uri : new Uri($uri);
+ $this->method = $method;
+ $this->setHeaders($headers);
+ $this->body = $body instanceof PsrHttpStream ? $body : new Stream('php://temp', 'w+');
+ $this->protocol = $protocol;
- /**
- * [getUri description]
- * @return Uri [description]
- */
- public function getUri()
- {
- // cache URI
- if (is_null($this->uri)) {
- $this->uri = Uri::createFromRequest();
- }
+ // set request target
+ $path = $this->uri->getPath();
- return $this->uri;
- }
-
- /**
- * [getMethod description]
- * @return string [description]
- */
- public function getMethod()
- {
- return array_key_exists('REQUEST_METHOD', $_SERVER) ? $_SERVER['REQUEST_METHOD'] : 'GET';
- }
-
- /**
- * [getParam description]
- * @param string $key [description]
- * @param mixed $default [description]
- * @return mixed [description]
- */
- public function getParam($key, $default = null)
- {
- // check query parameters
- if (array_key_exists($key, $this->get)) {
- return $this->get[$key];
-
- // check request
- } else if (array_key_exists($key, $this->post)) {
- return $this->post[$key];
-
- // finally, check user set data
- } else if (array_key_exists($key, $this->attributes)) {
- return $this->attributes[$key];
+ if (empty($path)) {
+ $path = '/';
}
- return $default;
- }
+ $query = $this->uri->getQuery();
- /**
- * [setSession description]
- * @param Session $session [description]
- */
- public function setSession(Session $session)
- {
- $this->session = $session;
-
- return $this;
- }
-
- /**
- * [getQueryParams description]
- * @return DataCollection [description]
- */
- public function getQueryParams()
- {
- return $this->get;
- }
+ if (!empty($query)) {
+ $path = $path . '?' . $query;
+ }
- /**
- * [getRequestParams description]
- * @return DataCollection [description]
- */
- public function getRequestParams()
- {
- return $this->post;
+ $this->requestTarget = $path;
}
/**
- * [getCookies description]
- * @return CookieCollection [description]
+ * {@inheritdoc}
*/
- public function getCookies()
+ public function getRequestTarget()
{
- return $this->cookies;
+ return $this->requestTarget;
}
/**
- * [getServer description]
- * @return ServerCollection [description]
+ * {@inheritdoc}
*/
- public function getServer()
+ public function withRequestTarget($requestTarget)
{
- return $this->server;
- }
+ $request = clone $this;
+ $request->requestTarget = $requestTarget;
- /**
- * [getHeaders description]
- * @return HeaderCollection [description]
- */
- public function getHeaders()
- {
- return $this->headers;
+ return $request;
}
/**
- * [getFiles description]
- * @return FileCollection [description]
+ * {@inheritdoc}
*/
- public function getFiles()
+ public function getMethod()
{
- return $this->files;
+ return $this->method;
}
/**
- * [getBody description]
- * @return [type] [description]
+ * {@inheritdoc}
*/
- public function getBody()
+ public function withMethod($method)
{
- // cache body
- if (is_null($this->body)) {
- $this->body = file_get_contents('php://input');
- }
-
- return $this->body;
- }
+ $request = clone $this;
+ $request->method = $method;
- /**
- * [__get description]
- * @param [type] $key [description]
- * @return [type] [description]
- */
- public function __get($key)
- {
- return $this->__isset($key) ? $this->attributes[$key] : null;
+ return $request;
}
/**
- * [__set description]
- * @param [type] $key [description]
- * @param [type] $value [description]
+ * {@inheritdoc}
*/
- public function __set($key, $value)
+ public function getUri()
{
- if ($key === null) {
- throw new InvalidArgumentException('A key was not provided.');
- }
-
- $this->attributes[$key] = $value;
+ return $this->uri;
}
/**
- * [__isset description]
- * @param [type] $key [description]
- * @return boolean [description]
+ * {@inheritdoc}
*/
- public function __isset($key)
+ public function withUri(PsrHttpUri $uri, $preserveHost = false)
{
- return array_key_exists($key, $this->attributes);
- }
+ $request = clone $this;
+ $request->$uri = $uri;
- /**
- * [__unset description]
- * @param [type] $key [description]
- */
- public function __unset($key)
- {
- if ($this->__isset($key)) {
- unset($this->attributes[$key]);
- }
+ return $request;
}
}
diff --git a/src/Response.php b/src/Response.php
index 2a2980a..4141d3d 100644
--- a/src/Response.php
+++ b/src/Response.php
@@ -2,158 +2,276 @@
namespace Meek\Http;
-use Meek\Http\HeaderCollection;
+use Psr\Http\Message\ResponseInterface as PsrHttpResponse;
+use Psr\Http\Message\StreamInterface as PsrHttpStream;
+use Meek\Http\Message;
+use Meek\Http\Stream;
use Meek\Http\Status;
+
use Meek\Http\Request;
use InvalidArgumentException;
+use RuntimeException;
+use Psr\Http\Message\ServerRequestInterface as PsrServerRequest;
-class Response
+class Response implements PsrHttpResponse
{
- protected $status;
- private $sent = false;
- private $body;
- public $headers;
- protected $cookies;
- private $protocol = '1.1';
- private $charset = 'utf-8';
- public $session;
-
- public function __construct($body = '', $status = 200, array $headers = [])
- {
- $this->headers = new HeaderCollection($headers);
- $this->setBody($body);
+ use Message;
+
+ /**
+ * [$status description]
+ *
+ * @var [type]
+ */
+ private $status;
+
+ /**
+ * [$charset description]
+ *
+ * @var [type]
+ */
+ private $charset;
+
+ /**
+ * [__construct description]
+ *
+ * @param string $body [description]
+ * @param integer $status [description]
+ * @param array $headers [description]
+ */
+ public function __construct(PsrHttpStream $body = null, $status = 200, array $headers = [])
+ {
+ $this->body = $body ?: new Stream('php://temp', 'wb+');
$this->setStatus($status);
+ $this->setHeaders($headers);
}
- public function setBody($body, $contentType = 'text/html')
+ /**
+ * {@inheritdoc}
+ */
+ public function getStatusCode()
{
- if ($body === null) {
- throw new InvalidArgumentException('Please provide a body!');
- }
-
- $this->headers['Content-Type'] = "$contentType; charset=$this->charset";
- $this->body = (string) $body;
-
- return $this;
+ return $this->status->getCode();
}
- public function setStatus($code, $message = null)
+ /**
+ * {@inheritdoc}
+ */
+ public function withStatus($code, $reasonPhrase = '')
{
- if (!($code instanceof Status)) {
- $code = new Status($code, $message);
- } else if ($message !== null) {
- $code->setMessage($message);
- }
+ $instance = clone $this;
+ $instance->setStatus($code, $reasonPhrase);
- $this->status = $code;
-
- return $this;
+ return $instance;
}
- public function setCharset($charset)
+ /**
+ * {@inheritdoc}
+ */
+ public function getReasonPhrase()
{
- $this->charset = $charset;
-
- return $this;
+ return $this->status->getMessage();
}
- public function setProtocol($protocol)
+ /**
+ * https://github.com/symfony/http-foundation/blob/master/Response.php#L250
+ * @param PsrServerRequest $request [description]
+ * @return self
+ */
+ public function prepare(PsrServerRequest $request)
{
- $this->protocol = $protocol;
+ $response = clone $this;
- return $this;
- }
+ if ($response->status->isInformational() || in_array($response->getStatusCode(), [204, 304])) {
+ $response = $respone->withBody(new Stream('php://temp'))
+ ->withoutHeader('Content-Type')
+ ->withoutHeader('Content-Length');
+ } else {
+ // set content type based on request, if we haven't provided one
+ if (!$response->hasHeader('Content-Type')) {
- public function getStatus()
- {
- return $this->status;
- }
+ }
- public function getBody()
- {
- return $this->body;
- }
+ // fix content type
+ $charset = $response->charset ?: 'UTF-8';
+ if (!$response->hasHeader('Content-Type')) {
+ $response = $response->withHeader('Content-Type', sprintf('text/html; charset=%s', $charset));
+ } elseif (stripos($response->getHeaderLine('Content-Type'), 'text/') === 0 &&
+ stripos($response->getHeaderLine('Content-Type'), 'charset') === false
+ ) {
+ $value = sprintf('%s; charset=%s', $response->getHeaderLine('Content-Type'), $charset);
+ $response = $response->withHeader('Content-Type', $value);
+ }
- public function prepare(Request $request)
- {
- return $this;
+ // fix content length
+ if ($response->hasHeader('Transfer-Encoding')) {
+ $response = $response->withoutHeader('Content-Length');
+ }
+
+ if (!$response->hasHeader('Content-Length')) {
+ $response->withHeader('Content-Length', (string) $this->getBody()->getSize());
+ }
+
+ // fix HEAD requests
+ if ($request->getMethod() === 'HEAD') {
+ // make sure content length is specified
+ if (!$response->hasHeader('Content-Length')) {
+ $response = $response->withHeader('Content-Length', $response->getBody()->getSize());
+ }
+
+ // body should be empty
+ $response = $respone->withBody(new Stream('php://temp'));
+ }
+ }
+
+ // fix protocol
+ if ($request->getProtocolVersion() !== '1.0') {
+ $response = $response->withProtocolVersion('1.1');
+ }
+
+ // check if we need to send extra expire info headers
+ if ($response->getProtocolVersion() === '1.0' &&
+ in_array('no-cache', $response->getHeader('Cache-Control'))
+ ) {
+ $response = $response->withAddedHeader('Pragma', 'no-cache')
+ ->withAddedHeader('Expires', -1);
+ }
+
+ return $response;
}
+ /**
+ * [send description]
+ */
public function send()
{
- $this->sendHeaders();
- $this->sendBody();
-
- if (function_exists('fastcgi_finish_request')) {
- fastcgi_finish_request();
- } else if ('cli' !== PHP_SAPI) {
- static::closeOutputBuffers(0, true);
+ if (headers_sent()) {
+ throw new RuntimeException('Headers have already been sent.');
}
- return $this;
+ $this->sendStatusLine();
+ $this->sendHeaders();
+ static::flush();
+ $this->sendBody();
}
+ /**
+ * [__toString description]
+ *
+ * @return string [description]
+ */
public function __toString()
{
$output = '';
$output .= $this->getHttpStatusLine() . "\r\n";
- $output .= (string) $this->headers;
+
+ foreach (array_keys($this->getHeaders()) as $header) {
+ $output .= sprintf("%s: %s\r\n", static::normalize($header), $this->getHeaderLine($header));
+ }
+
$output .= "\r\n";
$output .= $this->body;
return $output;
}
+ /**
+ * @see self::send
+ */
public function __invoke()
{
$this->send();
}
+ /**
+ * [__clone description]
+ */
public function __clone()
{
- $this->headers = clone $this->headers;
+ $this->status = clone $this->status;
+ $this->body = clone $this->body;
}
- // borrowed from Symfony's Http component.
- public static function closeOutputBuffers($targetLevel, $flush)
+ protected function getHttpStatusLine()
{
- $status = ob_get_status(true);
- $level = count($status);
- // PHP_OUTPUT_HANDLER_* are not defined on HHVM 3.3
- $flags = defined('PHP_OUTPUT_HANDLER_REMOVABLE') ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
- while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || $flags === ($s['flags'] & $flags) : $s['del'])) {
- if ($flush) {
- ob_end_flush();
- } else {
- ob_end_clean();
- }
- }
+ return sprintf('HTTP/%s %s', $this->getProtocolVersion(), $this->status);
}
- protected function getHttpStatusLine()
+ /**
+ * [sendStatusLine description]
+ */
+ protected function sendStatusLine()
{
- return sprintf('HTTP/%s %s', $this->protocol, $this->status);
+ header($this->getHttpStatusLine());
}
+ /**
+ * [sendHeaders description]
+ */
protected function sendHeaders()
{
- header($this->getHttpStatusLine());
+ if (!$this->hasHeader('Content-Length')) {
+ $this->setHeaders([
+ 'Content-Length' => $this->getBody()->getSize()
+ ]);
+ }
+
+ foreach (array_keys($this->getHeaders()) as $header) {
+ header(sprintf('%s: %s', static::normalize($header), $this->getHeaderLine($header)));
+ }
+ }
+
+ /**
+ * [sendBody description]
+ */
+ protected function sendBody()
+ {
+ echo $this->getBody();
+ }
- $this->headers->send();
+ /**
+ * [setStatus description]
+ *
+ * @param [type] $code [description]
+ * @param [type] $message [description]
+ */
+ private function setStatus($code, $message = null)
+ {
+ if (!($code instanceof Status)) {
+ $code = new Status($code, $message);
+ }
- $this->sendCookies();
+ if ($message !== null) {
+ $code->setMessage($message);
+ }
- return $this;
+ $this->status = $code;
}
- protected function sendCookies()
+ /**
+ * [flush description]
+ *
+ * @param [type] $level [description]
+ */
+ private static function flush($level = null)
{
- //$this->cookies->send();
+ if ($level === null) {
+ $level = ob_get_level();
+ }
+
+ while (ob_get_level() > $level) {
+ ob_end_flush();
+ }
}
- protected function sendBody()
+ /**
+ * [normalize description]
+ *
+ * @param [type] $header [description]
+ * @return [type] [description]
+ */
+ private static function normalize($header)
{
- echo $this->body;
+ return str_replace(' ', '-', ucwords(strtolower(str_replace('-', ' ', $header))));
}
}
diff --git a/src/JsonResponse.php b/src/Response/Json.php
similarity index 100%
rename from src/JsonResponse.php
rename to src/Response/Json.php
diff --git a/src/RedirectedResponse.php b/src/Response/Redirect.php
similarity index 100%
rename from src/RedirectedResponse.php
rename to src/Response/Redirect.php
diff --git a/src/ServerRequest.php b/src/ServerRequest.php
new file mode 100644
index 0000000..591dcb5
--- /dev/null
+++ b/src/ServerRequest.php
@@ -0,0 +1,226 @@
+server = $server;
+ $this->query = [];
+ $this->post = [];
+ $this->cookies = [];
+ $this->files = [];
+ $this->attributes = [];
+
+ parent::__construct($uri, $method, $headers, $body, $protocol);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getServerParams()
+ {
+ return $this->server;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCookieParams()
+ {
+ return $this->cookies;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withCookieParams(array $cookies)
+ {
+ $request = clone $this;
+
+ array_merge($request->cookies, $cookies);
+
+ return $request;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQueryParams()
+ {
+ return $this->query;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withQueryParams(array $query)
+ {
+ $request = clone $this;
+
+ array_merge($request->query, $query);
+
+ return $request;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getUploadedFiles()
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withUploadedFiles(array $files)
+ {
+ $request = clone $this;
+
+ array_merge($request->files, $files);
+
+ return $request;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getParsedBody()
+ {
+ return $this->post;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withParsedBody($data)
+ {
+ $request = clone $this;
+
+ $request->data = $data;
+
+ return $request;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttributes()
+ {
+ $this->attributes;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAttribute($name, $default = null)
+ {
+ return self::arrayGet($this->attributes, $name, $default);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withAttribute($name, $value)
+ {
+ $request = clone $this;
+ $request->attributes[$name] = $value;
+
+ return $request;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function withoutAttribute($name)
+ {
+ $request = clone $this;
+
+ unset($request->attributes[$name]);
+
+ return $request;
+ }
+
+ /**
+ * [createFromGlobals description]
+ *
+ * @return self
+ */
+ public static function createFromGlobals()
+ {
+ $method = self::arrayGet($_SERVER, 'REQUEST_METHOD', 'GET');
+ $headers = function_exists('getallheaders') ? getallheaders() : [];
+ $uri = Uri::createFromRequest();
+ $body = new Stream('php://input', 'r+');
+ $protocol = str_replace('HTTP/', '', self::arrayGet($_SERVER, 'SERVER_PROTOCOL', '1.1'));
+ $request = new static($uri, $method, $headers, $body, $protocol, $_SERVER);
+
+ return $request->withCookieParams($_COOKIE)
+ ->withQueryParams($_GET)
+ ->withParsedBody(!empty($_POST) ? $_POST : null)
+ ->withUploadedFiles($_FILES);
+ }
+
+ private static function arrayGet(array $data, $key, $default = null)
+ {
+ return array_key_exists($key, $data) ? $data[$key] : $default;
+ }
+}
diff --git a/src/Status.php b/src/Status.php
index a266d14..4275268 100644
--- a/src/Status.php
+++ b/src/Status.php
@@ -108,7 +108,7 @@ public function setCode($code)
*/
public function getMessage()
{
- return $this->message;
+ return (string) $this->message;
}
/**
diff --git a/src/Stream.php b/src/Stream.php
new file mode 100644
index 0000000..5dd3277
--- /dev/null
+++ b/src/Stream.php
@@ -0,0 +1,277 @@
+open($resource, $mode);
+ }
+
+ /**
+ * [open description]
+ *
+ * @param string|resource $resource [description]
+ * @param string $mode [description]
+ */
+ public function open($resource, $mode = 'r')
+ {
+ if (is_string($resource)) {
+ $resource = @fopen($resource, $mode);
+ }
+
+ if (!is_resource($resource) || get_resource_type($resource) !== 'stream') {
+ throw new InvalidArgumentException('A valid resource URI or a stream resource was not provided.');
+ }
+
+ $this->resource = $resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ if (is_resource($this->resource)) {
+ $reource = $this->detach();
+ fclose($resource);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function detach()
+ {
+ $resource = $this->resource;
+ $this->resource = null;
+
+ return $resource;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getSize()
+ {
+ if (!is_resource($this->resource)) {
+ return null;
+ }
+
+ return fstat($this->resource)['size'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function tell()
+ {
+ $this->requireResource();
+
+ $result = ftell($this->resource);
+
+ if (!is_integer($result)) {
+ throw new RuntimeException('An unknown error has occured while trying to tell.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function eof()
+ {
+ if (!is_resource($this->resource)) {
+ return true;
+ }
+
+ return feof($this->resource);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isSeekable()
+ {
+ if (!is_resource($this->resource)) {
+ return false;
+ }
+
+ return stream_get_meta_data($this->resource)['seekable'];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function seek($offset, $whence = SEEK_SET)
+ {
+ $this->requireResource();
+
+ if (!$this->isSeekable()) {
+ throw new RuntimeException('The resource is not seekable');
+ }
+
+ $result = fseek($this->resource, $offset, $whence);
+
+ if ($result !== 0) {
+ throw new RuntimeException('An unknown error has occured while trying to seek within the stream.');
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function rewind()
+ {
+ return $this->seek(0);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isWritable()
+ {
+ if (!is_resource($this->resource)) {
+ return false;
+ }
+
+ $mode = stream_get_meta_data($this->resource)['mode'];
+
+ // maybe preg_match??
+ // return preg_match('/[xwca\+]{1}/', $mode);
+ return strstr($mode, 'x') || strstr($mode, 'w')
+ || strstr($mode, 'c') || strstr($mode, 'a') || strstr($mode, '+');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function write($string)
+ {
+ $this->requireResource();
+
+ if (!$this->isWritable()) {
+ throw new RuntimeException('The stream is not writeable.');
+ }
+
+ $result = fwrite($this->resource, $string);
+
+ if ($result === false) {
+ throw new RuntimeException('An unknown error has occured while trying to write to the stream.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isReadable()
+ {
+ if (!is_resource($this->resource)) {
+ return false;
+ }
+
+ $mode = stream_get_meta_data($this->resource)['mode'];
+
+ return strstr($mode, 'r') || strstr($mode, '+');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function read($length)
+ {
+ $this->requireResource();
+ $this->requireReadable();
+
+ $result = fread($this->resource, $length);
+
+ if ($result === false) {
+ throw new RuntimeException('An unknown error has occured while trying to read from the stream.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getContents()
+ {
+ $this->requireReadable();
+
+ $result = stream_get_contents($this->resource);
+
+ if ($result === false) {
+ throw new RuntimeException('An unknown error has occured while attempting to get the stream contents.');
+ }
+
+ return $result;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMetadata($key = null)
+ {
+ $metadata = stream_get_meta_data($this->resource);
+
+ if ($key === null) {
+ return $metadata;
+ }
+
+ return array_key_exists($key, $metadata) ? $metadata[$key] : null;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
+ {
+ if (!$this->isReadable()) {
+ return '';
+ }
+
+ try {
+ $this->rewind();
+ return $this->getContents();
+ } catch (RuntimeException $e) {
+ return '';
+ }
+ }
+
+ /**
+ * [requireResource description]
+ */
+ private function requireResource()
+ {
+ if (!is_resource($this->resource)) {
+ throw new RuntimeException('The resource has either been detached or closed.');
+ }
+ }
+
+ /**
+ * [requireReadable description]
+ */
+ private function requireReadable()
+ {
+ if (!$this->isReadable()) {
+ throw new RuntimeException('The stream is not readable.');
+ }
+ }
+}
diff --git a/src/UploadedFile.php b/src/UploadedFile.php
new file mode 100644
index 0000000..a98e4a6
--- /dev/null
+++ b/src/UploadedFile.php
@@ -0,0 +1,136 @@
+
- * @copyright Copyright (c) 2016, Nathan Bishop
- * @package Meek\Http
- * @version 0.8.7
- * @license MIT
- */
-class Uri
+use Psr\Http\Message\UriInterface as PsrHttpUri;
+
+ /**
+ *A class for manipulating URI's.
+ *
+ * @version 0.1.0
+ * @author Nathan Bishop (nbish11)
+ * @copyright 2016 Nathan Bishop
+ * @license MIT
+ */
+class Uri implements PsrHttpUri
{
- private $scheme;
- private $userInfo;
- private $host;
- private $port;
- private $path;
- private $query;
- private $fragment;
-
- public function __construct($uri = null)
- {
- if (!is_array($uri)) {
- $uri = static::parse((string) $uri);
- }
-
- $parsedUri = array_fill_keys([
- 'scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment'
- ], '');
-
- $uri = array_merge($parsedUri, $uri);
-
- $this->setScheme($uri['scheme']);
- $this->setUserInfo($uri['user'], $uri['pass']);
- $this->setHost($uri['host']);
- $this->setPort($uri['port'] === '' ? null : (integer) $uri['port']);
- $this->setPath($uri['path']);
- $this->setQuery($uri['query']);
- $this->setFragment($uri['fragment']);
- }
-
+ protected $scheme;
+ protected $userInfo;
+ protected $host;
+ protected $port;
+ protected $path;
+ protected $query;
+ protected $fragment;
+
+ protected static $allowedSchemes = [];
+
+ /**
+ * {@inheritdoc}
+ */
public function getScheme()
{
return (string) $this->scheme;
}
+ /**
+ * {@inheritdoc}
+ */
public function getAuthority()
{
$authority = '';
@@ -64,16 +52,25 @@ public function getAuthority()
return $authority;
}
+ /**
+ * {@inheritdoc}
+ */
public function getUserInfo()
{
return (string) $this->userInfo;
}
+ /**
+ * {@inheritdoc}
+ */
public function getHost()
{
return (string) $this->host;
}
+ /**
+ * {@inheritdoc}
+ */
public function getPort()
{
// If a port is present, and it is non-standard for the current scheme,
@@ -84,96 +81,217 @@ public function getPort()
return null;
}
- return $this->port;
+ return (integer) $this->port;
}
+ /**
+ * {@inheritdoc}
+ */
public function getPath()
{
return (string) $this->path;
}
+ /**
+ * {@inheritdoc}
+ */
public function getQuery()
{
return (string) $this->query;
}
+ /**
+ * {@inheritdoc}
+ */
public function getFragment()
{
return (string) $this->fragment;
}
- public function setScheme($scheme)
+ /**
+ * {@inheritdoc}
+ */
+ public function withScheme($scheme)
{
- $this->scheme = $scheme;
+ $instance = clone $this;
+ $instance->scheme = $scheme;
- return $this;
+ return $instance;
}
- public function setUserInfo($user, $pass = null)
+ /**
+ * {@inheritdoc}
+ */
+ public function withUserInfo($user, $pass = null)
{
+ $instance = clone $this;
+
if ($pass) {
$user = $user . ':' . $pass;
}
- $this->userInfo = $user;
+ $instance->userInfo = $user;
- return $this;
+ return $instance;
}
- public function setHost($host)
+ /**
+ * {@inheritdoc}
+ */
+ public function withHost($host)
{
- $this->host = $host;
+ $instance = clone $this;
+ $instance->host = $host;
- return $this;
+ return $instance;
}
- public function setPort($port)
+ /**
+ * {@inheritdoc}
+ */
+ public function withPort($port)
{
- $this->port = $port;
+ $instance = clone $this;
+ $instance->port = $port;
- return $this;
+ return $instance;
}
- public function setPath($path)
+ /**
+ * {@inheritdoc}
+ */
+ public function withPath($path)
{
- $this->path = $path;
+ $instance = clone $this;
+ $instance->path = $path;
- return $this;
+ return $instance;
}
- public function setQuery($query)
+ /**
+ * {@inheritdoc}
+ */
+ public function withQuery($query)
{
- $this->query = $query;
+ $instance = clone $this;
+ $instance->query = $query;
- return $this;
+ return $instance;
}
- public function setFragment($fragment)
+ /**
+ * {@inheritdoc}
+ */
+ public function withFragment($fragment)
{
- $this->fragment = $fragment;
+ $instance = clone $this;
+ $instance->fragment = $fragment;
- return $this;
+ return $instance;
}
- public static function createFromRequest()
+ /**
+ * {@inheritdoc}
+ */
+ public function __toString()
{
- return new static([
- 'scheme' => $_SERVER['REQUEST_SCHEME'],
- 'host' => $_SERVER['SERVER_NAME'],
- 'port' => $_SERVER['SERVER_PORT'],
- 'user' => isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : '',
- 'pass' => isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '',
- 'path' => strstr($_SERVER['REQUEST_URI'], '?', true) ?: $_SERVER['REQUEST_URI'],
- 'query' => $_SERVER['QUERY_STRING']
+ return static::build([
+ 'scheme' => $this->getScheme(),
+ 'authority' => $this->getAuthority(),
+ 'path' => $this->getPath(),
+ 'query' => $this->getQuery(),
+ 'fragment' => $this->getFragment()
+ ]);
+ }
+
+ /**
+ * [createFromRequest description]
+ *
+ * @param array $server [description]
+ * @return self [description]
+ */
+ public static function createFromRequest(array $server)
+ {
+ return static::createFromArray([
+ 'scheme' => $server['REQUEST_SCHEME'],
+ 'host' => $server['SERVER_NAME'],
+ 'port' => $server['SERVER_PORT'],
+ 'user' => isset($server['PHP_AUTH_USER']) ? $server['PHP_AUTH_USER'] : '',
+ 'pass' => isset($server['PHP_AUTH_PW']) ? $server['PHP_AUTH_PW'] : '',
+ 'path' => strstr($server['REQUEST_URI'], '?', true) ?: $server['REQUEST_URI'],
+ 'query' => $server['QUERY_STRING']
]);
}
+ /**
+ * [createFromString description]
+ *
+ * @param string $uri [description]
+ * @return self [description]
+ */
+ public static function createFromString($uri)
+ {
+ return static::createFromArray(static::parse($uri));
+ }
+
+ /**
+ * [createFromArray description]
+ *
+ * @param array $uri [description]
+ * @return self [description]
+ */
+ public static function createFromArray(array $uri)
+ {
+ $uri += [
+ 'scheme' => '',
+ 'user' => '',
+ 'pass' => '',
+ 'host' => '',
+ 'port' => null,
+ 'query' => '',
+ 'fragment' => ''
+ ];
+
+ return (new static())
+ ->withScheme($uri['scheme'])
+ ->withUserInfo($uri['user'], $uri['pass'])
+ ->withHost($uri['host'])
+ ->withPort($uri['port'])
+ ->withPath($uri['path'])
+ ->withQuery($uri['query'])
+ ->withFragment($uri['fragment']);
+ }
+
+ /**
+ * [parse description]
+ *
+ * @param string $uri [description]
+ * @return array [description]
+ */
protected static function parse($uri)
{
- return parse_url($uri);
+ $uri = (string) $uri;
+
+ if (!is_string($uri) || empty($uri)) {
+ throw new InvalidArgumentException('Invalid uri.');
+ }
+
+ $uri = parse_url((string) $uri);
+
+ if ($uri === false) {
+ throw new InvalidArgumentException('Malformed uri.');
+ }
+
+ return $uri;
}
- // http://tools.ietf.org/html/rfc3986#section-5.3
+ /**
+ * [compile description]
+ *
+ * @link http://tools.ietf.org/html/rfc3986#section-5.3
+ * @param array $parts [description]
+ * @return string [description]
+ */
protected static function compile(array $parts)
{
$result = '';
@@ -203,6 +321,12 @@ protected static function compile(array $parts)
return $result;
}
+ /**
+ * [build description]
+ *
+ * @param array $parsedUri [description]
+ * @return string [description]
+ */
protected static function build(array $parsedUri)
{
$uri = '';
@@ -240,15 +364,4 @@ protected static function build(array $parsedUri)
return $uri;
}
-
- public function __toString()
- {
- return static::build([
- 'scheme' => $this->getScheme(),
- 'authority' => $this->getAuthority(),
- 'path' => $this->getPath(),
- 'query' => $this->getQuery(),
- 'fragment' => $this->getFragment()
- ]);
- }
}
diff --git a/tests/ClientTest.php b/tests/ClientTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/Collections/FileListTest.php b/tests/Collections/FileListTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/Collections/ServerDataTest.php b/tests/Collections/ServerDataTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/CookieCollectionTest.php b/tests/CookieCollectionTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/CookieTest.php b/tests/CookieTest.php
index e69de29..965e156 100644
--- a/tests/CookieTest.php
+++ b/tests/CookieTest.php
@@ -0,0 +1,8 @@
+message = $this->getMockForTrait(Message::class);
+ }
+
+ /**
+ * @covers Meek\Http\Message::getProtocolVersion
+ */
+ public function testHasDefaultProtocol()
+ {
+ $this->assertEquals('1.1', $this->message->getProtocolVersion());
+ }
+
+ /**
+ * @covers Meek\Http\Message::withProtocolVersion
+ * @dataProvider invalidProtocolVersions
+ * @expectedException InvalidArgumentException
+ */
+ public function testThrowsErrorWithIncorrectProtocolVersionOrTypes($invalidVersion)
+ {
+ $message = $this->message->withProtocolVersion($invalidVersion);
+ }
+
+ /**
+ * @covers Meek\Http\Message::getProtocolVersion
+ * @covers Meek\Http\Message::withProtocolVersion
+ */
+ public function testChangingProtocolKeepsMessageImmuttable()
+ {
+ $message = $this->message->withProtocolVersion('1.0');
+
+ $this->assertNotSame($this->message, $message);
+ $this->assertEquals('1.1', $this->message->getProtocolVersion());
+ $this->assertEquals('1.0', $message->getProtocolVersion());
+ }
+
+ /**
+ * @covers Meek\Http\Message::getHeaders
+ */
+ public function testDefaultsToNoHeaders()
+ {
+ $this->assertEmpty($this->message->getHeaders());
+ }
+
+ /**
+ * @covers Meek\Http\Message::hasHeader
+ * @dataProvider differentCases
+ */
+ public function testHeaderExists($name)
+ {
+ $message = $this->message->withHeader('Foo', 'bar');
+
+ $this->assertTrue($message->hasHeader($name));
+ }
+
+ /**
+ * @covers Meek\Http\Message::hasHeader
+ * @dataProvider differentCases
+ */
+ public function testHeaderDoesNotExist($name)
+ {
+ $this->assertFalse($this->message->hasHeader($name));
+ }
+
+ /**
+ * @covers Meek\Http\Message::getHeader
+ * @dataProvider differentCases
+ */
+ public function testRetrievingANonExistantHeaderReturnsAnEmptyArray($name)
+ {
+ $value = $this->message->getHeader($name);
+
+ $this->assertInternalType('array', $value);
+ $this->assertEmpty($value);
+ }
+
+ /**
+ * @covers Meek\Http\Message::getHeader
+ * @dataProvider differentCases
+ */
+ public function testRetrievingHeaderReturnsCorrectValue($name)
+ {
+ $message = $this->message->withHeader('Foo', 'bar');
+ $value = $message->getHeader($name);
+
+ $this->assertInternalType('array', $value);
+ $this->assertCount(1, $value);
+ $this->assertEquals('bar', $value[0]);
+ }
+
+ /**
+ * @covers Meek\Http\Message::getHeader
+ * @dataProvider differentCases
+ */
+ public function testRetrievingHeaderReturnsCorrectValues($name)
+ {
+ $message = $this->message
+ ->withHeader('Foo', 'bar')
+ ->withAddedHeader('Foo', 'baz');
+ $value = $message->getHeader($name);
+
+ $this->assertCount(2, $value);
+ $this->assertEquals('bar', $value[0]);
+ $this->assertEquals('baz', $value[1]);
+ }
+
+ /**
+ * @covers Meek\Http\Message::getHeaderLine
+ * @dataProvider differentCases
+ */
+ public function testBuildsAnEmptyValueIfHeaderDoesNotExist($name)
+ {
+ $value = $this->message->getHeaderLine($name);
+
+ $this->assertInternalType('string', $value);
+ $this->assertEmpty($value);
+ }
+
+ /**
+ * @covers Meek\Http\Message::getHeaderLine
+ * @dataProvider differentCases
+ */
+ public function testBuildsValueIfOnlyOneValue($name)
+ {
+ $message = $this->message->withHeader('Foo', 'bar');
+ $value = $message->getHeaderLine($name);
+
+ $this->assertInternalType('string', $value);
+ $this->assertEquals('bar', $value);
+ }
+
+ /**
+ * @covers Meek\Http\Message::getHeaderLine
+ * @dataProvider differentCases
+ */
+ public function testBuildsCommaSeparatedListIfMoreThanOneValue($name)
+ {
+ $message = $this->message
+ ->withHeader('Foo', 'bar')
+ ->withAddedHeader('Foo', 'baz');
+ $value = $message->getHeaderLine($name);
+
+ $this->assertEquals('bar, baz', $value);
+ }
+
+ /**
+ * @covers Meek\Http\Message::withHeader
+ * @dataProvider invalidDataTypes
+ * @expectedException InvalidArgumentException
+ */
+ public function testThowsErrorWhenChangingHeaderWithInvalidType($invalidType)
+ {
+ $message = $this->message->withHeader('foo', $invalidType);
+ }
+
+ /**
+ * @covers Meek\Http\Message::withHeader
+ */
+ public function testAddingANewHeaderKeepsTheMessageImmutable()
+ {
+ $message = $this->message->withHeader('Foo', 'bar');
+
+ $this->assertNotSame($this->message, $message);
+ $this->assertEquals('bar', $message->getHeaderLine('foo'));
+ }
+
+ /**
+ * @covers Meek\Http\Message::withHeader
+ */
+ public function testAddingtoAnExistingHeaderKeepsTheMessageImmutable()
+ {
+ $message = $this->message
+ ->withHeader('Foo', 'bar')
+ ->withAddedHeader('Foo', 'baz');
+
+ $this->assertNotSame($this->message, $message);
+ $this->assertEquals('bar, baz', $message->getHeaderLine('foo'));
+ }
+
+ /**
+ * @covers Meek\Http\Message::withHeader
+ */
+ public function testChangingAnExistingHeaderChangesTheOriginalHeaderName()
+ {
+ $message = $this->message
+ ->withHeader('Foo', 'bar')
+ ->withHeader('FOO', 'baz');
+ $headers = $message->getHeaders();
+
+ $this->assertCount(1, $headers);
+ $this->assertArrayHasKey('FOO', $headers);
+ }
+
+ /**
+ * @covers Meek\Http\Message::withAddedHeader
+ * @dataProvider invalidDataTypes
+ * @expectedException InvalidArgumentException
+ */
+ public function testThrowsErrorWhenAddingHeadersWithInvalidTypes($invalidType)
+ {
+ $message = $this->message->withAddedHeader('foo', $invalidType);
+ }
+
+ /**
+ * @covers Meek\Http\Message::withAddedHeader
+ */
+ public function testAddingANewHeaderKeepsTheMessageImmutableWithAddedHeader()
+ {
+ $message = $this->message->withAddedHeader('Foo', 'bar');
+
+ $this->assertNotSame($this->message, $message);
+ $this->assertEquals('bar', $message->getHeaderLine('foo'));
+ }
+
+ /**
+ * @covers Meek\Http\Message::withHeader
+ */
+ public function testAddingtoAnExistingHeaderKeepsTheMessageImmutableWithAddedHeader()
+ {
+ $message = $this->message
+ ->withAddedHeader('Foo', 'bar')
+ ->withAddedHeader('Foo', 'baz');
+
+ $this->assertNotSame($this->message, $message);
+ $this->assertEquals('bar, baz', $message->getHeaderLine('foo'));
+ }
+
+ /**
+ * @covers Meek\Http\Message::withHeader
+ */
+ public function testCanAddMultipleValues()
+ {
+ $message = $this->message->withHeader('Foo', ['bar', 'baz']);
+
+ $this->assertEquals('bar, baz', $message->getHeaderLine('foo'));
+ }
+
+ /**
+ * @covers Meek\Http\Message::withAddedHeader
+ */
+ public function testDoesNotModifyOriginalHeaderName()
+ {
+ $message = $this->message
+ ->withHeader('Foo', 'bar')
+ ->withAddedHeader('FOO', 'baz');
+ $headers = $message->getHeaders();
+
+ $this->assertArrayHasKey('Foo', $headers);
+ }
+
+ /**
+ * @covers Meek\Http\Message::withAddedHeader
+ */
+ public function testCanAddMultipleValuesWithHeader()
+ {
+ $message = $this->message
+ ->withHeader('Foo', 'test')
+ ->withAddedHeader('Foo', ['bar', 'baz']);
+
+ $this->assertEquals('test, bar, baz', $message->getHeaderLine('foo'));
+ }
+
+ /**
+ * @covers Meek\Http\Message::withoutHeader
+ */
+ public function testMessageIsImmutableIfNoHeadersWereRemoved()
+ {
+ $message = $this->message->withoutHeader('Foo');
+
+ $this->assertNotSame($this->message, $message);
+ }
+
+ /**
+ * @covers Meek\Http\Message::withoutHeader
+ * @dataProvider differentCases
+ */
+ public function testRemovingAHeaderKeepsTheMessageImmutable()
+ {
+ $message1 = $this->message->withHeader('Foo', 'bar');
+ $message2 = $message1->withoutHeader('Foo');
+
+ $this->assertNotSame($message1, $message2);
+ $this->assertEmpty($message2->getHeaderLine('foo'));
+ }
+
+ /**
+ * @covers Meek\Http\Message::setHeaders
+ */
+ public function testSettingHeadersCanBeMuttable()
+ {
+ $headers = [
+ 'Host' => 'www.example.com',
+ 'Cache-Control' => ['no-cache', 'private']
+ ];
+
+ $setHeaders = new \ReflectionMethod($this->message, 'setHeaders');
+ $setHeaders->setAccessible(true);
+ $setHeaders->invoke($this->message, $headers);
+
+ $headers['Host'] = ['www.example.com'];
+ $this->assertEquals($headers, $this->message->getHeaders());
+ }
+
+ public function invalidProtocolVersions()
+ {
+ return [
+ 'an empty string' => [''],
+ 'a boolean data type' => [true],
+ 'an array data type' => [[]],
+ 'an object' => [new \stdClass],
+ 'a float data type' => [1.1],
+ 'an integer data type' => [2],
+ 'an alpabetic string' => ['hello']
+ ];
+ }
+
+ public function invalidDataTypes()
+ {
+ return [
+ 'a boolean' => [true],
+ 'an object' => [new \stdClass],
+ 'a float data type' => [3.14159],
+ 'an integer' => [4],
+ 'an array of booleans' => [[true]],
+ 'an array of arrays' => [[[]]],
+ 'an array of objects' => [[new \stdClass]],
+ 'an array of floats' => [[3.14159]],
+ 'an array of integers' => [[4]]
+ ];
+ }
+
+ public function differentCases()
+ {
+ return [
+ 'lowercase' => ['foo'],
+ 'uppercase' => ['FOO'],
+ 'mixedcase' => ['FoO']
+ ];
+ }
+}
diff --git a/tests/RedirectedResponseTest.php b/tests/RedirectedResponseTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/RequestTest.php b/tests/RequestTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/Session/PdoStorageDriverTest.php b/tests/Session/PdoStorageDriverTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/Session/StorageHandlerTest.php b/tests/Session/StorageHandlerTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/SessionTest.php b/tests/SessionTest.php
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/StatusTest.php b/tests/StatusTest.php
index 37a61d7..cd347c0 100644
--- a/tests/StatusTest.php
+++ b/tests/StatusTest.php
@@ -1,11 +1,8 @@