Remove old files

This commit is contained in:
PJ Dietz 2015-04-12 13:51:16 -04:00
parent 4096295421
commit b0a0f5262e
36 changed files with 0 additions and 4741 deletions

View File

@ -1,131 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Client
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed;
use pjdietz\WellRESTed\Exceptions\CurlException;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
/**
* Class for making HTTP requests using cURL.
*/
class Client
{
/** @var array Map of cURL options with cURL constants as keys */
private $curlOpts;
/**
* Create a new client.
*
* You may optionally provide an array of cURL options to use by default.
* Options passed in the requset method will override these.
*
* @param array $curlOpts Optional array of cURL options
*/
public function __construct(array $curlOpts = null)
{
if (is_array($curlOpts)) {
$this->curlOpts = $curlOpts;
} else {
$this->curlOpts = array();
}
}
/**
* Make an HTTP request and return a Response.
*
* @param RequestInterface $rqst
* @param array $curlOpts Optional array of cURL options
* @throws \pjdietz\WellRESTed\Exceptions\CurlException
* @return ResponseInterface
*/
public function request(RequestInterface $rqst, $curlOpts = null)
{
$ch = curl_init();
$headers = array();
foreach ($rqst->getHeaders() as $field => $value) {
$headers[] = sprintf('%s: %s', $field, $value);
}
$options = $this->curlOpts;
$options[CURLOPT_URL] = $rqst->getUri();
$options[CURLOPT_PORT] = $rqst->getPort();
$options[CURLOPT_HEADER] = 1;
$options[CURLOPT_RETURNTRANSFER] = 1;
$options[CURLOPT_HTTPHEADER] = $headers;
// Set the method. Include the body, if needed.
switch ($rqst->getMethod()) {
case 'GET':
$options[CURLOPT_HTTPGET] = 1;
break;
case 'POST':
$options[CURLOPT_POST] = 1;
$options[CURLOPT_POSTFIELDS] = $rqst->getBody();
break;
case 'PUT':
$options[CURLOPT_CUSTOMREQUEST] = 'PUT';
$options[CURLOPT_POSTFIELDS] = $rqst->getBody();
break;
default:
$options[CURLOPT_CUSTOMREQUEST] = $rqst->getMethod();
$options[CURLOPT_POSTFIELDS] = $rqst->getBody();
break;
}
// Override cURL options with the user options passed in.
if ($curlOpts) {
foreach ($curlOpts as $optKey => $optValue) {
$options[$optKey] = $optValue;
}
}
// Set the cURL options.
curl_setopt_array($ch, $options);
// Make the cURL request.
$result = curl_exec($ch);
// Throw an exception in the event of a cURL error.
if ($result === false) {
$error = curl_error($ch);
$errno = curl_errno($ch);
curl_close($ch);
throw new CurlException($error, $errno);
}
// Make a reponse to populate and return with data obtained via cURL.
$resp = new Response();
$resp->setStatusCode(curl_getinfo($ch, CURLINFO_HTTP_CODE));
// Split the result into headers and body.
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($result, 0, $headerSize);
$body = substr($result, $headerSize);
// Set the body. Do not auto-add the Content-length header.
$resp->setBody($body, false);
// Iterate over the headers line by line and add each one.
foreach (explode("\r\n", $headers) as $header) {
if (strpos($header, ':')) {
list ($headerName, $headerValue) = explode(':', $header, 2);
$resp->setHeader($headerName, ltrim($headerValue));
}
}
curl_close($ch);
return $resp;
}
}

View File

@ -1,19 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Exceptions\CurlException
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Exceptions;
/**
* Exception related to a cURL operations. The message and code should correspond
* to the cURL error and error number that caused the excpetion.
*/
class CurlException extends WellRESTedException
{
}

View File

@ -1,20 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Exceptions\WellRESTedException
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2013 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Exceptions;
use Exception;
/**
* Top-level class for custom exceptions thrown by WellRESTed.
*/
class WellRESTedException extends Exception
{
}

View File

@ -1,207 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Handler
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed;
use pjdietz\WellRESTed\Exceptions\HttpExceptions\HttpException;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
/**
* Responds to a request based on the HTTP method.
*
* To use Handler, create a subclass and implement the methods for any HTTP
* verbs you would like to support. (get() for GET, post() for POST, etc).
*
* - Access the request via the protected member $this->request
* - Access a map of arguments via $this->args (e.g., URI path variables)
* - Modify $this->response to provide the response the instance will return
*/
abstract class Handler implements HandlerInterface
{
/** @var array Map of variables to supplement the request, usually path variables. */
protected $args;
/** @var RequestInterface The HTTP request to respond to. */
protected $request;
/** @var ResponseInterface The HTTP response to send based on the request. */
protected $response;
/**
* Return the handled response.
*
* @param RequestInterface $request
* @param array|null $args
* @return ResponseInterface
*/
public function getResponse(RequestInterface $request, array $args = null)
{
$this->request = $request;
$this->args = $args;
$this->response = new Response();
try {
$this->buildResponse();
} catch (HttpException $e) {
$this->response->setStatusCode($e->getCode());
$this->response->setBody($e->getMessage());
}
return $this->response;
}
/**
* Prepare the Response. Override this method if your subclass needs to
* repond to any non-standard HTTP methods. Otherwise, override the
* get, post, put, etc. methods.
*
* An uncaught HttpException (or subclass) will be converted to a response
* using the exception's code as the status code and the exceptios message
* as the body.
*/
protected function buildResponse()
{
switch ($this->request->getMethod()) {
case 'GET':
$this->get();
break;
case 'HEAD':
$this->head();
break;
case 'POST':
$this->post();
break;
case 'PUT':
$this->put();
break;
case 'DELETE':
$this->delete();
break;
case 'PATCH':
$this->patch();
break;
case 'OPTIONS':
$this->options();
break;
default:
$this->respondWithMethodNotAllowed();
}
}
/**
* Method for handling HTTP GET requests.
*
* This method should modify the instance's response member.
*/
protected function get()
{
$this->respondWithMethodNotAllowed();
}
/**
* Method for handling HTTP HEAD requests.
*
* This method should modify the instance's response member.
*/
protected function head()
{
// The default function calls the instance's get() method, then sets
// the resonse's body member to an empty string.
$this->get();
$this->response->setBody('', false);
}
/**
* Method for handling HTTP POST requests.
*
* This method should modify the instance's response member.
*/
protected function post()
{
$this->respondWithMethodNotAllowed();
}
/**
* Method for handling HTTP PUT requests.
*
* This method should modify the instance's response member.
*/
protected function put()
{
$this->respondWithMethodNotAllowed();
}
/**
* Method for handling HTTP DELETE requests.
*
* This method should modify the instance's response member.
*/
protected function delete()
{
$this->respondWithMethodNotAllowed();
}
/**
* Method for handling HTTP PATCH requests.
*
* This method should modify the instance's response member.
*/
protected function patch()
{
$this->respondWithMethodNotAllowed();
}
/**
* Method for handling HTTP OPTION requests.
*
* This method should modify the instance's response member.
*/
protected function options()
{
if ($this->addAllowHeader()) {
$this->response->setStatusCode(200);
} else {
$this->response->setStatusCode(405);
}
}
/**
* Provide a default response for unsupported methods.
*/
protected function respondWithMethodNotAllowed()
{
$this->response->setStatusCode(405);
$this->addAllowHeader();
}
/**
* Return an array of HTTP verbs this handler supports.
*
* For example, to support GET and POST, return array("GET","POST");
*
* @return array
*/
protected function getAllowedMethods()
{
}
/**
* Add an Allow: header using the methods returned by getAllowedMethods()
*
* @return bool The header was added.
*/
protected function addAllowHeader()
{
$methods = $this->getAllowedMethods();
if ($methods) {
$this->response->setHeader('Allow', join($methods, ', '));
return true;
}
return false;
}
}

View File

@ -1,37 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\HandlerUnpacker
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
/**
* Class for retreiving a handler or response from a callable, string, or instance.
*/
class HandlerUnpacker
{
/**
* Return the handler or response from a callable, string, or instance.
*
* @param $handler
* @param RequestInterface $request
* @param array $args
* @return mixed
*/
public function unpack($handler, RequestInterface $request = null, array $args = null)
{
if (is_callable($handler)) {
$handler = $handler($request, $args);
} elseif (is_string($handler)) {
$handler = new $handler();
}
return $handler;
}
}

View File

@ -1,26 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Interfaces\HandlerInterface
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Interfaces;
/**
* Provides a mechanism for obtaining a response given a request.
*/
interface HandlerInterface
{
/**
* Return the handled response.
*
* @param RequestInterface $request The request to respond to.
* @param array|null $args Optional additional arguments.
* @return ResponseInterface The handled response.
*/
public function getResponse(RequestInterface $request, array $args = null);
}

View File

@ -1,84 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Interfaces\RequestInterface
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Interfaces;
/**
* Interface for representing an HTTP request.
*/
interface RequestInterface
{
/**
* Return the HTTP verb (e.g., GET, POST, PUT).
*
* @return string Request verb
*/
public function getMethod();
/**
* Return the full URI for the request.
*
* @return string
*/
public function getUri();
/**
* Return the hostname portion of the URI
*
* @return string
*/
public function getHostname();
/**
* Return path component of the request URI.
*
* @return string Path component
*/
public function getPath();
/**
* Return the HTTP port
*
* @return int
*/
public function getPort();
/**
* Return an associative array of query paramters.
*
* @return array Query paramters
*/
public function getQuery();
/**
* Return the value for a given header.
*
* Per RFC 2616, HTTP headers are case-insensitive. Take care to conform to
* this when implementing.
*
* @param string $headerName Field name of the header
* @return string Header field value
*/
public function getHeader($headerName);
/**
* Return an associative array of headers.
*
* @return array
*/
public function getHeaders();
/**
* Return the body of the request.
*
* @return string Request body
*/
public function getBody();
}

View File

@ -1,70 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Interfaces\ResponseInterface
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Interfaces;
/**
* Interface for representing an HTTP response.
*/
interface ResponseInterface
{
/**
* Return the HTTP status code
*
* @return int HTTP status code
*/
public function getStatusCode();
/**
* Set the status code for the response.
*
* @param int $statusCode HTTP status code
*/
public function setStatusCode($statusCode);
/**
* Return the value for a given header.
*
* Per RFC 2616, HTTP headers are case-insensitive. Take care to conform to
* this when implementing.
*
* @param string $headerName Field name of the header
* @return string Header field value
*/
public function getHeader($headerName);
/**
* Set the value for a given header.
*
* Per RFC 2616, HTTP headers are case-insensitive. Take care to conform to
* this when implementing.
*
* @param string $headerName Field name
* @param string $headerValue Field value
*/
public function setHeader($headerName, $headerValue);
/**
* Return the body of the response.
*
* @return string Response body
*/
public function getBody();
/**
* Set the body of the response.
*
* @param string $body Response body
*/
public function setBody($body);
/** Issue the reponse to the client. */
public function respond();
}

View File

@ -1,24 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Interfaces\Route\PrefixRouteInterface
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Interfaces\Routes;
/**
* Interface for routes that map to paths begining with a given prefix or prefixes
*/
interface PrefixRouteInterface
{
/**
* Returns the path prefixes the instance maps to a target handler.
*
* @return string[] List array of path prefixes.
*/
public function getPrefixes();
}

View File

@ -1,24 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Interfaces\Route\StaticRouteInterface
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Interfaces\Routes;
/**
* Interface for routes that map to an exact path or paths
*/
interface StaticRouteInterface
{
/**
* Returns the paths the instance maps to a target handler.
*
* @return string[] List array of paths.
*/
public function getPaths();
}

View File

@ -1,144 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Message
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed;
/**
* Common base class for the Request and Response classes.
*/
abstract class Message
{
/** @var string Entity body of the message */
protected $body;
/** @var array Associative array of HTTP headers */
protected $headers;
/**
* Associative array of lowercase header field names as keys with
* corresponding case sensitive field names from the $headers array as
* values.
*
* @var array
*/
protected $headerLookup;
// -------------------------------------------------------------------------
/**
* Create a new HTTP message.
*/
public function __construct()
{
$this->headers = array();
$this->headerLookup = array();
}
// -------------------------------------------------------------------------
// Accessors
/**
* Return the body payload of the instance.
*
* @return string
*/
public function getBody()
{
return $this->body;
}
/**
* Set the body for the request.
*
* @param string $body
*/
public function setBody($body)
{
$this->body = $body;
}
/**
* Return an associative array of all set headers.
*
* @return array
*/
public function getHeaders()
{
return $this->headers;
}
/**
* Return the value of a given header, or null if it does not exist.
*
* @param string $name
* @return string|null
*/
public function getHeader($name)
{
$lowerName = strtolower($name);
if (isset($this->headerLookup[$lowerName])) {
$realName = $this->headerLookup[$lowerName];
return $this->headers[$realName];
}
return null;
}
/**
* Add or update a header to a given value
*
* @param string $name
* @param string $value
*/
public function setHeader($name, $value)
{
$lowerName = strtolower($name);
// Check if a mapping already exists for this header.
// Remove it, if needed.
if (isset($this->headerLookup[$lowerName])
&& $this->headerLookup[$lowerName] !== $name
) {
unset($this->headers[$this->headerLookup[$lowerName]]);
}
// Store the actual header.
$this->headers[$name] = $value;
// Store a mapping to the user's prefered case.
$this->headerLookup[$lowerName] = $name;
}
/**
* Return if the response contains a header with the given key.
*
* @param $name
* @return bool
*/
public function issetHeader($name)
{
$lowerName = strtolower($name);
return isset($this->headerLookup[$lowerName]);
}
/**
* Remove a header. This method does nothing if the header does not exist.
*
* @param string $name
*/
public function unsetHeader($name)
{
$lowerName = strtolower($name);
if (isset($this->headerLookup[$lowerName])) {
$realName = $this->headerLookup[$lowerName];
if (isset($this->headers[$realName])) {
unset($this->headers[$realName]);
}
unset($this->headerLookup[$lowerName]);
}
}
}

View File

@ -1,365 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Request
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed;
use InvalidArgumentException;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use UnexpectedValueException;
/**
* A Request instance represents an HTTP request.
*/
class Request extends Message implements RequestInterface
{
/**
* Singleton instance derived from reading the request sent to the server.
*
* @var Request
* @static
*/
static protected $theRequest;
/** @var string HTTP method or verb for the request */
private $method = "GET";
/** @var string Scheme for the request (Must be "http" or "https" */
private $scheme;
/** @var string The Hostname for the request */
private $hostname = "localhost";
/** @var string Path component of the URI for the request */
private $path = "/";
/** @var array Array of fragments of the path, delimited by slashes */
private $pathParts;
/** @var int HTTP Port */
private $port = 80;
/** @var array Associative array of query parameters */
private $query;
// -------------------------------------------------------------------------
/**
* Create a new Request instance.
*
* @param string|null $uri
* @param string $method
*/
public function __construct($uri = null, $method = "GET")
{
parent::__construct();
if (!is_null($uri)) {
$this->setUri($uri);
}
$this->method = $method;
}
// -------------------------------------------------------------------------
/**
* Return a reference to the singleton instance of the Request derived
* from the server's information about the request sent to the server.
*
* @return Request
* @static
*/
public static function getRequest()
{
if (!isset(self::$theRequest)) {
$request = new Request();
$request->readHttpRequest();
self::$theRequest = $request;
}
return self::$theRequest;
}
/**
* Read and return all request headers from the request issued to the server.
*
* @return array Associative array of headers
*/
public static function getRequestHeaders()
{
// Prefer apache_request_headers is available.
if (function_exists('apache_request_headers')) {
return apache_request_headers();
}
// http://www.php.net/manual/en/function.getallheaders.php#84262
$headers = array();
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) === "HTTP_") {
$headers[str_replace(" ", "-", ucwords(strtolower(str_replace("_", " ", substr($name, 5)))))] = $value;
}
}
return $headers;
}
/**
* Set instance members based on the HTTP request sent to the server.
*/
public function readHttpRequest()
{
$this->setBody(file_get_contents("php://input"), false);
$this->headers = self::getRequestHeaders();
// Add case insensitive headers to the lookup table.
foreach ($this->headers as $key => $value) {
$this->headerLookup[strtolower($key)] = $key;
}
$this->setMethod($_SERVER["REQUEST_METHOD"]);
$this->setUri($_SERVER["REQUEST_URI"]);
$this->setHostname($_SERVER["HTTP_HOST"]);
}
/**
* Return the method (e.g., GET, POST, PUT, DELETE)
*
* @return string
*/
public function getMethod()
{
return $this->method;
}
/**
* Assign the method (e.g., GET, POST, PUT, DELETE)
*
* @param string $method
*/
public function setMethod($method)
{
$this->method = $method;
}
/**
* Return the full URI includeing protocol, hostname, path, and query.
*
* @return array
*/
public function getUri()
{
$uri = $this->scheme . "://" . $this->hostname;
if ($this->port !== $this->getDefaultPort()) {
$uri .= ":" . $this->port;
}
if ($this->path !== "/") {
$uri .= $this->path;
}
if ($this->query) {
$uri .= "?" . http_build_query($this->query);
}
return $uri;
}
/**
* Set the URI for the Request. This sets the other members: hostname,
* path, port, and query.
*
* @param string $uri
*/
public function setUri($uri)
{
// Provide http and localhost if missing.
if ($uri[0] === "/") {
$uri = "http://localhost" . $uri;
} elseif (strpos($uri, "://") === false) {
$uri = "http://" . $uri;
}
$parsed = parse_url($uri);
$scheme = isset($parsed["scheme"]) ? $parsed["scheme"] : "http";
$this->setScheme($scheme);
$host = isset($parsed["host"]) ? $parsed["host"] : "localhost";
$this->setHostname($host);
$port = isset($parsed["port"]) ? (int) $parsed["port"] : $this->getDefaultPort();
$this->setPort($port);
$path = isset($parsed["path"]) ? $parsed["path"] : "/";
$this->setPath($path);
$query = isset($parsed["query"]) ? $parsed["query"] : "";
$this->setQuery($query);
}
/**
* Return the hostname portion of the URI
*
* @return string
*/
public function getHostname()
{
return $this->hostname;
}
/**
* Assign the hostname portion of the URI
*
* @param string $hostname
*/
public function setHostname($hostname)
{
$this->hostname = $hostname;
}
/**
* Set the scheme for the request (either "http" or "https")
*
* @param string $scheme
* @throws \UnexpectedValueException
*/
public function setScheme($scheme)
{
$scheme = strtolower($scheme);
if (!in_array($scheme, array("http", "https"))) {
throw new UnexpectedValueException('Scheme must be "http" or "https".');
}
$this->scheme = $scheme;
}
/**
* Return the scheme for the request (either "http" or "https")
*
* @return string
*/
public function getScheme()
{
return $this->scheme;
}
/**
* Return the path part of the URI as a string.
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Set the path and pathParts members.
*
* @param string $path
*/
public function setPath($path)
{
$this->path = $path;
if ($path !== "/") {
$this->pathParts = explode("/", substr($path, 1));
} else {
$this->pathParts = array();
}
}
/**
* Return an array of the sections of the path delimited by slashes.
*
* @return array
*/
public function getPathParts()
{
return $this->pathParts;
}
/**
* Return the HTTP port
*
* @return int
*/
public function getPort()
{
return $this->port;
}
/**
* Set the HTTP port
*
* @param int $port
*/
public function setPort($port = null)
{
if (is_null($port)) {
$port = $this->getDefaultPort();
}
$this->port = $port;
}
/**
* Return an associative array representing the query.
*
* @return array
*/
public function getQuery()
{
return $this->query;
}
/**
* Set the query. The value passed can be a query string of key-value pairs
* joined by ampersands or it can be an associative array.
*
* @param string|array $query
* @throws InvalidArgumentException
*/
public function setQuery($query)
{
if (is_string($query)) {
$qs = $query;
parse_str($qs, $query);
} elseif (is_object($query)) {
$query = (array) $query;
}
if (is_array($query)) {
ksort($query);
$this->query = $query;
} else {
throw new InvalidArgumentException("Unable to parse query string.");
}
}
/**
* Return the form fields for this request.
*
* @return array
*/
public function getFormFields()
{
parse_str($this->getBody(), $fields);
return $fields;
}
/**
* Set the body by supplying an associative array of form fields.
*
* In addition, add a "Content-type: application/x-www-form-urlencoded" header
*
* @param array $fields
*/
public function setFormFields(array $fields)
{
$this->setBody(http_build_query($fields));
$this->setHeader("Content-type", "application/x-www-form-urlencoded");
}
// -------------------------------------------------------------------------
/**
* Return the default port for the currently set scheme.
*
* @return int;
*/
protected function getDefaultPort()
{
return $this->scheme === "http" ? 80 : 443;
}
}

View File

@ -1,321 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Response
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed;
use InvalidArgumentException;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
/**
* A Response instance allows you to build an HTTP response and send it when
* finished.
*/
class Response extends Message implements ResponseInterface
{
const CHUNK_SIZE = 1048576;
/** @var string Path to a file to read and output as the body. */
private $bodyFilePath;
/**
* Text explanation of the HTTP Status Code. You only need to set this if
* you are using nonstandard status codes. Otherwise, the instance will
* set the when you update the status code.
*
* @var string
*/
private $reasonPhrase;
/** @var int HTTP status code */
private $statusCode;
/** @var string HTTP protocol and version */
private $protocol = "HTTP/1.1";
// -------------------------------------------------------------------------
/**
* Create a new Response instance, optionally passing a status code, body,
* and headers.
*
* @param int $statusCode
* @param string $body
* @param array $headers
*/
public function __construct($statusCode = 500, $body = null, $headers = null)
{
parent::__construct();
$this->setStatusCode($statusCode);
if (is_array($headers)) {
foreach ($headers as $key => $value) {
$this->setHeader($key, $value);
}
}
if (!is_null($body)) {
$this->body = $body;
}
}
// -------------------------------------------------------------------------
// Accessors
/**
* Provide a new entity body for the respone.
* This method also updates the content-length header based on the length
* of the new body string.
*
* @param string $value
* @param bool $setContentLength Automatically add a Content-length header
*/
public function setBody($value, $setContentLength = true)
{
$this->body = $value;
if ($setContentLength === true) {
$this->setHeader('Content-Length', strlen($value));
}
}
/**
* Provide the path to a file to output as the response body.
*
* @param string $bodyFilePath Filepath
*/
public function setBodyFilePath($bodyFilePath)
{
$this->bodyFilePath = $bodyFilePath;
}
/**
* Return the path to the file to output as the response body.
*
* @return string Filepath
*/
public function getBodyFilePath()
{
return $this->bodyFilePath;
}
/**
* Return the portion of the status line explaining the status.
*
* @return string
*/
public function getReasonPhrase()
{
return $this->reasonPhrase;
}
/**
* Return true for status codes in the 1xx-3xx range.
*
* @return bool
*/
public function getSuccess()
{
return $this->statusCode < 400;
}
/**
* Return the HTTP status code for the response.
*
* @return int
*/
public function getStatusCode()
{
return $this->statusCode;
}
/**
* Return the HTTP status line, e.g. HTTP/1.1 200 OK.
*
* @return string
*/
public function getStatusLine()
{
return $this->protocol . " " . $this->statusCode . " " . $this->reasonPhrase;
}
/**
* Set the status code and optionally the reason phrase explaining it.
*
* @param int $statusCode
* @param string|null $reasonPhrase
* @throws InvalidArgumentException
*/
public function setStatusCode($statusCode, $reasonPhrase = null)
{
$this->statusCode = (int) $statusCode;
if (is_null($reasonPhrase)) {
switch ($this->statusCode) {
case 100:
$text = 'Continue';
break;
case 101:
$text = 'Switching Protocols';
break;
case 200:
$text = 'OK';
break;
case 201:
$text = 'Created';
break;
case 202:
$text = 'Accepted';
break;
case 203:
$text = 'Non-Authoritative Information';
break;
case 204:
$text = 'No Content';
break;
case 205:
$text = 'Reset Content';
break;
case 206:
$text = 'Partial Content';
break;
case 300:
$text = 'Multiple Choices';
break;
case 301:
$text = 'Moved Permanently';
break;
case 302:
$text = 'Found';
break;
case 303:
$text = 'See Other';
break;
case 304:
$text = 'Not Modified';
break;
case 305:
$text = 'Use Proxy';
break;
case 400:
$text = 'Bad Request';
break;
case 401:
$text = 'Unauthorized';
break;
case 402:
$text = 'Payment Required';
break;
case 403:
$text = 'Forbidden';
break;
case 404:
$text = 'Not Found';
break;
case 405:
$text = 'Method Not Allowed';
break;
case 406:
$text = 'Not Acceptable';
break;
case 407:
$text = 'Proxy Authentication Required';
break;
case 408:
$text = 'Request Timeout';
break;
case 409:
$text = 'Conflict';
break;
case 410:
$text = 'Gone';
break;
case 411:
$text = 'Length Required';
break;
case 412:
$text = 'Precondition Failed';
break;
case 413:
$text = 'Request Entity Too Large';
break;
case 414:
$text = 'Request-URI Too Long';
break;
case 415:
$text = 'Unsupported Media Type';
break;
case 500:
$text = 'Internal Server Error';
break;
case 501:
$text = 'Not Implemented';
break;
case 502:
$text = 'Bad Gateway';
break;
case 503:
$text = 'Service Unavailable';
break;
case 504:
$text = 'Gateway Timeout';
break;
case 505:
$text = 'HTTP Version Not Supported';
break;
default:
$text = 'Nonstandard';
break;
}
$this->reasonPhrase = $text;
} else {
if (is_string($reasonPhrase)) {
$this->reasonPhrase = $reasonPhrase;
} else {
throw new InvalidArgumentException('$reasonPhrase must be a string (or null to use standard HTTP Reason-Phrase');
}
}
}
/**
* Output the response to the client.
*
* @param bool $headersOnly Do not include the body, only the headers.
*/
public function respond($headersOnly = false)
{
// Output the HTTP status code.
header($this->getStatusLine());
// Output each header.
foreach ($this->headers as $header => $value) {
header($header . ': ' . $value);
}
// Output the entity body.
if (!$headersOnly) {
if (isset($this->bodyFilePath) && $this->bodyFilePath && file_exists($this->bodyFilePath)) {
$this->outputBodyFile();
} else {
print $this->body;
}
}
}
// -------------------------------------------------------------------------
/** Output the contents of a file */
private function outputBodyFile()
{
$handle = fopen($this->getBodyFilePath(), 'rb');
if ($handle !== false) {
while (!feof($handle)) {
$buffer = fread($handle, self::CHUNK_SIZE);
print $buffer;
flush();
}
}
}
}

View File

@ -1,166 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\RouteTable
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
use pjdietz\WellRESTed\Interfaces\Routes\PrefixRouteInterface;
use pjdietz\WellRESTed\Interfaces\Routes\StaticRouteInterface;
/**
* RouteTable
*
* A RouteTable uses the request path to dispatche the best-matching handler.
*/
class RouteTable implements HandlerInterface
{
/** @var HandlerInterface[] Array of Route objects */
private $routes;
/** @var array Hash array mapping exact paths to routes */
private $staticRoutes;
/** @var array Hash array mapping path prefixes to routes */
private $prefixRoutes;
/** Create a new RouteTable */
public function __construct()
{
$this->routes = array();
$this->prefixRoutes = array();
$this->staticRoutes = array();
}
/**
* Return the response from the best matching route.
*
* @param RequestInterface $request
* @param array|null $args
* @return ResponseInterface
*/
public function getResponse(RequestInterface $request, array $args = null)
{
$response = null;
// First check if there is a static route.
$response = $this->getStaticResponse($request, $args);
if ($response) {
return $response;
}
// Check prefix routes for any routes that match. Use the longest matching prefix.
$response = $this->getPrefixResponse($request, $args);
if ($response) {
return $response;
}
// Try each of the routes.
foreach ($this->routes as $route) {
/** @var HandlerInterface $route */
$response = $route->getResponse($request, $args);
if ($response) {
return $response;
}
}
return null;
}
/**
* Return the response from the matching static route, or null if none match.
*
* @param RequestInterface $request
* @param array|null $args
* @return ResponseInterface
*/
private function getStaticResponse(RequestInterface $request, array $args = null)
{
$path = $request->getPath();
if (isset($this->staticRoutes[$path])) {
$route = $this->staticRoutes[$path];
return $route->getResponse($request, $args);
}
return null;
}
/**
* Returning the response from the best-matching prefix handler, or null if none match.
*
* @param RequestInterface $request
* @param array|null $args
* @return ResponseInterface
*/
private function getPrefixResponse(RequestInterface $request, array $args = null)
{
$path = $request->getPath();
// Find all prefixes that match the start of this path.
$prefixes = array_keys($this->prefixRoutes);
$matches = array_filter(
$prefixes,
function ($prefix) use ($path) {
return (strrpos($path, $prefix, -strlen($path)) !== false);
}
);
if ($matches) {
if (count($matches) > 0) {
// If there are multiple matches, sort them to find the one with the longest string length.
$compareByLength = function ($a, $b) {
return strlen($b) - strlen($a);
};
usort($matches, $compareByLength);
}
$route = $this->prefixRoutes[$matches[0]];
return $route->getResponse($request, $args);
}
return null;
}
/**
* Append a new route.
*
* @param HandlerInterface $route
*/
public function addRoute(HandlerInterface $route)
{
if ($route instanceof StaticRouteInterface) {
$this->addStaticRoute($route);
} elseif ($route instanceof PrefixRouteInterface) {
$this->addPrefixRoute($route);
} else {
$this->routes[] = $route;
}
}
/**
* Register a new static route.
*
* @param StaticRouteInterface $staticRoute
*/
private function addStaticRoute(StaticRouteInterface $staticRoute)
{
foreach ($staticRoute->getPaths() as $path) {
$this->staticRoutes[$path] = $staticRoute;
}
}
/**
* Register a new prefix route.
*
* @param PrefixRouteInterface $prefixRoute
*/
private function addPrefixRoute(PrefixRouteInterface $prefixRoute)
{
foreach ($prefixRoute->getPrefixes() as $prefix) {
$this->prefixRoutes[$prefix] = $prefixRoute;
}
}
}

View File

@ -1,256 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Router
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed;
use pjdietz\WellRESTed\Exceptions\HttpExceptions\HttpException;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
use pjdietz\WellRESTed\Routes\PrefixRoute;
use pjdietz\WellRESTed\Routes\RouteFactory;
use pjdietz\WellRESTed\Routes\StaticRoute;
/**
* Router
*
* A Router uses a table of Routes to find the appropriate Handler for a request.
*/
class Router implements HandlerInterface
{
/** @var array Hash array of status code => error handler */
private $errorHandlers;
/** @var RouteTable Collection of routes */
private $routeTable;
/** Create a new Router. */
public function __construct()
{
$this->errorHandlers = array();
$this->routeTable = new RouteTable();
}
/**
* Add a route or series of routes to the Router.
*
* When adding a single route, the first argument should be the path, path
* prefix, URI template, or regex pattern. The method will attempt to find
* the best type of route based on this argument and send the remaining
* arguments to the route's constructor. @see {RouteFactory::createRoute}
*
* To add multiple routes, pass arrays where each array contains an argument list.
*/
public function add()
{
$factory = new RouteFactory();
$args = func_get_args();
if (count($args) > 1 && is_array($args[0])) {
foreach ($args as $argumentList) {
$route = call_user_func_array(array($factory, "createRoute"), $argumentList);
$this->addRoute($route);
}
return;
}
$route = call_user_func_array(array($factory, "createRoute"), $args);
$this->addRoute($route);
}
/**
* Append a series of routes.
*
* @param array $routes List array of routes
*/
public function addRoutes(array $routes)
{
foreach ($routes as $route) {
if ($route instanceof HandlerInterface) {
$this->addRoute($route);
}
}
}
/**
* Append a new route to the route table.
*
* @param HandlerInterface $route
*/
public function addRoute(HandlerInterface $route)
{
$this->routeTable->addRoute($route);
}
/**
* Add custom error handlers.
*
* @param array $errorHandlers Array mapping integer error codes to handlers
*/
public function setErrorHandlers(array $errorHandlers)
{
foreach ($errorHandlers as $statusCode => $errorHandler) {
$this->setErrorHandler($statusCode, $errorHandler);
}
}
/**
* Add a custom error handler.
*
* @param integer $statusCode The error status code
* @param mixed $errorHandler
*/
public function setErrorHandler($statusCode, $errorHandler)
{
$this->errorHandlers[$statusCode] = $errorHandler;
}
/**
* Dispatch the server request through the router and output the response.
*
* Respond with a 404 Not Found if no route provides a response.
* @param array|null $args
*/
public function respond($args = null)
{
$request = Request::getRequest();
$response = $this->getResponse($request, $args);
if (!$response) {
$response = $this->getNoRouteResponse($request);
}
if ($response instanceof ResponseInterface) {
$response->respond();
}
}
/**
* Return the response built by the handler based on the request
*
* @param RequestInterface $request
* @param array|null $args
* @return ResponseInterface
*/
public function getResponse(RequestInterface $request, array $args = null)
{
$response = $this->tryResponse($this->routeTable, $request, $args);
if ($response && $response instanceof ResponseInterface) {
// Check if the router has an error handler for this status code.
$status = $response->getStatusCode();
$errorResponse = $this->getErrorResponse($status, $request, $args, $response);
if ($errorResponse) {
return $errorResponse;
}
}
return $response;
}
/**
* Prepare a response indicating a 404 Not Found error.
*
* Rather than subclassing and overriding this method, you may provide an
* error handler for status code 404. (@see setErrorHandler)
*
* @param RequestInterface $request
* @return ResponseInterface
*/
protected function getNoRouteResponse(RequestInterface $request)
{
$response = $this->getErrorResponse(404, $request);
if ($response) {
return $response;
}
$response = new Response(404);
$response->setBody('No resource at ' . $request->getPath());
return $response;
}
/**
* Obtain a response from the registered error handlers.
*
* @param int $status HTTP Status Code
* @param RequestInterface $request The original request
* @param null $args Optional additional data
* @param null $response The response providing the error
* @return mixed
*/
private function getErrorResponse($status, $request, $args = null, $response = null)
{
if (isset($this->errorHandlers[$status])) {
// Pass the response triggering this along to the error handler.
$errorArgs = array("response" => $response);
if ($args) {
$errorArgs = array_merge($args, $errorArgs);
}
$unpacker = new HandlerUnpacker();
$handler = $unpacker->unpack($this->errorHandlers[$status], $request, $errorArgs);
if (!is_null($handler) && $handler instanceof HandlerInterface) {
return $handler->getResponse($request, $errorArgs);
}
return $handler;
}
return null;
}
/**
* Wraps the getResponse method in a try-catch.
*
* In an HttpException is caught while trying to get a response, the method
* returns a response based on the HttpException's error code and message.
*
* @param HandlerInterface $handler The Route or Handler to try.
* @param RequestInterface $request The incoming request.
* @param array $args The array of arguments.
* @return Response
*/
private function tryResponse($handler, $request, $args)
{
$response = null;
try {
$response = $handler->getResponse($request, $args);
} catch (HttpException $e) {
$response = new Response();
$response->setStatusCode($e->getCode());
$response->setBody($e->getMessage());
}
return $response;
}
////////////////
// Deprecated //
////////////////
/**
* Set a route for specific prefix
*
* @deprecated Use {@see addRoute} instead.
* @see addRoute
* @param array|string $prefixes
* @param mixed $handler
*/
public function setPrefixRoute($prefixes, $handler)
{
$this->addRoute(new PrefixRoute($prefixes, $handler));
trigger_error("Router::setPrefixRoute is deprecated. Use addRoute", E_USER_DEPRECATED);
}
/**
* Set a route for a given path
*
* @deprecated Use {@see addRoute} instead.
* @see addRoute
* @param array|string $paths
* @param mixed $handler
*/
public function setStaticRoute($paths, $handler)
{
$this->addRoute(new StaticRoute($paths, $handler));
trigger_error("Router::setStaticRoute is deprecated. Use addRoute", E_USER_DEPRECATED);
}
}

View File

@ -1,62 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\BaseRoute
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Routes;
use pjdietz\WellRESTed\HandlerUnpacker;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
/**
* Base class for Routes.
*/
abstract class BaseRoute implements HandlerInterface
{
/** @var callable|string|HandlerInterface Handler to dispatch */
private $target;
/**
* Create a new route that will dispatch an instance of the given handler.
*
* $target may be:
* - A callable
* - A string containing the fully qualified class of a HandlerInterface
* - A HandlerInterface instance
*
* Callable targets should expect to receive the same arguments as would
* be passed to a HandlerInterface's getResponse() method. The callable
* should return a HandlerInterface instance, a ResponseInterface instance,
* or null.
*
* @param mixed $target Handler to dispatch
*/
public function __construct($target)
{
$this->target = $target;
}
/**
* Return the handled response.
*
* @param RequestInterface $request The request to respond to.
* @param array|null $args Optional additional arguments.
* @return ResponseInterface The response.
*/
protected function getResponseFromTarget(RequestInterface $request, array $args = null)
{
$unpacker = new HandlerUnpacker();
$target = $unpacker->unpack($this->target, $request, $args);
if (!is_null($target) && $target instanceof HandlerInterface) {
return $target->getResponse($request, $args);
}
return $target;
}
}

View File

@ -1,80 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\Routes\PrefixRoute
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Routes;
use InvalidArgumentException;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
use pjdietz\WellRESTed\Interfaces\Routes\PrefixRouteInterface;
/**
* Maps a list of static URI paths to a Handler
*/
class PrefixRoute extends BaseRoute implements PrefixRouteInterface
{
/** @var string[] List of static URI path prefixes*/
private $prefixes;
/**
* Create a new PrefixRoute for a given prefix or prefixes and a handler class.
*
* @param string|string[] $prefix Path or list of paths the request must match
* @param mixed $target Handler to dispatch
* @throws \InvalidArgumentException
*
* @see BaseRoute for details about $target
*/
public function __construct($prefix, $target)
{
parent::__construct($target);
if (is_string($prefix)) {
$this->prefixes = array($prefix);
} elseif (is_array($prefix)) {
$this->prefixes = $prefix;
} else {
throw new InvalidArgumentException("$prefix must be a string or array of string");
}
}
// ------------------------------------------------------------------------
/* HandlerInterface */
/**
* Return the handled response.
*
* @param RequestInterface $request The request to respond to.
* @param array|null $args Optional additional arguments.
* @return ResponseInterface
*/
public function getResponse(RequestInterface $request, array $args = null)
{
$requestPath = $request->getPath();
foreach ($this->prefixes as $prefix) {
if (strrpos($requestPath, $prefix, -strlen($requestPath)) !== false) {
return $this->getResponseFromTarget($request, $args);
}
}
return null;
}
// ------------------------------------------------------------------------
/* PrefixRouteInterface */
/**
* Returns the path prefixes the instance maps to a target handler.
*
* @return string[] List array of path prefixes.
*/
public function getPrefixes()
{
return $this->prefixes;
}
}

View File

@ -1,79 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\RegexRout
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Routes;
use pjdietz\WellRESTed\Exceptions\ParseException;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
/**
* Maps a regular expression pattern for a URI path to a Handler
*/
class RegexRoute extends BaseRoute
{
/** @var string Regular expression pattern for the route. */
private $pattern;
/**
* Create a new route mapping a regex pattern to a handler.
*
* @param string $pattern Regular expression the path must match.
* @param mixed $target Handler to dispatch
* @throws \InvalidArgumentException
*
* @see BaseRoute for details about $target
*/
public function __construct($pattern, $target)
{
parent::__construct($target);
$this->pattern = $pattern;
}
// ------------------------------------------------------------------------
/* HandlerInterface */
/**
* Return the handled response or null.
*
* A null return value indicates that this route failed to match the request.
*
* @param RequestInterface $request The request to respond to.
* @param array|null $args Optional additional arguments.
* @return ResponseInterface The handled response.
* @throws ParseException
*/
public function getResponse(RequestInterface $request, array $args = null)
{
$matched = @preg_match($this->getPattern(), $request->getPath(), $matches);
if ($matched) {
if (is_null($args)) {
$args = array();
}
$args = array_merge($args, $matches);
return $this->getResponseFromTarget($request, $args);
} elseif ($matched === false) {
throw new ParseException("Invalid regular expression: " . $this->getPattern());
}
return null;
}
// ------------------------------------------------------------------------
/**
* Return the regex pattern for the route.
*
* @return string Regex pattern
*/
protected function getPattern()
{
return $this->pattern;
}
}

View File

@ -1,71 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\RouteCreator
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Routes;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use ReflectionClass;
/**
* Class for creating routes
*/
class RouteFactory
{
/**
* Create and return a route given a string path, a handler, and optional
* extra arguments.
*
* The method will determine the most appropriate route subclass to use
* and will forward the arguments on to the subclass's constructor.
*
* - Paths with no special characters will generate StaticRoutes
* - Paths ending with * will generate PrefixRoutes
* - Paths containing URI variables (e.g., {id}) will generate TemplateRoutes
* - Regular exressions will generate RegexRoutes
*
* @param mixed
* @return HandlerInterface
*/
public function createRoute()
{
$args = func_get_args();
$path = $args[0];
if ($path[0] === "/") {
// Possible static, prefix, or template
// PrefixRoutes end with *
if (substr($path, -1) === "*") {
// Remove the trailing *, since the PrefixRoute constructor doesn't expect it.
$path = substr($path, 0, -1);
$constructorArgs = $args;
$constructorArgs[0] = $path;
$reflector = new ReflectionClass("\\pjdietz\\WellRESTed\\Routes\\PrefixRoute");
return $reflector->newInstanceArgs($constructorArgs);
}
// TempalateRoutes contain {variable}
if (preg_match(TemplateRoute::URI_TEMPLATE_EXPRESSION_RE, $path)) {
$reflector = new ReflectionClass("\\pjdietz\\WellRESTed\\Routes\\TemplateRoute");
return $reflector->newInstanceArgs($args);
}
// StaticRoute
$reflector = new ReflectionClass("\\pjdietz\\WellRESTed\\Routes\\StaticRoute");
return $reflector->newInstanceArgs($args);
}
// Regex
$reflector = new ReflectionClass("\\pjdietz\\WellRESTed\\Routes\\RegexRoute");
return $reflector->newInstanceArgs($args);
}
}

View File

@ -1,77 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\StaticRoute
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Routes;
use InvalidArgumentException;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
use pjdietz\WellRESTed\Interfaces\ResponseInterface;
use pjdietz\WellRESTed\Interfaces\Routes\StaticRouteInterface;
/**
* Maps a list of static URI paths to a Handler
*/
class StaticRoute extends BaseRoute implements StaticRouteInterface
{
/** @var string[] List of static URI paths */
private $paths;
/**
* Create a new StaticRoute for a given path or paths and a handler.
*
* @param string|array $path Path or list of paths the request must match
* @param mixed $target Handler to dispatch
* @throws \InvalidArgumentException
*
* @see BaseRoute for details about $target
*/
public function __construct($path, $target)
{
parent::__construct($target);
if (is_string($path)) {
$this->paths = array($path);
} elseif (is_array($path)) {
$this->paths = $path;
} else {
throw new InvalidArgumentException("$path must be a string or array of strings");
}
}
// ------------------------------------------------------------------------
/* HandlerInterface */
/**
* Return the handled response.
*
* @param RequestInterface $request The request to respond to.
* @param array|null $args Optional additional arguments.
* @return ResponseInterface
*/
public function getResponse(RequestInterface $request, array $args = null)
{
if (in_array($request->getPath(), $this->paths)) {
return $this->getResponseFromTarget($request, $args);
}
return null;
}
// ------------------------------------------------------------------------
/* StaticRouteInterface */
/**
* Returns the paths the instance maps to a target handler.
*
* @return string[] List array of paths.
*/
public function getPaths()
{
return $this->paths;
}
}

View File

@ -1,105 +0,0 @@
<?php
/**
* pjdietz\WellRESTed\TemplateRoute
*
* @author PJ Dietz <pj@pjdietz.com>
* @copyright Copyright 2015 by PJ Dietz
* @license MIT
*/
namespace pjdietz\WellRESTed\Routes;
/**
* Maps a URI template to a Handler
*/
class TemplateRoute extends RegexRoute
{
/**
* Regular expression matching URL friendly characters (i.e., letters,
* digits, hyphen and underscore)
*/
const RE_SLUG = '[0-9a-zA-Z\-_]+';
/** Regular expression matching digitis */
const RE_NUM = '[0-9]+';
/** Regular expression matching letters */
const RE_ALPHA = '[a-zA-Z]+';
/** Regular expression matching letters and digits */
const RE_ALPHANUM = '[0-9a-zA-Z]+';
/** Regular expression matching a URI template variable (e.g., {id}) */
const URI_TEMPLATE_EXPRESSION_RE = '/{([[a-zA-Z][a-zA-Z0-_]*)}/';
/**
* Create a new route that matches a URI template to a handler.
*
* Optionally provide patterns for the variables in the template.
*
* @param string $template URI template the path must match
* @param mixed $target Handler to dispatch
* @param string $defaultPattern Regular expression for variables
* @param array $variablePatterns Map of variable names and partial regular expression
*
* @see BaseRoute for details about $target
*/
public function __construct(
$template,
$target,
$defaultPattern = self::RE_SLUG,
$variablePatterns = null
) {
$pattern = $this->buildPattern($template, $defaultPattern, $variablePatterns);
parent::__construct($pattern, $target);
}
/**
* Translate the URI template into a regular expression.
*
* @param string $template URI template the path must match
* @param string $defaultPattern Regular expression for variables
* @param array $variablePatterns Map of variable names and regular expression
* @return string
*/
private function buildPattern($template, $defaultPattern, $variablePatterns)
{
// Ensure $variablePatterns is an array.
if (is_null($variablePatterns)) {
$variablePatterns = array();
} elseif (is_object($variablePatterns)) {
$variablePatterns = (array) $variablePatterns;
}
// Ensure a default is set.
if (!$defaultPattern) {
$defaultPattern = self::RE_SLUG;
}
// Convert the template into the pattern
$pattern = $template;
// Escape allowable characters with regex meaning.
$pattern = str_replace(
array("-", "."),
array("\\-", "\\."),
$pattern);
// Replace * with .* AFTER escaping to avoid escaping .*
$pattern = str_replace("*", ".*", $pattern);
// Surround the pattern with delimiters.
$pattern = "~^{$pattern}$~";
// Replace all template variables with matching subpatterns.
$callback = function ($matches) use ($variablePatterns, $defaultPattern) {
$key = $matches[1];
if (isset($variablePatterns[$key])) {
$pattern = $variablePatterns[$key];
} else {
$pattern = $defaultPattern;
}
return "(?<{$key}>{$pattern})";
};
$pattern = preg_replace_callback(self::URI_TEMPLATE_EXPRESSION_RE, $callback, $pattern);
return $pattern;
}
}

View File

@ -1,242 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use Faker\Factory;
use pjdietz\ShamServer\ShamServer;
use pjdietz\WellRESTed\Client;
use pjdietz\WellRESTed\Request;
/**
* @covers pjdietz\WellRESTed\Client
*/
class ClientTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider httpMethodProvider
*/
public function testSendsHttpMethod($method)
{
$host = "localhost";
$port = $this->getRandomNumberInRange(getenv("PORT"));
$script = realpath(__DIR__ . "/../../sham-routers/method.php");
$server = new ShamServer($host, $port, $script);
$rqst = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$rqst->getUri()->willReturn("http://$host:$port");
$rqst->getMethod()->willReturn($method);
$rqst->getPort()->willReturn($port);
$rqst->getHeaders()->willReturn([]);
$rqst->getBody()->willReturn(null);
$client = new Client();
$resp = $client->request($rqst->reveal());
$body = trim($resp->getBody());
$this->assertEquals($method, $body);
$server->stop();
}
public function httpMethodProvider()
{
return [
["GET"],
["POST"],
["PUT"],
["DELETE"],
["PATCH"],
["OPTIONS"]
];
}
/**
* @dataProvider httpHeaderProvider
*/
public function testSendsHttpHeaders($headerKey, $headerValue)
{
$host = "localhost";
$port = $this->getRandomNumberInRange(getenv("PORT"));
$script = realpath(__DIR__ . "/../../sham-routers/headers.php");
$server = new ShamServer($host, $port, $script);
$rqst = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$rqst->getUri()->willReturn("http://$host:$port");
$rqst->getMethod()->willReturn("GET");
$rqst->getPort()->willReturn($port);
$rqst->getHeaders()->willReturn([$headerKey => $headerValue]);
$rqst->getBody()->willReturn(null);
$client = new Client();
$resp = $client->request($rqst->reveal());
$headers = json_decode($resp->getBody());
$this->assertEquals($headerValue, $headers->{$headerKey});
$server->stop();
}
public function httpHeaderProvider()
{
return [
["Cache-Control", "max-age=0"],
["X-Custom-Header", "custom value"],
["Accept-Charset", "utf-8"]
];
}
/**
* @dataProvider bodyProvider
*/
public function testSendsBody($body)
{
$host = "localhost";
$port = $this->getRandomNumberInRange(getenv("PORT"));
$script = realpath(__DIR__ . "/../../sham-routers/body.php");
$server = new ShamServer($host, $port, $script);
$rqst = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$rqst->getUri()->willReturn("http://$host:$port");
$rqst->getMethod()->willReturn("POST");
$rqst->getPort()->willReturn($port);
$rqst->getHeaders()->willReturn([]);
$rqst->getBody()->willReturn($body);
$client = new Client();
$resp = $client->request($rqst->reveal());
$this->assertEquals($body, $resp->getBody());
$server->stop();
}
public function bodyProvider()
{
$faker = Factory::create();
return [
[$faker->text()],
[$faker->text()],
[$faker->text()]
];
}
/**
* @dataProvider formProvider
*/
public function testSendsForm($form)
{
$host = "localhost";
$port = $this->getRandomNumberInRange(getenv("PORT"));
$script = realpath(__DIR__ . "/../../sham-routers/formFields.php");
$server = new ShamServer($host, $port, $script);
$rqst = new Request("http://$host:$port");
$rqst->setMethod("POST");
$rqst->setFormFields($form);
$client = new Client();
$resp = $client->request($rqst);
$body = json_decode($resp->getBody(), true);
$this->assertEquals($form, $body);
$server->stop();
}
public function formProvider()
{
$faker = Factory::create();
return [
[
[
"firstName" => $faker->firstName,
"lastName" => $faker->lastName,
"email" => $faker->email
]
],
];
}
public function testSetsCustomCurlOptionsOnInstantiation()
{
$host = "localhost";
$port = $this->getRandomNumberInRange(getenv("PORT"));
$script = realpath(__DIR__ . "/../../sham-routers/headers.php");
$server = new ShamServer($host, $port, $script);
$rqst = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$rqst->getUri()->willReturn("http://$host:$port");
$rqst->getMethod()->willReturn("GET");
$rqst->getPort()->willReturn($port);
$rqst->getHeaders()->willReturn([]);
$rqst->getBody()->willReturn(null);
$cookieValue = "key=value";
$client = new Client([CURLOPT_COOKIE => $cookieValue]);
$resp = $client->request($rqst->reveal());
$headers = json_decode($resp->getBody());
$this->assertEquals($cookieValue, $headers->Cookie);
$server->stop();
}
public function testSetsCustomCurlOptionsOnRequest()
{
$host = "localhost";
$port = $this->getRandomNumberInRange(getenv("PORT"));
$script = realpath(__DIR__ . "/../../sham-routers/headers.php");
$server = new ShamServer($host, $port, $script);
$rqst = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$rqst->getUri()->willReturn("http://$host:$port");
$rqst->getMethod()->willReturn("GET");
$rqst->getPort()->willReturn($port);
$rqst->getHeaders()->willReturn([]);
$rqst->getBody()->willReturn(null);
$cookieValue = "key=value";
$client = new Client();
$resp = $client->request($rqst->reveal(), [CURLOPT_COOKIE => $cookieValue]);
$headers = json_decode($resp->getBody());
$this->assertEquals($cookieValue, $headers->Cookie);
$server->stop();
}
/**
* @dataProvider curlErrorProvider
* @expectedException \pjdietz\WellRESTed\Exceptions\CurlException
*/
public function testThrowsCurlException($uri, $opts)
{
$rqst = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$rqst->getUri()->willReturn($uri);
$rqst->getMethod()->willReturn("GET");
$rqst->getPort()->willReturn(parse_url($uri, PHP_URL_PORT));
$rqst->getHeaders()->willReturn([]);
$rqst->getBody()->willReturn(null);
$client = new Client();
$client->request($rqst->reveal(), $opts);
}
public function curlErrorProvider()
{
$port = $this->getRandomNumberInRange(getenv("FAIL_PORT"));
return [
["http://localhost:{$port}", [
CURLOPT_FAILONERROR, true,
CURLOPT_TIMEOUT_MS, 10
]],
];
}
private function getRandomNumberInRange($range)
{
static $pattern = '/(\d+)\-(\d+)/';
if (preg_match($pattern, $range, $matches)) {
$lower = $matches[1];
$upper = $matches[2];
return rand($lower, $upper);
} else {
return $range;
}
}
}

View File

@ -1,83 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\Exceptions\HttpExceptions\NotFoundException;
use pjdietz\WellRESTed\Handler;
/**
* @covers pjdietz\WellRESTed\Handler
*/
class HandlerTest extends \PHPUnit_Framework_TestCase
{
public function testReturnsResponse()
{
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$handler = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Handler");
$response = $handler->getResponse($request->reveal());
$this->assertNotNull($response);
}
/**
* @dataProvider verbProvider
*/
public function testCallsMethodForHttpVerb($method)
{
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$request->getMethod()->willReturn($method);
$handler = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Handler");
$response = $handler->getResponse($request->reveal());
$this->assertNotNull($response);
}
public function verbProvider()
{
return [
["GET"],
["POST"],
["PUT"],
["DELETE"],
["HEAD"],
["PATCH"],
["OPTIONS"],
["NOTALLOWED"]
];
}
public function testTranslatesHttpExceptionToResponse()
{
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$request->getMethod()->willReturn("GET");
$handler = new ExceptionHandler();
$response = $handler->getResponse($request->reveal());
$this->assertEquals(404, $response->getStatusCode());
}
public function testProvidesAllowHeader()
{
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$request->getMethod()->willReturn("OPTIONS");
$handler = new OptionsHandler();
$response = $handler->getResponse($request->reveal());
$this->assertEquals("GET, POST", $response->getHeader("Allow"));
}
}
class OptionsHandler extends Handler
{
protected function getAllowedMethods()
{
return ["GET","POST"];
}
}
class ExceptionHandler extends Handler
{
protected function get()
{
throw new NotFoundException();
}
}

View File

@ -1,70 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\HandlerUnpacker;
use pjdietz\WellRESTed\Interfaces\HandlerInterface;
use pjdietz\WellRESTed\Interfaces\RequestInterface;
/**
* @covers pjdietz\WellRESTed\HandlerUnpacker
*/
class HandlerUnpackerTest extends \PHPUnit_Framework_TestCase
{
public function testUnpacksFromCallable()
{
$handlerContainer = function () {
return new HandlerUnpackerTest_Handler();
};
$handlerUnpacker = new HandlerUnpacker();
$handler = $handlerUnpacker->unpack($handlerContainer);
$this->assertInstanceOf("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface", $handler);
}
public function testPropagatesArgumentsToCallable()
{
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$args = [
"cat" => "Molly"
];
$callableRequest = null;
$callableArguments = null;
$handlerCallable = function ($rqst, $args) use (&$callableRequest, &$callableArguments) {
$callableRequest = $rqst;
$callableArguments = $args;
return null;
};
$handlerUnpacker = new HandlerUnpacker();
$handlerUnpacker->unpack($handlerCallable, $request->reveal(), $args);
$this->assertSame($callableRequest, $request->reveal());
$this->assertSame($callableArguments, $args);
}
public function testUnpacksFromString()
{
$handlerContainer = __NAMESPACE__ . "\\HandlerUnpackerTest_Handler";
$handlerUnpacker = new HandlerUnpacker();
$handler = $handlerUnpacker->unpack($handlerContainer);
$this->assertInstanceOf("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface", $handler);
}
public function testUnpacksInstance()
{
$handler = new HandlerUnpackerTest_Handler();
$handlerUnpacker = new HandlerUnpacker();
$handler = $handlerUnpacker->unpack($handler);
$this->assertInstanceOf("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface", $handler);
}
}
class HandlerUnpackerTest_Handler implements HandlerInterface
{
public function getResponse(RequestInterface $request, array $args = null)
{
return null;
}
}

View File

@ -1,95 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
/**
* @covers pjdietz\WellRESTed\Message
*/
class MessageTest extends \PHPUnit_Framework_TestCase
{
public function testSetsBody()
{
$message = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Message");
$body = "This is the body";
$message->setBody($body);
$this->assertEquals($body, $message->getBody());
}
public function testBodyIsNullByDefault()
{
$message = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Message");
$this->assertNull($message->getBody());
}
/**
* @dataProvider headerProvider
*/
public function testSetsHeader($headerKey, $headerValue, $badCapsKey)
{
$message = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Message");
$message->setHeader($headerKey, $headerValue);
$this->assertEquals($headerValue, $message->getHeader($badCapsKey));
}
/**
* @dataProvider headerProvider
*/
public function testUpdatesHeader($headerKey, $headerValue, $testName)
{
$message = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Message");
$message->setHeader($headerKey, $headerValue);
$newValue = "newvalue";
$message->setHeader($testName, "newvalue");
$this->assertEquals($newValue, $message->getHeader($testName));
}
/**
* @dataProvider headerProvider
*/
public function testNonsetHeaderIsNull()
{
$message = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Message");
$this->assertNull($message->getHeader("no-header"));
}
/**
* @dataProvider headerProvider
*/
public function testUnsetHeaderIsNull($headerKey, $headerValue, $testName)
{
$message = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Message");
$message->setHeader($headerKey, $headerValue);
$message->unsetHeader($testName);
$this->assertNull($message->getHeader($headerKey));
}
/**
* @dataProvider headerProvider
*/
public function testChecksIfHeaderIsSet($headerKey, $headerValue, $testName)
{
$message = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Message");
$message->setHeader($headerKey, $headerValue);
$this->assertTrue($message->issetHeader($testName));
}
public function headerProvider()
{
return [
["Accept-Charset", "utf-8", "accept-charset"],
["Accept-Encoding", "gzip, deflate", "ACCEPT-ENCODING"],
["Cache-Control", "no-cache", "Cache-Control"],
];
}
public function testReturnsListOfHeaders()
{
$message = $this->getMockForAbstractClass("\\pjdietz\\WellRESTed\\Message");
$headers = $this->headerProvider();
foreach ($headers as $header) {
$message->setHeader($header[0], $header[1]);
}
$this->assertEquals(count($headers), count($message->getHeaders()));
}
}

View File

@ -1,404 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use Faker\Factory;
use pjdietz\WellRESTed\Request;
use pjdietz\WellRESTed\Test;
/**
* @covers pjdietz\WellRESTed\Request
*/
class RequestTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider methodProvider
*/
public function testSetsMethod($method)
{
$rqst = new Request();
$rqst->setMethod($method);
$this->assertEquals($method, $rqst->getMethod());
}
public function methodProvider()
{
return [
["GET"],
["POST"],
["PUT"],
["DELETE"],
["OPTIONS"],
["HEAD"]
];
}
/**
* @dataProvider uriProvider
*/
public function testSetsUri($uri, $data)
{
$rqst = new Request($uri);
$this->assertEquals($data->uri, $rqst->getUri());
}
/**
* @dataProvider uriProvider
*/
public function testParsesSchemeFromUri($uri, $data)
{
$rqst = new Request($uri);
$this->assertEquals($data->scheme, $rqst->getScheme());
}
/**
* @dataProvider uriProvider
*/
public function testParsesHostnameFromUri($uri, $data)
{
$rqst = new Request($uri);
$this->assertEquals($data->hostname, $rqst->getHostname());
}
/**
* @dataProvider uriProvider
*/
public function testParsesPortFromUri($uri, $data)
{
$rqst = new Request($uri);
$this->assertEquals($data->port, $rqst->getPort());
}
/**
* @dataProvider uriProvider
*/
public function testParsesPathFromUri($uri, $data)
{
$rqst = new Request($uri);
$this->assertEquals($data->path, $rqst->getPath());
}
/**
* @dataProvider uriProvider
*/
public function testParsesPathPartsFromUri($uri, $data)
{
$rqst = new Request($uri);
$this->assertEquals($data->parts, $rqst->getPathParts());
}
/**
* @dataProvider uriProvider
*/
public function testParsesQueryFromUri($uri, $data)
{
$rqst = new Request($uri);
$this->assertEquals($data->query, $rqst->getQuery());
}
public function uriProvider()
{
return [
[
"http://www.google.com",
(object) [
"uri" => "http://www.google.com",
"scheme" => "http",
"hostname" => "www.google.com",
"port" => 80,
"path" => "/",
"query" => [],
"parts" => []
]
],
[
"https://www.google.com",
(object) [
"uri" => "https://www.google.com",
"scheme" => "https",
"hostname" => "www.google.com",
"port" => 443,
"path" => "/",
"query" => [],
"parts" => []
]
],
[
"localhost:8080/my/path/with/parts",
(object) [
"uri" => "http://localhost:8080/my/path/with/parts",
"scheme" => "http",
"hostname" => "localhost",
"port" => 8080,
"path" => "/my/path/with/parts",
"query" => [],
"parts" => ["my", "path", "with", "parts"]
]
],
[
"localhost?dog=bear&cat=molly",
(object) [
"uri" => "http://localhost?cat=molly&dog=bear",
"scheme" => "http",
"hostname" => "localhost",
"port" => 80,
"path" => "/",
"query" => [
"cat" => "molly",
"dog" => "bear"
],
"parts" => []
]
],
[
"/my-page?id=2",
(object) [
"uri" => "http://localhost/my-page?id=2",
"scheme" => "http",
"hostname" => "localhost",
"port" => 80,
"path" => "/my-page",
"query" => [
"id" => "2"
],
"parts" => ["my-page"]
]
]
];
}
/**
* @dataProvider formProvider
*/
public function testEncodesFormFields($form)
{
$rqst = new Request();
$rqst->setFormFields($form);
$body = $rqst->getBody();
parse_str($body, $fields);
$this->assertEquals($form, $fields);
}
/**
* @dataProvider formProvider
*/
public function testDecodesFormFields($form)
{
$rqst = new Request();
$rqst->setFormFields($form);
$fields = $rqst->getFormFields();
$this->assertEquals($form, $fields);
}
public function formProvider()
{
$faker = Factory::create();
return [
[
[
"firstName" => $faker->firstName,
"lastName" => $faker->lastName,
"username" => $faker->userName
]
]
];
}
/**
* @dataProvider queryProvider
*/
public function testSetsQuery($input, $expected)
{
$rqst = new Request();
$rqst->setQuery($input);
$this->assertEquals($expected, $rqst->getQuery());
}
public function queryProvider()
{
return [
[
"cat=molly&dog=bear",
[
"cat" => "molly",
"dog" => "bear"
]
],
[
["id" => "1"],
["id" => "1"]
],
[
(object)["dog" => "bear"],
["dog" => "bear"]
],
["", []],
[[], []],
];
}
/**
* @dataProvider invalidQueryProvider
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionOnInvalidQuery($query)
{
$rqst = new Request();
$rqst->setQuery($query);
}
public function invalidQueryProvider()
{
return [
[11],
[false],
[true],
[null]
];
}
/**
* @dataProvider invalidSchemeProvider
* @expectedException \UnexpectedValueException
*/
public function testThrowsExceptionOnInvalidScheme($scheme)
{
$rqst = new Request();
$rqst->setScheme($scheme);
}
public function invalidSchemeProvider()
{
return [
[""],
["ftp"],
["ssh"],
[null],
[0]
];
}
/**
* @dataProvider defaultPortProvider
*/
public function testSetsDefaultPort($scheme, $port)
{
$rqst = new Request("http://localhost:9999");
$rqst->setScheme($scheme);
$rqst->setPort();
$this->assertEquals($port, $rqst->getPort());
}
public function defaultPortProvider()
{
return [
["http", 80],
["https", 443]
];
}
/**
* @dataProvider serverProvider
* @preserveGlobalState disabled
*/
public function testReadsServerRequestMethod($serverVars, $expected)
{
$_SERVER = array_merge($_SERVER, $serverVars);
$rqst = new Request();
$rqst->readHttpRequest();
$this->assertEquals($expected->method, $rqst->getMethod());
}
/**
* @dataProvider serverProvider
* @preserveGlobalState disabled
*/
public function testReadsServerRequestHost($serverVars, $expected)
{
$_SERVER = array_merge($_SERVER, $serverVars);
$rqst = new Request();
$rqst->readHttpRequest();
$this->assertEquals($expected->host, $rqst->getHostname());
}
/**
* @dataProvider serverProvider
* @preserveGlobalState disabled
*/
public function testReadsServerRequestPath($serverVars, $expected)
{
$_SERVER = array_merge($_SERVER, $serverVars);
$rqst = new Request();
$rqst->readHttpRequest();
$this->assertEquals($expected->path, $rqst->getPath());
}
/**
* @dataProvider serverProvider
* @preserveGlobalState disabled
*/
public function testReadsServerRequestHeaders($serverVars, $expected)
{
$_SERVER = array_merge($_SERVER, $serverVars);
$rqst = new Request();
$rqst->readHttpRequest();
foreach ($expected->headers as $name => $value) {
$this->assertEquals($value, $rqst->getHeader($name));
}
}
/**
* @preserveGlobalState disabled
*/
public function testReadsStaticRequest()
{
$data = $this->serverProvider();
$serverVars = $data[0][0];
$expected = $data[0][1];
$_SERVER = array_merge($_SERVER, $serverVars);
$rqst = Request::getRequest();
$this->assertEquals($expected->host, $rqst->getHostname());
$rqst2 = Request::getRequest();
$this->assertSame($rqst2, $rqst);
}
public function serverProvider()
{
return [
[
[
"REQUEST_METHOD" => "GET",
"REQUEST_URI" => "/",
"HTTP_ACCEPT_CHARSET" => "utf-8",
"HTTP_HOST" => "localhost"
],
(object) [
"method" => "GET",
"host" => "localhost",
"path" => "/",
"headers" => [
"Accept-charset" => "utf-8"
]
]
],
[
[
"REQUEST_METHOD" => "POST",
"REQUEST_URI" => "/my/page",
"HTTP_ACCEPT_CHARSET" => "utf-8",
"HTTP_HOST" => "mysite.com"
],
(object) [
"method" => "POST",
"host" => "mysite.com",
"path" => "/my/page",
"headers" => [
"Accept-charset" => "utf-8"
]
]
]
];
}
}

View File

@ -1,213 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use Faker\Factory;
use pjdietz\WellRESTed\Response;
/**
* @covers pjdietz\WellRESTed\Response
*/
class ResponseTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider statusCodeProvider
*/
public function testSetsStatusCodeInConstructor($statusCode, $reasonPhrase, $statusLine)
{
$resp = new Response($statusCode);
$this->assertEquals($statusCode, $resp->getStatusCode());
}
/**
* @dataProvider statusCodeProvider
*/
public function testProvidesReasonPhrase($statusCode, $reasonPhrase, $statusLine)
{
$resp = new Response();
$resp->setStatusCode($statusCode, $reasonPhrase);
$this->assertEquals(substr($statusLine, 13), $resp->getReasonPhrase());
}
/**
* @dataProvider statusCodeProvider
*/
public function testProvidesStatusLine($statusCode, $reasonPhrase, $statusLine)
{
$resp = new Response();
$resp->setStatusCode($statusCode, $reasonPhrase);
$this->assertEquals($statusLine, $resp->getStatusLine());
}
/**
* @dataProvider statusCodeProvider
*/
public function testDeterminesSuccessFromStatusCode($statusCode, $reasonPhrase, $statusLine)
{
$resp = new Response();
$resp->setStatusCode($statusCode, $reasonPhrase);
if ($statusCode < 400) {
$this->assertTrue($resp->getSuccess());
} else {
$this->assertFalse($resp->getSuccess());
}
}
public function statusCodeProvider()
{
return [
[100, null, "HTTP/1.1 100 Continue"],
[101, null, "HTTP/1.1 101 Switching Protocols"],
[200, null, "HTTP/1.1 200 OK"],
[201, null, "HTTP/1.1 201 Created"],
[202, null, "HTTP/1.1 202 Accepted"],
[203, null, "HTTP/1.1 203 Non-Authoritative Information"],
[204, null, "HTTP/1.1 204 No Content"],
[205, null, "HTTP/1.1 205 Reset Content"],
[206, null, "HTTP/1.1 206 Partial Content"],
[300, null, "HTTP/1.1 300 Multiple Choices"],
[301, null, "HTTP/1.1 301 Moved Permanently"],
[302, null, "HTTP/1.1 302 Found"],
[303, null, "HTTP/1.1 303 See Other"],
[304, null, "HTTP/1.1 304 Not Modified"],
[305, null, "HTTP/1.1 305 Use Proxy"],
[400, null, "HTTP/1.1 400 Bad Request"],
[401, null, "HTTP/1.1 401 Unauthorized"],
[402, null, "HTTP/1.1 402 Payment Required"],
[403, null, "HTTP/1.1 403 Forbidden"],
[404, null, "HTTP/1.1 404 Not Found"],
[405, null, "HTTP/1.1 405 Method Not Allowed"],
[406, null, "HTTP/1.1 406 Not Acceptable"],
[407, null, "HTTP/1.1 407 Proxy Authentication Required"],
[408, null, "HTTP/1.1 408 Request Timeout"],
[409, null, "HTTP/1.1 409 Conflict"],
[410, null, "HTTP/1.1 410 Gone"],
[411, null, "HTTP/1.1 411 Length Required"],
[412, null, "HTTP/1.1 412 Precondition Failed"],
[413, null, "HTTP/1.1 413 Request Entity Too Large"],
[414, null, "HTTP/1.1 414 Request-URI Too Long"],
[415, null, "HTTP/1.1 415 Unsupported Media Type"],
[500, null, "HTTP/1.1 500 Internal Server Error"],
[501, null, "HTTP/1.1 501 Not Implemented"],
[502, null, "HTTP/1.1 502 Bad Gateway"],
[503, null, "HTTP/1.1 503 Service Unavailable"],
[504, null, "HTTP/1.1 504 Gateway Timeout"],
[505, null, "HTTP/1.1 505 HTTP Version Not Supported"],
[598, null, "HTTP/1.1 598 Nonstandard"],
[599, "Smelly", "HTTP/1.1 599 Smelly"]
];
}
/**
* @dataProvider invalidReasonPhraseProvider
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionOnInvalidReasonPhrase($statusCode, $reasonPhrase)
{
$resp = new Response();
$resp->setStatusCode($statusCode, $reasonPhrase);
}
public function invalidReasonPhraseProvider()
{
return [
[599, false],
["100", true],
["*", []]
];
}
public function testSetsBody()
{
$faker = Factory::create();
$body = $faker->text();
$resp = new Response();
$resp->setBody($body);
$this->assertEquals($body, $resp->getBody());
}
public function testSetsBodyInConstructor()
{
$faker = Factory::create();
$body = $faker->text();
$resp = new Response(200, $body);
$this->assertEquals($body, $resp->getBody());
}
public function testSetsBodyFile()
{
$path = tempnam(sys_get_temp_dir(), "TST");
$resp = new Response();
$resp->setBodyFilePath($path);
$this->assertEquals($path, $resp->getBodyFilePath());
unlink($path);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testOutputsResponse()
{
$faker = Factory::create();
$body = $faker->text();
$resp = new Response(200, $body, ["Content-type" => "text/plain"]);
ob_start();
$resp->respond();
$captured = ob_get_contents();
ob_end_clean();
$this->assertEquals($body, $captured);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testOutputsResponseFromFile()
{
$path = tempnam(sys_get_temp_dir(), "TST");
$faker = Factory::create();
$body = $faker->text();
$f = fopen($path, "w");
fwrite($f, $body);
fclose($f);
$resp = new Response();
$resp->setStatusCode(200);
$resp->setBodyFilePath($path);
ob_start();
$resp->respond();
$captured = ob_get_contents();
ob_end_clean();
unlink($path);
$this->assertEquals($captured, $body);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testRespondsWithNoBodyWhenResponseFileIsMissing()
{
$path = tempnam(sys_get_temp_dir(), "TST");
$resp = new Response();
$resp->setStatusCode(200);
$resp->setBodyFilePath($path);
unlink($path);
ob_start();
$resp->respond();
$captured = ob_get_contents();
ob_end_clean();
$this->assertEquals("", $captured);
}
}

View File

@ -1,201 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\RouteTable;
use Prophecy\Argument;
/**
* @covers pjdietz\WellRESTed\RouteTable
*/
class RouteTableTest extends \PHPUnit_Framework_TestCase
{
private $handler;
private $request;
private $response;
private $route;
public function setUp()
{
$this->request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$this->response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$this->route = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$this->handler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
}
public function testReturnsNullWhenNoRoutesMatch()
{
$table = new RouteTable();
$response = $table->getResponse($this->request->reveal());
$this->assertNull($response);
}
public function testMatchesStaticRoute()
{
$this->route->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\StaticRouteInterface");
$this->route->getPaths()->willReturn(["/cats/"]);
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/cats/");
$table = new RouteTable();
$table->addRoute($this->route->reveal());
$table->getResponse($this->request->reveal());
$this->route->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testMatchesPrefixRoute()
{
$this->route->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\PrefixRouteInterface");
$this->route->getPrefixes()->willReturn(["/cats/"]);
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/cats/molly");
$table = new RouteTable();
$table->addRoute($this->route->reveal());
$table->getResponse($this->request->reveal());
$this->route->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testMatchesBestPrefixRoute()
{
$route1 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route1->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\PrefixRouteInterface");
$route1->getPrefixes()->willReturn(["/animals/"]);
$route1->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$route2 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route2->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\PrefixRouteInterface");
$route2->getPrefixes()->willReturn(["/animals/cats/"]);
$route2->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/animals/cats/molly");
$table = new RouteTable();
$table->addRoute($route1->reveal());
$table->addRoute($route2->reveal());
$table->getResponse($this->request->reveal());
$route1->getResponse(Argument::cetera())->shouldNotHaveBeenCalled();
$route2->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testMatchesStaticRouteBeforePrefixRoute()
{
$route1 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route1->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\PrefixRouteInterface");
$route1->getPrefixes()->willReturn(["/animals/cats/"]);
$route1->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$route2 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route2->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\StaticRouteInterface");
$route2->getPaths()->willReturn(["/animals/cats/molly"]);
$route2->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/animals/cats/molly");
$table = new RouteTable();
$table->addRoute($route1->reveal());
$table->addRoute($route2->reveal());
$table->getResponse($this->request->reveal());
$route1->getResponse(Argument::cetera())->shouldNotHaveBeenCalled();
$route2->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testMatchesPrefixRouteBeforeHandlerRoute()
{
$route1 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route1->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\PrefixRouteInterface");
$route1->getPrefixes()->willReturn(["/animals/cats/"]);
$route1->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$route2 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route2->getResponse(Argument::cetera())->willReturn(null);
$this->request->getPath()->willReturn("/animals/cats/molly");
$table = new RouteTable();
$table->addRoute($route1->reveal());
$table->addRoute($route2->reveal());
$table->getResponse($this->request->reveal());
$route1->getResponse(Argument::cetera())->shouldHaveBeenCalled();
$route2->getResponse(Argument::cetera())->shouldNotHaveBeenCalled();
}
public function testReturnsFirstNonNullResponse()
{
$route1 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route1->getResponse(Argument::cetera())->willReturn(null);
$route2 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route2->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$route3 = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$route3->getResponse(Argument::cetera())->willReturn(null);
$this->request->getPath()->willReturn("/");
$table = new RouteTable();
$table->addRoute($route1->reveal());
$table->addRoute($route2->reveal());
$table->addRoute($route3->reveal());
$response = $table->getResponse($this->request->reveal());
$this->assertNotNull($response);
$route1->getResponse(Argument::cetera())->shouldHaveBeenCalled();
$route2->getResponse(Argument::cetera())->shouldHaveBeenCalled();
$route3->getResponse(Argument::cetera())->shouldNotHaveBeenCalled();
}
public function testPropagatesArgumentsToStaticRoute()
{
$this->route->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\StaticRouteInterface");
$this->route->getPaths()->willReturn(["/cats/"]);
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/cats/");
$args = ["cat" => "molly"];
$table = new RouteTable();
$table->addRoute($this->route->reveal());
$table->getResponse($this->request->reveal(), $args);
$this->route->getResponse($this->request->reveal(), $args)->shouldHaveBeenCalled();
}
public function testPropagatesArgumentsToPrefixRoute()
{
$this->route->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\PrefixRouteInterface");
$this->route->getPrefixes()->willReturn(["/cats/"]);
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/cats/");
$args = ["cat" => "molly"];
$table = new RouteTable();
$table->addRoute($this->route->reveal());
$table->getResponse($this->request->reveal(), $args);
$this->route->getResponse($this->request->reveal(), $args)->shouldHaveBeenCalled();
}
public function testPropagatesArwgumentsToRoute()
{
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/cats/");
$args = ["cat" => "molly"];
$table = new RouteTable();
$table->addRoute($this->route->reveal());
$table->getResponse($this->request->reveal(), $args);
$this->route->getResponse($this->request->reveal(), $args)->shouldHaveBeenCalled();
}
}

View File

@ -1,323 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\Exceptions\HttpExceptions\HttpException;
use pjdietz\WellRESTed\Router;
use pjdietz\WellRESTed\Routes\TemplateRoute;
use Prophecy\Argument;
/**
* @covers pjdietz\WellRESTed\Router
*/
class RouterTest extends \PHPUnit_Framework_TestCase
{
private $handler;
private $request;
private $response;
private $route;
public function setUp()
{
$this->request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$this->request->getPath()->willReturn("/");
$this->request->getMethod()->willReturn("GET");
$this->response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$this->route = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$this->handler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
}
public function testAddsSingleRoute()
{
$this->request->getPath()->willReturn("/cats/");
$this->handler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$router = new Router();
$router->add("/cats/", $this->handler->reveal());
$response = $router->getResponse($this->request->reveal());
$this->assertNotNull($response);
}
/**
* @dataProvider pathProvider
*/
public function testAddsMultpleRoutes($path, $exptectedSuccess)
{
$this->request->getPath()->willReturn($path);
$this->handler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$router = new Router();
$router->add(
["/cats/", $this->handler->reveal()],
["/cats/*", $this->handler->reveal()],
["/dogs/{name}", $this->handler->reveal(), TemplateRoute::RE_ALPHA],
["~/hamsters/[a-z]+~", $this->handler->reveal()]
);
$response = $router->getResponse($this->request->reveal());
$this->assertEquals($exptectedSuccess, !is_null($response));
}
public function pathProvider()
{
return [
["/cats/", true],
["/cats/molly", true],
["/dogs/bear", true],
["/hamsters/fizzgig", true],
["/dogs/", false],
["/birds/", false],
["/hamsters/23", false]
];
}
public function testAddsSingleRouteInstance()
{
$router = new Router();
$router->addRoute($this->route->reveal());
$router->getResponse($this->request->reveal());
$this->route->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testAddsMultipleRouteInstances()
{
$router = new Router();
$router->addRoutes([$this->route->reveal()]);
$router->getResponse($this->request->reveal());
$this->route->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testPropagatesArgumentsToRouteTable()
{
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/cats/");
$args = ["cat" => "molly"];
$router = new Router();
$router->addRoute($this->route->reveal());
$router->getResponse($this->request->reveal(), $args);
$this->route->getResponse($this->request->reveal(), $args)->shouldHaveBeenCalled();
}
public function testRespondsWithErrorResponseForHttpException()
{
$this->route->getResponse(Argument::cetera())->willThrow(new HttpException());
$router = new Router();
$router->addRoute($this->route->reveal());
$response = $router->getResponse($this->request->reveal());
$this->assertEquals(500, $response->getStatusCode());
}
public function testDispatchesErrorHandlerForStatusCode()
{
$this->response->getStatusCode()->willReturn(403);
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$errorHandler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$errorHandler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$router = new Router();
$router->addRoute($this->route->reveal());
$router->setErrorHandlers([403 => $errorHandler->reveal()]);
$router->getResponse($this->request->reveal());
$errorHandler->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testDispatchesErrorHandlerWithOriginalRequest()
{
$this->request->getPath()->willReturn("/");
$this->response->getStatusCode()->willReturn(403);
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$errorHandler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$errorHandler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$request = $this->request->reveal();
$router = new Router();
$router->addRoute($this->route->reveal());
$router->setErrorHandlers([403 => $errorHandler->reveal()]);
$router->getResponse($request);
$errorHandler->getResponse(
Argument::that(
function ($arg) use ($request) {
return $arg === $request;
}
),
Argument::any()
)->shouldHaveBeenCalled();
}
public function testDispatchesErrorHandlerWithOriginalArguments()
{
$this->request->getPath()->willReturn("/");
$this->response->getStatusCode()->willReturn(403);
$response = $this->response->reveal();
$this->route->getResponse(Argument::cetera())->willReturn($response);
$errorHandler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$errorHandler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$arguments = [
"cat" => "Molly",
"dog" => "Bear"
];
$router = new Router();
$router->addRoute($this->route->reveal());
$router->setErrorHandlers([403 => $errorHandler->reveal()]);
$router->getResponse($this->request->reveal(), $arguments);
$errorHandler->getResponse(
Argument::any(),
Argument::that(
function ($args) use ($arguments) {
return count(array_diff_assoc($arguments, $args)) === 0;
}
)
)->shouldHaveBeenCalled();
}
public function testDispatchesErrorHandlerWithPreviousResponse()
{
$this->request->getPath()->willReturn("/");
$this->response->getStatusCode()->willReturn(403);
$response = $this->response->reveal();
$this->route->getResponse(Argument::cetera())->willReturn($response);
$errorHandler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$errorHandler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$router = new Router();
$router->addRoute($this->route->reveal());
$router->setErrorHandlers([403 => $errorHandler->reveal()]);
$router->getResponse($this->request->reveal());
$errorHandler->getResponse(
Argument::any(),
Argument::that(
function ($arg) use ($response) {
return isset($arg["response"]) && $arg["response"] === $response;
}
)
)->shouldHaveBeenCalled();
}
public function testDispatchesErrorCallable()
{
$this->request->getPath()->willReturn("/");
$this->response->getStatusCode()->willReturn(403);
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$errorResponse = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$errorResponse->respond()->willReturn();
$errorCallable = function () use ($errorResponse) {
return $errorResponse->reveal();
};
$router = new Router();
$router->addRoute($this->route->reveal());
$router->setErrorHandlers([403 => $errorCallable]);
$result = $router->getResponse($this->request->reveal());
$this->assertSame($errorResponse->reveal(), $result);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testRoutesServerRequest()
{
$_SERVER["REQUEST_URI"] = "/cats/";
$_SERVER["HTTP_HOST"] = "localhost";
$_SERVER["REQUEST_METHOD"] = "GET";
$this->response->getStatusCode()->willReturn(200);
$this->response->respond()->willReturn();
$this->route->willImplement("\\pjdietz\\WellRESTed\\Interfaces\\Routes\\StaticRouteInterface");
$this->route->getPaths()->willReturn(["/cats/"]);
$this->route->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$router = new Router();
$router->addRoute($this->route->reveal());
ob_start();
$router->respond();
ob_end_clean();
$this->response->respond()->shouldHaveBeenCalled();
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testRoutesStaticRequestToNoRouteResponse()
{
$_SERVER["REQUEST_URI"] = "/cats/";
$_SERVER["HTTP_HOST"] = "localhost";
$_SERVER["REQUEST_METHOD"] = "GET";
$router = new Router();
ob_start();
$router->respond();
$captured = ob_get_contents();
ob_end_clean();
$this->assertEquals("No resource at /cats/", $captured);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testRoutesStaticRequestTo404ErrorHandler()
{
$_SERVER["REQUEST_URI"] = "/cats/";
$_SERVER["HTTP_HOST"] = "localhost";
$_SERVER["REQUEST_METHOD"] = "GET";
$errorHandler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$errorHandler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$router = new Router();
$router->setErrorHandler(404, $errorHandler->reveal());
ob_start();
$router->respond();
ob_end_clean();
$errorHandler->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testDeprecatedSetsStaticRoute()
{
$this->handler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/cats/");
$router = new Router();
@$router->setStaticRoute(["/cats/"], $this->handler->reveal());
$router->getResponse($this->request->reveal());
$this->handler->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testDeprecatedSetsPrefixRoute()
{
$this->handler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
$this->request->getPath()->willReturn("/cats/molly");
$router = new Router();
@$router->setPrefixRoute(["/cats/"], $this->handler->reveal());
$router->getResponse($this->request->reveal());
$this->handler->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
}

View File

@ -1,74 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\Routes\StaticRoute;
use Prophecy\Argument;
/**
* @covers pjdietz\WellRESTed\Routes\BaseRoute
*/
class BaseRouteTest extends \PHPUnit_Framework_TestCase
{
public function testDispatchesHandlerTarget()
{
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$request->getPath()->willReturn("/");
$response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$handler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$handler->getResponse(Argument::cetera())->willReturn($response->reveal());
$route = new StaticRoute("/", $handler->reveal());
$result = $route->getResponse($request->reveal());
$this->assertSame($response->reveal(), $result);
$handler->getResponse(Argument::cetera())->shouldHaveBeenCalled();
}
public function testDispatchesResponseTarget()
{
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$request->getPath()->willReturn("/");
$response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$route = new StaticRoute("/", $response->reveal());
$result = $route->getResponse($request->reveal());
$this->assertSame($response->reveal(), $result);
}
public function testDispatchesNullTarget()
{
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$request->getPath()->willReturn("/");
$route = new StaticRoute("/", function () { return null; });
$result = $route->getResponse($request->reveal());
$this->assertNull($result);
}
public function testPropagatesArgumentsToCallable()
{
$callableRequest = null;
$callableArgs = null;
$callable = function ($request, $args) use (&$callableRequest, &$callableArgs) {
$callableRequest = $request;
$callableArgs = $args;
};
$request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$request->getPath()->willReturn("/");
$args = ["cat" => "Molly"];
$route = new StaticRoute("/", $callable);
$route->getResponse($request->reveal(), $args);
$this->assertSame($request->reveal(), $callableRequest);
$this->assertSame($args, $callableArgs);
}
}

View File

@ -1,100 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\Routes\PrefixRoute;
use Prophecy\Argument;
/**
* @covers pjdietz\WellRESTed\Routes\PrefixRoute
*/
class PrefixRouteTest extends \PHPUnit_Framework_TestCase
{
private $handler;
private $request;
private $response;
public function testMatchesSinglePathExactly()
{
$this->request->getPath()->willReturn("/cats/");
$route = new PrefixRoute("/cats/", $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNotNull($resp);
}
public function testMatchesSinglePathWithPrefix()
{
$this->request->getPath()->willReturn("/cats/molly");
$route = new PrefixRoute("/cats/", $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNotNull($resp);
}
public function testMatchesPathInList()
{
$this->request->getPath()->willReturn("/cats/molly");
$route = new PrefixRoute(array("/cats/", "/dogs/"), $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNotNull($resp);
}
public function testFailsToMatchPath()
{
$this->request->getPath()->willReturn("/dogs/");
$route = new PrefixRoute("/cats/", $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNull($resp);
}
/**
* @dataProvider invalidPathsProvider
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionOnInvalidPath($path)
{
new PrefixRoute($path, "\\NoClass");
}
public function invalidPathsProvider()
{
return array(
array(false),
array(17),
array(null)
);
}
public function testReturnsPrefixes()
{
$paths = array("/cats/", "/dogs/");
$route = new PrefixRoute($paths, $this->handler->reveal());
$this->assertEquals($paths, $route->getPrefixes());
}
public function testPropagatesArgumentsToCallable()
{
$callableRequest = null;
$callableArgs = null;
$callable = function ($request, $args) use (&$callableRequest, &$callableArgs) {
$callableRequest = $request;
$callableArgs = $args;
};
$this->request->getPath()->willReturn("/");
$args = ["cat" => "Molly"];
$route = new PrefixRoute("/", $callable);
$route->getResponse($this->request->reveal(), $args);
$this->assertSame($this->request->reveal(), $callableRequest);
$this->assertSame($args, $callableArgs);
}
public function setUp()
{
$this->request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$this->response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$this->handler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$this->handler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
}
}

View File

@ -1,127 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\Routes\RegexRoute;
use Prophecy\Argument;
/**
* @covers pjdietz\WellRESTed\Routes\RegexRoute
*/
class RegexRouteTest extends \PHPUnit_Framework_TestCase
{
private $handler;
private $request;
private $response;
/**
* @dataProvider matchingRouteProvider
*/
public function testMatchesPattern($pattern, $path)
{
$this->request->getPath()->willReturn($path);
$route = new RegexRoute($pattern, $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNotNull($resp);
}
/**
* @dataProvider matchingRouteProvider
*/
public function testExtractsCaptures($pattern, $path, $captures)
{
$this->request->getPath()->willReturn($path);
$route = new RegexRoute($pattern, $this->handler->reveal());
$route->getResponse($this->request->reveal());
$this->handler->getResponse(Argument::any(), Argument::that(
function ($args) use ($captures) {
return $args = $captures;
}))->shouldHaveBeenCalled();
}
public function matchingRouteProvider()
{
return [
["~/cat/[0-9]+~", "/cat/2", [0 => "/cat/2"]],
["#/dog/.*#", "/dog/his-name-is-bear", [0 => "/dog/his-name-is-bear"]],
["~/cat/([0-9]+)~", "/cat/2", [
0 => "/cat/2",
1 => "2"
]],
["~/dog/(?<id>[0-9+])~", "/dog/2", [
0 => "/dog/2",
1 => "2",
"id" => "2"
]]
];
}
/**
* @dataProvider mismatchingRouteProvider
*/
public function testFailsToMatchMismatchingPattern($pattern, $path)
{
$this->request->getPath()->willReturn($path);
$route = new RegexRoute($pattern, $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNull($resp);
}
public function mismatchingRouteProvider()
{
return [
["~/cat/[0-9]+~", "/cat/molly"],
["~/cat/[0-9]+~", "/dog/bear"],
["#/dog/.*#", "/dog"]
];
}
/**
* @dataProvider invalidRouteProvider
* @expectedException \pjdietz\WellRESTed\Exceptions\ParseException
*/
public function testThrowsExceptionOnInvalidPattern($pattern)
{
$route = new RegexRoute($pattern, $this->handler->reveal());
$route->getResponse($this->request->reveal());
}
public function invalidRouteProvider()
{
return [
["~/unterminated"],
["/nope"]
];
}
public function testPropagatesArgumentsToCallable()
{
$callableRequest = null;
$callableArgs = null;
$callable = function ($request, $args) use (&$callableRequest, &$callableArgs) {
$callableRequest = $request;
$callableArgs = $args;
};
$this->request->getPath()->willReturn("/dog/bear");
$args = ["cat" => "Molly"];
$route = new RegexRoute("~/dog/(?<dog>[a-z]+)~", $callable);
$route->getResponse($this->request->reveal(), $args);
$this->assertSame($this->request->reveal(), $callableRequest);
$this->assertArraySubset($args, $callableArgs);
$this->assertArraySubset(["dog" => "bear"], $callableArgs);
}
public function setUp()
{
$this->request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$this->response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$this->handler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$this->handler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
}
}

View File

@ -1,38 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\Routes\RouteFactory;
use Prophecy\Argument;
/**
* @covers pjdietz\WellRESTed\Routes\RouteFactory
*/
class RouteFactoryTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider routeProvider
*/
public function testCreatesRouteOfCorrectType($path, $expectedType)
{
$factory = new RouteFactory();
$route = $factory->createRoute($path, "\\MyHandler");
$this->assertInstanceOf($expectedType, $route);
}
public function routeProvider()
{
$static = "\\pjdietz\\WellRESTed\\Routes\\StaticRoute";
$prefix = "\\pjdietz\\WellRESTed\\Routes\\PrefixRoute";
$template = "\\pjdietz\\WellRESTed\\Routes\\TemplateRoute";
$regex = "\\pjdietz\\WellRESTed\\Routes\\RegexRoute";
return [
["/cats/", $static],
["/cats/*", $prefix],
["/cats/{catId}", $template],
["~/cat/[0-9]+~", $regex]
];
}
}

View File

@ -1,93 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\Routes\StaticRoute;
use Prophecy\Argument;
/**
* @covers pjdietz\WellRESTed\Routes\StaticRoute
*/
class StaticRouteTest extends \PHPUnit_Framework_TestCase
{
private $handler;
private $request;
private $response;
public function testMatchesSinglePath()
{
$this->request->getPath()->willReturn("/cats/");
$route = new StaticRoute("/cats/", $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNotNull($resp);
}
public function testMatchesPathInList()
{
$this->request->getPath()->willReturn("/cats/");
$route = new StaticRoute(array("/cats/", "/dogs/"), $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNotNull($resp);
}
public function testFailsToMatchPath()
{
$this->request->getPath()->willReturn("/dogs/");
$route = new StaticRoute("/cats/", $this->handler->reveal());
$resp = $route->getResponse($this->request->reveal());
$this->assertNull($resp);
}
/**
* @dataProvider invalidPathsProvider
* @expectedException \InvalidArgumentException
*/
public function testThrowsExceptionOnInvalidPath($path)
{
new StaticRoute($path, "\\NoClass");
}
public function invalidPathsProvider()
{
return array(
array(false),
array(17),
array(null)
);
}
public function testReturnsPaths()
{
$paths = array("/cats/", "/dogs/");
$route = new StaticRoute($paths, $this->handler->reveal());
$this->assertEquals($paths, $route->getPaths());
}
public function testPropagatesArgumentsToCallable()
{
$callableRequest = null;
$callableArgs = null;
$callable = function ($request, $args) use (&$callableRequest, &$callableArgs) {
$callableRequest = $request;
$callableArgs = $args;
};
$this->request->getPath()->willReturn("/");
$args = ["cat" => "Molly"];
$route = new StaticRoute("/", $callable);
$route->getResponse($this->request->reveal(), $args);
$this->assertSame($this->request->reveal(), $callableRequest);
$this->assertSame($args, $callableArgs);
}
public function setUp()
{
$this->request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$this->response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$this->handler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$this->handler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
}
}

View File

@ -1,208 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test;
use pjdietz\WellRESTed\Routes\TemplateRoute;
use Prophecy\Argument;
/**
* @covers pjdietz\WellRESTed\Routes\TemplateRoute
*/
class TemplateRouteTest extends \PHPUnit_Framework_TestCase
{
private $handler;
private $request;
private $response;
/**
* @dataProvider matchingTemplateProvider
*/
public function testMatchesTemplate($template, $default, $vars, $path)
{
$this->request->getPath()->willReturn($path);
$route = new TemplateRoute($template, $this->handler->reveal(), $default, $vars);
$resp = $route->getResponse($this->request->reveal());
$this->assertNotNull($resp);
}
/**
* @dataProvider matchingTemplateProvider
*/
public function testExtractsCaptures($template, $default, $vars, $path, $expectedCaptures)
{
$this->request->getPath()->willReturn($path);
$route = new TemplateRoute($template, $this->handler->reveal(), $default, $vars);
$route->getResponse($this->request->reveal());
$this->handler->getResponse(
Argument::any(),
Argument::that(
function ($args) use ($expectedCaptures) {
return count(array_diff_assoc($expectedCaptures, $args)) === 0;
}
)
)->shouldHaveBeenCalled();
}
public function matchingTemplateProvider()
{
return [
["/cat/{id}", TemplateRoute::RE_NUM, null, "/cat/12", ["id" => "12"]],
[
"/cat/{catId}/{dogId}",
TemplateRoute::RE_SLUG,
null,
"/cat/molly/bear",
[
"catId" => "molly",
"dogId" => "bear"
]
],
[
"/cat/{catId}/{dogId}",
TemplateRoute::RE_NUM,
[
"catId" => TemplateRoute::RE_SLUG,
"dogId" => TemplateRoute::RE_SLUG
],
"/cat/molly/bear",
[
"catId" => "molly",
"dogId" => "bear"
]
],
[
"/cat/{catId}/{dogId}",
TemplateRoute::RE_NUM,
(object) [
"catId" => TemplateRoute::RE_SLUG,
"dogId" => TemplateRoute::RE_SLUG
],
"/cat/molly/bear",
[
"catId" => "molly",
"dogId" => "bear"
]
],
["/cat/{id}/*", null, null, "/cat/12/molly", ["id" => "12"]],
[
"/cat/{id}-{width}x{height}.jpg",
TemplateRoute::RE_NUM,
null,
"/cat/17-200x100.jpg",
[
"id" => "17",
"width" => "200",
"height" => "100"
]
],
["/cat/{path}", ".*", null, "/cat/this/section/has/slashes", ["path" => "this/section/has/slashes"]]
];
}
/**
* @dataProvider allowedVariableNamesProvider
*/
public function testMatchesAllowedVariablesNames($template, $path, $expectedCaptures)
{
$this->request->getPath()->willReturn($path);
$route = new TemplateRoute($template, $this->handler->reveal(), null, null);
$route->getResponse($this->request->reveal());
$this->handler->getResponse(
Argument::any(),
Argument::that(
function ($args) use ($expectedCaptures) {
return count(array_diff_assoc($expectedCaptures, $args)) === 0;
}
)
)->shouldHaveBeenCalled();
}
public function allowedVariableNamesProvider()
{
return [
["/{n}", "/lower", ["n" => "lower"]],
["/{N}", "/UPPER", ["N" => "UPPER"]],
["/{var1024}", "/digits", ["var1024" => "digits"]],
["/{variable_name}", "/underscore", ["variable_name" => "underscore"]],
];
}
/**
* @dataProvider illegalVariableNamesProvider
*/
public function testFailsToMatchIllegalVariablesNames($template, $path)
{
$this->request->getPath()->willReturn($path);
$route = new TemplateRoute($template, $this->handler->reveal(), null, null);
$route->getResponse($this->request->reveal());
$this->handler->getResponse(Argument::cetera())->shouldNotHaveBeenCalled();
}
public function illegalVariableNamesProvider()
{
return [
["/{not-legal}", "/hyphen"],
["/{1digitfirst}", "/digitfirst"],
["/{%2f}", "/percent-encoded"],
["/{}", "/empty"],
["/{{nested}}", "/nested"]
];
}
public function setUp()
{
$this->request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$this->response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$this->handler = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\HandlerInterface");
$this->handler->getResponse(Argument::cetera())->willReturn($this->response->reveal());
}
/**
* @dataProvider nonmatchingTemplateProvider
*/
public function testFailsToMatchNonmatchingTemplate($template, $default, $vars, $path)
{
$this->request->getPath()->willReturn($path);
$route = new TemplateRoute($template, $this->handler->reveal(), $default, $vars);
$resp = $route->getResponse($this->request->reveal());
$this->assertNull($resp);
}
public function nonmatchingTemplateProvider()
{
return array(
array("/cat/{id}", TemplateRoute::RE_NUM, null, "/cat/molly"),
array("/cat/{catId}/{dogId}", TemplateRoute::RE_ALPHA, null, "/cat/12/13"),
array(
"/cat/{catId}/{dogId}",
TemplateRoute::RE_NUM,
array(
"catId" => TemplateRoute::RE_ALPHA,
"dogId" => TemplateRoute::RE_ALPHA
),
"/cat/12/13"
)
);
}
public function testPropagatesArgumentsToCallable()
{
$callableRequest = null;
$callableArgs = null;
$callable = function ($request, $args) use (&$callableRequest, &$callableArgs) {
$callableRequest = $request;
$callableArgs = $args;
};
$this->request->getPath()->willReturn("/dog/bear");
$args = ["cat" => "Molly"];
$route = new TemplateRoute("/dog/{dog}", $callable);
$route->getResponse($this->request->reveal(), $args);
$this->assertSame($this->request->reveal(), $callableRequest);
$this->assertArraySubset($args, $callableArgs);
$this->assertArraySubset(["dog" => "bear"], $callableArgs);
}
}

View File

@ -1,102 +0,0 @@
<?php
namespace pjdietz\WellRESTed\Test\Integration;
use pjdietz\WellRESTed\Router;
use Prophecy\Argument;
/**
* @coversNothing
*/
class RouterTest extends \PHPUnit_Framework_TestCase
{
private $request;
private $response;
public function setUp()
{
$this->request = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\RequestInterface");
$this->request->getPath()->willReturn("/");
$this->request->getMethod()->willReturn("GET");
$this->response = $this->prophesize("\\pjdietz\\WellRESTed\\Interfaces\\ResponseInterface");
$this->response->getStatusCode()->willReturn(200);
$this->response->getBody()->willReturn("Hello, world!");
}
public function testDispatchesCallable()
{
$response = $this->response;
$router = new Router();
$router->add("/", function () use ($response) {
return $response->reveal();
});
$result = $router->getResponse($this->request->reveal());
$this->assertSame($response->reveal(), $result);
}
public function testDispatchesCallableWithArguments()
{
$response = $this->response;
$args = ["cat" => "molly"];
$router = new Router();
$router->add("/", function ($rqst, $args) use ($response) {
$response->getBody()->willReturn($args["cat"]);
return $response->reveal();
});
$result = $router->getResponse($this->request->reveal(), $args);
$this->assertEquals("molly", $result->getBody());
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testStopsDispatchingCallablesAfterFirstNonNull()
{
$router = new Router();
$router->add("/cats/{cat}", function () {
echo "Hello, cat!";
return true;
});
$router->add("/cats/{cat}", function () {
echo "Hello, cat!";
});
$this->request->getPath()->willReturn("/cats/molly");
ob_start();
$router->getResponse($this->request->reveal());
$captured = ob_get_contents();
ob_end_clean();
$this->assertEquals("Hello, cat!", $captured);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testRouterRespondsWithNoisyCallable()
{
$_SERVER["REQUEST_URI"] = "/cats/molly";
$_SERVER["HTTP_HOST"] = "localhost";
$_SERVER["REQUEST_METHOD"] = "GET";
$router = new Router();
$router->add("/cats/{cat}", function () {
echo "Hello, cat!";
return true;
});
ob_start();
@$router->respond();
$captured = ob_get_contents();
ob_end_clean();
$this->assertEquals("Hello, cat!", $captured);
}
}