Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

hi

  • Loading branch information...
commit 17fa81c8d89ca939a99ed25cf0f7f782b6383965 0 parents
@alganet authored
Showing with 2,968 additions and 0 deletions.
  1. +7 −0 config/app.ini
  2. +9 −0 config/config.ini
  3. +8 −0 config/router.ini
  4. +4 −0 config/template.ini
  5. +209 −0 library/Respect/Config/Container.php
  6. +181 −0 library/Respect/Config/Instantiator.php
  7. +55 −0 library/Respect/Loader.php
  8. +107 −0 library/Respect/Rest/Request.php
  9. +9 −0 library/Respect/Rest/Routable.php
  10. +192 −0 library/Respect/Rest/Router.php
  11. +146 −0 library/Respect/Rest/Routes/AbstractRoute.php
  12. +44 −0 library/Respect/Rest/Routes/Callback.php
  13. +56 −0 library/Respect/Rest/Routes/ClassName.php
  14. +48 −0 library/Respect/Rest/Routes/Factory.php
  15. +42 −0 library/Respect/Rest/Routes/Instance.php
  16. +37 −0 library/Respect/Rest/Routes/StaticValue.php
  17. +97 −0 library/Respect/Rest/Routines/AbstractAccept.php
  18. +23 −0 library/Respect/Rest/Routines/AbstractRoutine.php
  19. +30 −0 library/Respect/Rest/Routines/AbstractSyncedRoutine.php
  20. +24 −0 library/Respect/Rest/Routines/Accept.php
  21. +9 −0 library/Respect/Rest/Routines/AcceptCharset.php
  22. +10 −0 library/Respect/Rest/Routines/AcceptEncoding.php
  23. +29 −0 library/Respect/Rest/Routines/AcceptLanguage.php
  24. +16 −0 library/Respect/Rest/Routines/By.php
  25. +52 −0 library/Respect/Rest/Routines/ContentType.php
  26. +32 −0 library/Respect/Rest/Routines/LastModified.php
  27. +10 −0 library/Respect/Rest/Routines/ParamSynced.php
  28. +12 −0 library/Respect/Rest/Routines/ProxyableBy.php
  29. +12 −0 library/Respect/Rest/Routines/ProxyableThrough.php
  30. +12 −0 library/Respect/Rest/Routines/ProxyableWhen.php
  31. +17 −0 library/Respect/Rest/Routines/Through.php
  32. +9 −0 library/Respect/Rest/Routines/Unique.php
  33. +16 −0 library/Respect/Rest/Routines/When.php
  34. +48 −0 library/Respect/Template/Adapter.php
  35. +32 −0 library/Respect/Template/Adapters/A.php
  36. +76 −0 library/Respect/Template/Adapters/AbstractAdapter.php
  37. +16 −0 library/Respect/Template/Adapters/Dom.php
  38. +24 −0 library/Respect/Template/Adapters/HtmlElement.php
  39. +24 −0 library/Respect/Template/Adapters/String.php
  40. +50 −0 library/Respect/Template/Adapters/Traversable.php
  41. +36 −0 library/Respect/Template/Decorators/AbstractDecorator.php
  42. +14 −0 library/Respect/Template/Decorators/Append.php
  43. +17 −0 library/Respect/Template/Decorators/Clean.php
  44. +15 −0 library/Respect/Template/Decorators/CleanAppend.php
  45. +16 −0 library/Respect/Template/Decorators/Replace.php
  46. +89 −0 library/Respect/Template/Document.php
  47. +81 −0 library/Respect/Template/Html.php
  48. +60 −0 library/Respect/Template/HtmlElement.php
  49. +43 −0 library/Respect/Template/Query.php
  50. +23 −0 library/Resplendor/Application.php
  51. +13 −0 library/Resplendor/Controllers/Index.php
  52. +146 −0 library/Zend/Dom/Css2Xpath.php
  53. +36 −0 library/Zend/Dom/Exception.php
  54. +37 −0 library/Zend/Dom/Exception/RuntimeException.php
  55. +188 −0 library/Zend/Dom/NodeList.php
  56. +298 −0 library/Zend/Dom/Query.php
  57. +9 −0 public/index.php
  58. +13 −0 templates/main.html
7 config/app.ini
@@ -0,0 +1,7 @@
+;This is the only file you have to edit to tweak the application!
+
+;Application settings
+virtual_host = /resplendor/public/index.php
+
+;Routes for every controller
+routes[] = [any, /, Resplendor\Controllers\Index]
9 config/config.ini
@@ -0,0 +1,9 @@
+;You do not need to edit this file unless you know what you're doing!
+
+;This file is responsible for loading every other
+;config file in order.
+
+[container Respect\Config\Container]
+loadFile[] = 'config/app.ini'
+loadFile[] = 'config/router.ini'
+loadFile[] = 'config/template.ini'
8 config/router.ini
@@ -0,0 +1,8 @@
+;You do not need to edit this file unless you know what you're doing!
+
+[request Respect\Rest\Request]
+
+[router Respect\Rest\Router]
+virtualHost = [virtual_host]
+autoDispatched = false
+classRoute = [routes]
4 config/template.ini
@@ -0,0 +1,4 @@
+;You do not need to edit this file unless you know what you're doing!
+
+[template Respect\Template\Html]
+templateFileOrString = "templates/main.html"
209 library/Respect/Config/Container.php
@@ -0,0 +1,209 @@
+<?php
+
+namespace Respect\Config;
+
+use UnexpectedValueException as Value;
+use InvalidArgumentException as Argument;
+use ArrayObject;
+
+class Container extends ArrayObject
+{
+
+ public function __construct($configurator=null)
+ {
+ if (is_null($configurator))
+ return;
+
+ if (is_array($configurator))
+ return $this->loadArray($configurator);
+
+ if (file_exists($configurator) && is_file($configurator))
+ return $this->loadFile($configurator);
+
+ if (is_string($configurator))
+ return $this->loadString($configurator);
+
+ throw new Argument("Invalid input. Must be a valid file or array");
+ }
+
+ public function __isset($name)
+ {
+ return parent::offsetExists($name);
+ }
+
+ public function getItem($name, $raw=false)
+ {
+ if (!isset($this[$name]))
+ throw new Argument("Item $name not found");
+
+ if ($raw || !is_callable($this[$name]))
+ return $this[$name];
+
+ return $this->lazyLoad($name);
+ }
+
+ public function loadString($configurator)
+ {
+ $iniData = parse_ini_string($configurator, true);
+ if (false === $iniData || count($iniData) == 0)
+ throw new Argument("Invalid configuration string");
+
+ return $this->loadArray($iniData);
+ }
+
+ public function loadFile($configurator)
+ {
+ $iniData = parse_ini_file($configurator, true);
+ if (false === $iniData)
+ throw new Argument("Invalid configuration INI file");
+
+ return $this->loadArray($iniData);
+ }
+
+ public function loadArray(array $configurator)
+ {
+ foreach ($configurator as $key => $value)
+ $this->parseItem($key, $value);
+ }
+
+ public function __get($name)
+ {
+ return $this->getItem($name);
+ }
+
+ public function __set($name, $value)
+ {
+ if (isset($this[$name]) && $this[$name] instanceof Instantiator)
+ $this[$name]->setInstance($value);
+ $this[$name] = $value;
+ }
+
+ protected function keyHasInstantiator($key)
+ {
+ return false !== stripos($key, ' ');
+ }
+
+ protected function parseItem($key, $value)
+ {
+ $key = trim($key);
+ if ($this->keyHasInstantiator($key))
+ $this->parseInstantiator($key, $value);
+ else
+ $this->parseStandardItem($key, $value);
+ }
+
+ protected function parseSubValues(&$value)
+ {
+ foreach ($value as &$subValue)
+ $subValue = $this->parseValue($subValue);
+ return $value;
+ }
+
+ protected function parseStandardItem($key, &$value)
+ {
+ if (is_array($value))
+ $this->parseSubValues($value);
+ else
+ $value = $this->parseValue($value);
+
+ $this->offsetSet($key, $value);
+ }
+
+ protected function removeDuplicatedSpaces($string)
+ {
+ return preg_replace('/\s+/', ' ', $string);
+ }
+
+ protected function parseInstantiator($key, $value)
+ {
+ $key = $this->removeDuplicatedSpaces($key);
+ list($keyName, $keyClass) = explode(' ', $key);
+ $instantiator = new Instantiator($keyClass);
+
+ if (is_array($value))
+ foreach ($value as $property => $pValue)
+ $instantiator->setParam($property, $this->parseValue($pValue));
+ else
+ $instantiator->setParam('__construct', $this->parseValue($value));
+
+ $this->offsetSet($keyName, $instantiator);
+ }
+
+ protected function parseValue($value)
+ {
+ if (is_array($value))
+ return $this->parseSubValues($value);
+ elseif (empty($value))
+ return null;
+ else
+ return $this->parseSingleValue($value);
+ }
+
+ protected function hasCompleteBrackets($value)
+ {
+ return false !== strpos($value, '[') && false !== strpos($value, ']');
+ }
+
+ protected function parseSingleValue($value)
+ {
+ $value = trim($value);
+ if ($this->hasCompleteBrackets($value))
+ return $this->parseBrackets($value);
+ else
+ return $this->parseConstants($value);
+ }
+
+ protected function parseConstants($value)
+ {
+ if (preg_match('/^[A-Z_]+([:]{2}[A-Z_]+)?$/', $value) && defined($value))
+ return constant($value);
+ else
+ return $value;
+ }
+
+ protected function matchSequence(&$value)
+ {
+ if (preg_match('/^\[(.*?,.*?)\]$/', $value, $match))
+ return (boolean) ($value = $match[1]);
+ }
+
+ protected function matchReference(&$value)
+ {
+ if (preg_match('/^\[(\w+)+\]$/', $value, $match))
+ return (boolean) ($value = $match[1]);
+ }
+
+ protected function parseBrackets($value)
+ {
+ if ($this->matchSequence($value))
+ return $this->parseArgumentList($value);
+ elseif ($this->matchReference($value))
+ return $this->getItem($value, true);
+ else
+ return $this->parseVariables($value);
+ }
+
+ protected function parseVariables($value)
+ {
+ $self = $this;
+ return preg_replace_callback(
+ '/\[(\w+)\]/',
+ function($match) use(&$self) {
+ return $self[$match[1]] ? : '';
+ }, $value
+ );
+ }
+
+ protected function parseArgumentList($value)
+ {
+ $subValues = explode(',', $value);
+ return $this->parseSubValues($subValues);
+ }
+
+ protected function lazyLoad($name)
+ {
+ $callback = $this[$name];
+ return $this[$name] = $callback();
+ }
+
+}
181 library/Respect/Config/Instantiator.php
@@ -0,0 +1,181 @@
+<?php
+
+namespace Respect\Config;
+
+use ReflectionClass;
+
+class Instantiator
+{
+
+ protected $instance;
+ protected $reflection;
+ protected $constructor = array();
+ protected $className;
+ protected $params = array();
+ protected $staticMethodCalls = array();
+ protected $methodCalls = array();
+ protected $propertySetters = array();
+
+ public function __construct($className)
+ {
+ $this->reflection = new ReflectionClass($className);
+ $this->constructor = $this->findConstructorParams($this->reflection);
+ $this->className = $className;
+ }
+
+ public function __invoke()
+ {
+ return call_user_func_array(array($this, 'getInstance'), func_get_args());
+ }
+
+ public function getClassName()
+ {
+ return $this->className;
+ }
+
+ public function getInstance($forceNew=false)
+ {
+ if ($this->instance && !$forceNew)
+ return $this->instance;
+
+ $className = $this->className;
+ $instance = $this->instance;
+ $staticMethods = count($this->staticMethodCalls);
+ foreach ($this->staticMethodCalls as $methodCalls) {
+ $this->performMethodCalls($className, $methodCalls,
+ function($result) use ($className, &$instance, $staticMethods) {
+ if ($result instanceof $className || ($staticMethods == 1 && is_object($result)))
+ $instance = $result;
+ }
+ );
+ }
+
+ $constructor = $this->reflection->getConstructor();
+ $hasConstructor = ($constructor) ? $constructor->isPublic() : false ;
+ if (empty($instance))
+ if ((empty($this->constructor) && $hasConstructor) || count($this->constructor) <= 0)
+ $instance = new $className;
+ else
+ $instance = $this->reflection->newInstanceArgs(
+ $this->cleanupParams($this->constructor)
+ );
+ $this->instance = $instance;
+
+ foreach ($this->propertySetters as $property => $value)
+ $instance->{$property} = $this->lazyLoad($value);
+
+ foreach ($this->methodCalls as $methodCalls)
+ $this->performMethodCalls($instance, $methodCalls);
+
+
+ return $instance;
+ }
+
+ public function getParam($name)
+ {
+ return $this->params[$name];
+ }
+
+ public function setInstance($instance)
+ {
+ $this->instance = $instance;
+ }
+
+ public function setParam($name, $value)
+ {
+ $value = $this->processValue($value);
+
+ if ($this->matchStaticMethod($name))
+ $this->staticMethodCalls[] = array($name, $value);
+ elseif ($this->matchConstructorParam($name))
+ $this->constructor[$name] = $value;
+ elseif ($this->matchFullConstructor($name, $value))
+ $this->constructor = $value;
+ elseif ($this->matchMethod($name))
+ $this->methodCalls[] = array($name, $value);
+ else
+ $this->propertySetters[$name] = $value;
+
+ $this->params[$name] = $value;
+ }
+
+ protected function cleanupParams(array $params)
+ {
+ while (null === end($params))
+ unset($params[key($params)]);
+
+ foreach ($params as &$p)
+ $p = $this->lazyLoad($p);
+
+ return $params;
+ }
+
+ protected function lazyLoad($value)
+ {
+ return $value instanceof self ? $value->getInstance() : $value;
+ }
+
+ protected function findConstructorParams(ReflectionClass $class)
+ {
+ $params = array();
+ $constructor = $class->getConstructor();
+
+ if (!$constructor)
+ return array();
+
+ foreach ($constructor->getParameters() as $param)
+ $params[$param->getName()] = $param->isDefaultValueAvailable() ?
+ $param->getDefaultValue() : null;
+
+ return $params;
+ }
+
+ protected function processValue($value)
+ {
+ if (is_array($value))
+ foreach ($value as $valueKey => $subValue)
+ $value[$valueKey] = $this->processValue($subValue);
+
+ return $value;
+ }
+
+ protected function matchConstructorParam($name)
+ {
+ return array_key_exists($name, $this->constructor);
+ }
+
+ protected function matchFullConstructor($name, &$value)
+ {
+ return $name == '__construct'
+ || ( $name == $this->className && stripos($this->className, '\\'));
+ }
+
+ protected function matchMethod($name)
+ {
+ return $this->reflection->hasMethod($name);
+ }
+
+ protected function matchStaticMethod($name)
+ {
+ return $this->reflection->hasMethod($name)
+ && $this->reflection->getMethod($name)->isStatic();
+ }
+
+ protected function performMethodCalls($class, array $methodCalls, $resultCallback=null)
+ {
+ list($methodName, $calls) = $methodCalls;
+ $resultCallback = $resultCallback ?: function(){};
+
+ foreach ($calls as $arguments)
+ if (is_array($arguments))
+ $resultCallback(call_user_func_array(
+ array($class, $methodName),
+ $this->cleanUpParams($arguments)
+ ));
+ elseif (!is_null($arguments))
+ $resultCallback(call_user_func(array($class, $methodName), $this->lazyLoad($arguments)));
+ else
+ $resultCallback(call_user_func(array($class, $methodName)));
+ }
+
+}
55 library/Respect/Loader.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace Respect;
+
+class Loader
+{
+ public function __invoke($className)
+ {
+ $fileParts = explode('\\', ltrim($className, '\\'));
+
+ if (false !== strpos(end($fileParts), '_'))
+ array_splice($fileParts, -1, 1, explode('_', current($fileParts)));
+
+ $fileName = implode(DIRECTORY_SEPARATOR, $fileParts) . '.php';
+
+ if (stream_resolve_include_path($fileName))
+ require $fileName;
+ }
+}
+
+if (!defined('RESPECT_DO_NOT_RETURN_AUTOLOADER'))
+ return new Loader;
+
+/**
+ * LICENSE
+ *
+ * Copyright (c) 2009-2011, Alexandre Gomes Gaigalas.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Alexandre Gomes Gaigalas nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
107 library/Respect/Rest/Request.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Respect\Rest;
+
+use ArrayAccess;
+use ReflectionFunctionAbstract;
+use ReflectionParameter;
+use RuntimeException;
+use Respect\Rest\Routes\AbstractRoute;
+use Respect\Rest\Routines\AbstractRoutine;
+use Respect\Rest\Routines\ProxyableBy;
+use Respect\Rest\Routines\ProxyableThrough;
+use Respect\Rest\Routines\ProxyableWhen;
+use Respect\Rest\Routines\ParamSynced;
+
+/** A routed HTTP Request */
+class Request
+{
+
+ public $method = '';
+ public $params = array();
+ /** @var AbstractRoute */
+ public $route;
+ public $uri = '';
+
+ public function __construct($method=null, $uri=null)
+ {
+ $uri = $uri ? : parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
+ $this->uri = rtrim($uri, ' /');
+ $this->method = strtoupper($method ? : $_SERVER['REQUEST_METHOD']);
+ }
+
+ public function __toString()
+ {
+ return $this->response();
+ }
+
+ /** Generates and returns the response from the current route */
+ public function response()
+ {
+ if (!$this->route)
+ return null;
+
+ foreach ($this->route->routines as $routine)
+ if ($routine instanceof ProxyableBy
+ && false === $this->routineCall('by', $this->method, $routine,
+ $this->params))
+ return false;
+
+ $response = $this->route->runTarget($this->method, $this->params);
+
+ if ($response instanceof AbstractRoute)
+ return $this->forward($response);
+
+ $proxyResults = array();
+
+ foreach ($this->route->routines as $routine)
+ if ($routine instanceof ProxyableThrough)
+ $proxyResults[] = $this->routineCall('through', $this->method,
+ $routine, $this->params);
+
+ if (!empty($proxyResults))
+ foreach ($proxyResults as $proxyCallback)
+ if (is_callable($proxyCallback))
+ $response = $proxyCallback($response);
+
+ return $response;
+ }
+
+ /** Calls a routine on the current route and returns its result */
+ public function routineCall($type, $method, AbstractRoutine $routine, &$routeParamsValues)
+ {
+ $reflection = $this->route->getReflection($method);
+
+ $callbackParameters = array();
+
+ if ($routine instanceof ParamSynced)
+ foreach ($routine->getParameters() as $parameter)
+ $callbackParameters[] = $this->extractRouteParam($reflection,
+ $parameter, $routeParamsValues);
+ else
+ $callbackParameters = $routeParamsValues;
+
+ return $routine->{$type}($this, $callbackParameters);
+ }
+
+ /** Extracts a parameter value from the current route */
+ protected function extractRouteParam(ReflectionFunctionAbstract $callback, ReflectionParameter $routeParam, &$routeParamsValues)
+ {
+ foreach ($callback->getParameters() as $callbackParamReflection)
+ if ($callbackParamReflection->getName() === $routeParam->getName()
+ && isset($routeParamsValues[$callbackParamReflection->getPosition()]))
+ return $routeParamsValues[$callbackParamReflection->getPosition()];
+
+ if ($routeParam->isDefaultValueAvailable())
+ return $routeParam->getDefaultValue();
+
+ return null;
+ }
+
+ protected function forward(AbstractRoute $route)
+ {
+ $this->route = $route;
+ return $this->response();
+ }
+
+}
9 library/Respect/Rest/Routable.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Respect\Rest;
+
+/** Some class that can be routed by the Router */
+interface Routable
+{
+
+}
192 library/Respect/Rest/Router.php
@@ -0,0 +1,192 @@
+<?php
+
+namespace Respect\Rest;
+
+use Exception;
+use ReflectionClass;
+use ReflectionMethod;
+use RuntimeException;
+use InvalidArgumentException;
+use Respect\Rest\Routes;
+use Respect\Rest\Routes\AbstractRoute;
+
+class Router
+{
+
+ public $isAutoDispatched = true;
+ protected $globalRoutines = array();
+ protected $routes = array();
+ protected $virtualHost = '';
+
+ /** Cleans up an return an array of extracted parameters */
+ public static function cleanUpParams($params)
+ {
+ return array_values(
+ array_filter(
+ array_slice($params, 1), function($param) {
+ return $param !== '';
+ }
+ )
+ );
+ }
+
+ public function __call($method, $args)
+ {
+ if (count($args) < 2)
+ throw new InvalidArgumentException('Any route binding must at least 2 arguments');
+
+ list ($path, $routeTarget) = $args;
+
+ if (is_callable($routeTarget)) //closures, func names, callbacks
+ return $this->callbackRoute($method, $path, $routeTarget);
+ elseif ($routeTarget instanceof Routable) //direct instances
+ return $this->instanceRoute($method, $path, $routeTarget);
+ elseif (!is_string($routeTarget)) //static returns the argument itself
+ return $this->staticRoute($method, $path, $routeTarget);
+ else
+ if (!isset($args[2])) //raw classnames
+ return $this->classRoute($method, $path, $routeTarget);
+ elseif (is_callable($args[2])) //classnames as factories
+ return $this->factoryRoute($method, $path, $routeTarget, $args[2]);
+ else //classnames with constructor arguments
+ return $this->classRoute($method, $path, $routeTarget, $args[2]);
+ }
+
+ public function __construct($virtualHost=null)
+ {
+ $this->virtualHost = $virtualHost;
+ }
+
+ public function __destruct()
+ {
+ if (!$this->isAutoDispatched || !isset($_SERVER['SERVER_PROTOCOL']))
+ return;
+
+ echo $this->run();
+ }
+
+ /** Applies a routine to every route */
+ public function always($routineName, $routineParameter)
+ {
+ $routineClass = 'Respect\\Rest\\Routines\\' . $routineName;
+ $routineInstance = new $routineClass($routineParameter);
+ $this->globalRoutines[] = $routineInstance;
+
+ foreach ($this->routes as $route)
+ $route->appendRoutine($routineInstance);
+
+ return $this;
+ }
+
+ /** Appends a pre-built route to the dispatcher */
+ public function appendRoute(AbstractRoute $route)
+ {
+ $this->routes[] = $route;
+
+ foreach ($this->globalRoutines as $routine)
+ $route->appendRoutine($routine);
+ }
+
+ /** Creates and returns a callback-based route */
+ public function callbackRoute($method, $path, $callback)
+ {
+ $route = new Routes\Callback($method, $path, $callback);
+ $this->appendRoute($route);
+ return $route;
+ }
+
+ /** Creates and returns a class-based route */
+ public function classRoute($method, $path, $class, array $arguments=array())
+ {
+ $route = new Routes\ClassName($method, $path, $class, $arguments);
+ $this->appendRoute($route);
+ return $route;
+ }
+
+ /** Dispatch the current route with a standard Request */
+ public function dispatch($method=null, $uri=null)
+ {
+ return $this->dispatchRequest(new Request($method, $uri));
+ }
+
+ /** Dispatch the current route with a custom Request */
+ public function dispatchRequest(Request $request=null)
+ {
+ usort($this->routes, function($a, $b) {
+ $a = $a->pattern;
+ $b = $b->pattern;
+
+ if (0 === stripos($a, $b) || $a == AbstractRoute::CATCHALL_IDENTIFIER)
+ return 1;
+ elseif (0 === stripos($b, $a) || $b == AbstractRoute::CATCHALL_IDENTIFIER)
+ return -1;
+ elseif (substr_count($a, '/') < substr_count($b, '/'))
+ return 1;
+
+ return substr_count($a, AbstractRoute::PARAM_IDENTIFIER)
+ < substr_count($b, AbstractRoute::PARAM_IDENTIFIER) ? -1 : 1;
+ }
+ );
+ $this->isAutoDispatched = false;
+ if (!$request)
+ $request = new Request;
+
+ if ($this->virtualHost)
+ $request->uri =
+ preg_replace('#^' . preg_quote($this->virtualHost) . '#', '', $request->uri);
+
+ foreach ($this->routes as $route)
+ if ($this->matchRoute($request, $route, $params))
+ return $this->configureRequest($request, $route, static::cleanUpParams($params));
+
+ $request->route = null;
+ return $request;
+ }
+
+ /** Dispatches and get response with default request parameters */
+ public function run()
+ {
+ $route = $this->dispatch();
+ return $route ? $route->response() : null;
+ }
+
+ /** Creates and returns an factory-based route */
+ public function factoryRoute($method, $path, $className, $factory)
+ {
+ $route = new Routes\Factory($method, $path, $className, $factory);
+ $this->appendRoute($route);
+ return $route;
+ }
+
+ /** Creates and returns an instance-based route */
+ public function instanceRoute($method, $path, $instance)
+ {
+ $route = new Routes\Instance($method, $path, $instance);
+ $this->appendRoute($route);
+ return $route;
+ }
+
+ /** Creates and returns a static route */
+ public function staticRoute($method, $path, $instance)
+ {
+ $route = new Routes\StaticValue($method, $path, $instance);
+ $this->appendRoute($route);
+ return $route;
+ }
+
+ /** Configures a request for a specific route with specific parameters */
+ protected function configureRequest(Request $request, AbstractRoute $route, array $params)
+ {
+ $request->route = $route;
+ $request->params = $params;
+ return $request;
+ }
+
+ /** Returns true if the passed route matches the passed request */
+ protected function matchRoute(Request $request, AbstractRoute $route, &$params=array())
+ {
+ $request->route = $route;
+ return $route->match($request, $params);
+ }
+
+}
146 library/Respect/Rest/Routes/AbstractRoute.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace Respect\Rest\Routes;
+
+use ReflectionClass;
+use Respect\Rest\Request;
+use Respect\Rest\Routines\AbstractRoutine;
+use Respect\Rest\Routines\ProxyableWhen;
+use Respect\Rest\Routines\Unique;
+
+/** Base class for all Routes */
+abstract class AbstractRoute
+{
+ const CATCHALL_IDENTIFIER = '/**';
+ const PARAM_IDENTIFIER = '/*';
+ const QUOTED_PARAM_IDENTIFIER = '/\*';
+ const REGEX_CATCHALL = '(/.*)?';
+ const REGEX_SINGLE_PARAM = '/([^/]+)';
+ const REGEX_TWO_ENDING_PARAMS = '/([^/]+)/([^/]+)';
+ const REGEX_TWO_MIXED_PARAMS = '(?:/([^/]+))?/([^/]+)';
+ const REGEX_TWO_OPTIONAL_ENDING_PARAMS = '/([^/]+)(?:/([^/]+))?';
+ const REGEX_TWO_OPTIONAL_PARAMS = '(?:/([^/]+))?(?:/([^/]+))?';
+
+ public $method = '';
+ public $pattern = '';
+ public $regexForMatch = '';
+ public $regexForReplace = '';
+ public $routines = array();
+
+ /** Returns the RelfectionFunctionAbstract object for the passed method */
+ abstract public function getReflection($method);
+
+ /** Runs the target method/params into this route */
+ abstract public function runTarget($method, &$params);
+
+ public function __construct($method, $pattern)
+ {
+ $this->pattern = $pattern;
+ $this->method = strtoupper($method);
+ list($this->regexForMatch, $this->regexForReplace)
+ = $this->createRegexPatterns($pattern);
+ }
+
+ public function __call($method, $arguments)
+ {
+ $routineReflection = new ReflectionClass(
+ 'Respect\\Rest\\Routines\\' . ucfirst($method)
+ );
+
+ $this->appendRoutine($routineReflection->newInstanceArgs($arguments));
+
+ return $this;
+ }
+
+ /** Appends a pre-built routine to this route */
+ public function appendRoutine(AbstractRoutine $routine)
+ {
+ $key = $routine instanceof Unique ? get_class($routine) : spl_object_hash($routine);
+ $this->routines[$key] = $routine;
+ }
+
+ /** Creates an URI for this route with the passed parameters */
+ public function createUri($param1=null, $etc=null)
+ {
+ $params = func_get_args();
+ array_unshift($params, $this->regexForReplace);
+ return call_user_func_array(
+ 'sprintf', $params
+ );
+ }
+
+ /** Checks if this route matches a request */
+ public function match(Request $request, &$params=array())
+ {
+ if (($request->method !== $this->method && $this->method !== 'ANY')
+ || 0 === stripos($request->method, '__'))
+ return false;
+
+ foreach ($this->routines as $routine)
+ if ($routine instanceof ProxyableWhen
+ && !$request->routineCall('when', $request->method, $routine, $params))
+ return false;
+
+ $matchUri = preg_replace('#(\.\w+)*$#', '', $request->uri);
+
+ if (!preg_match($this->regexForMatch, $matchUri, $params))
+ return false;
+
+ if (count($params) > 1 && false !== stripos(end($params), '/')) {
+ $lastParam = array_pop($params);
+ $params = array_merge($params, explode('/', $lastParam));
+ }
+
+ return true;
+ }
+
+ /** Creates the regex from the route patterns */
+ protected function createRegexPatterns($pattern)
+ {
+ $pattern = rtrim($pattern, ' /');
+ $extra = $this->extractCatchAllPattern($pattern);
+ $matchPattern = str_replace(
+ static::QUOTED_PARAM_IDENTIFIER, static::REGEX_SINGLE_PARAM, preg_quote($pattern), $paramCount
+ );
+ $replacePattern = str_replace(static::PARAM_IDENTIFIER, '/%s', $pattern);
+ $matchPattern = $this->fixOptionalParams($matchPattern);
+ $matchRegex = "#^{$matchPattern}{$extra}$#";
+ return array($matchRegex, $replacePattern);
+ }
+
+ /** Extracts the catch-all param from a pattern */
+ protected function extractCatchAllPattern(&$pattern)
+ {
+ $extra = static::REGEX_CATCHALL;
+
+ if ((strlen($pattern) - strlen(static::CATCHALL_IDENTIFIER))
+ === strripos($pattern, static::CATCHALL_IDENTIFIER))
+ $pattern = substr($pattern, 0, -3);
+ else
+ $extra = '';
+
+ $pattern = str_replace(
+ static::CATCHALL_IDENTIFIER, static::PARAM_IDENTIFIER, $pattern
+ );
+ return $extra;
+ }
+
+ /** Turn sequenced parameters optional */
+ protected function fixOptionalParams($quotedPattern)
+ {
+ if (strlen($quotedPattern) - strlen(static::REGEX_TWO_ENDING_PARAMS)
+ === strripos($quotedPattern, static::REGEX_TWO_ENDING_PARAMS))
+ $quotedPattern = str_replace(
+ array(
+ static::REGEX_TWO_ENDING_PARAMS,
+ static::REGEX_TWO_MIXED_PARAMS
+ ), array(
+ static::REGEX_TWO_OPTIONAL_ENDING_PARAMS,
+ static::REGEX_TWO_OPTIONAL_PARAMS
+ ), $quotedPattern
+ );
+
+ return $quotedPattern;
+ }
+
+}
44 library/Respect/Rest/Routes/Callback.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Respect\Rest\Routes;
+
+use ReflectionFunction;
+use ReflectionMethod;
+
+/** A callback-based route */
+class Callback extends AbstractRoute
+{
+
+ protected $callback;
+ /** @var ReflectionFunctionAbstract */
+ protected $reflection;
+
+ public function __construct($method, $pattern, $callback)
+ {
+ $this->callback = $callback;
+ parent::__construct($method, $pattern);
+ }
+
+ /** Returns an appropriate Reflection for any is_callable object */
+ public function getCallbackReflection()
+ {
+ if (is_array($this->callback))
+ return new ReflectionMethod($this->callback[0], $this->callback[1]);
+ else
+ return new ReflectionFunction($this->callback);
+ }
+
+ public function getReflection($method)
+ {
+ if (empty($this->reflection))
+ $this->reflection = $this->getCallbackReflection();
+
+ return $this->reflection;
+ }
+
+ public function runTarget($method, &$params)
+ {
+ return call_user_func_array($this->callback, $params);
+ }
+
+}
56 library/Respect/Rest/Routes/ClassName.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Respect\Rest\Routes;
+
+use InvalidArgumentException;
+use ReflectionClass;
+use ReflectionException;
+use ReflectionMethod;
+use Respect\Rest\Routable;
+
+class ClassName extends AbstractRoute
+{
+
+ protected $class = '';
+ protected $constructorParams = array();
+ protected $instance = null;
+
+ public function __construct($method, $pattern, $class, array $constructorParams=array())
+ {
+ $this->class = $class;
+ $this->constructorParams = $constructorParams;
+ parent::__construct($method, $pattern);
+ }
+
+ protected function createInstance()
+ {
+ $className = $this->class;
+
+ $reflection = new ReflectionClass($className);
+ if (!$reflection->implementsInterface('Respect\\Rest\\Routable'))
+ throw new InvalidArgumentException(''); //TODO
+
+ if (empty($this->constructorParams) || !method_exists($this->class,
+ '__construct'))
+ return new $className;
+
+ $reflection = new ReflectionClass($this->class);
+ return $reflection->newInstanceArgs($this->constructorParams);
+ }
+
+ public function getReflection($method)
+ {
+ return new ReflectionMethod($this->class, $method);
+ }
+
+ public function runTarget($method, &$params)
+ {
+ if (is_null($this->instance))
+ $this->instance = $this->createInstance();
+
+ return call_user_func_array(
+ array($this->instance, $method), $params
+ );
+ }
+
+}
48 library/Respect/Rest/Routes/Factory.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Respect\Rest\Routes;
+
+use ReflectionMethod;
+use InvalidArgumentException;
+use Respect\Rest\Routable;
+
+class Factory extends AbstractRoute
+{
+
+ protected $instance = null;
+ protected $factory = null;
+
+ /** @var ReflectionMethod */
+ protected $reflection;
+
+ public function __construct($method, $pattern, $className, $factory)
+ {
+ $this->factory = $factory;
+ $this->className = $className;
+ parent::__construct($method, $pattern);
+ }
+
+ public function getReflection($method)
+ {
+ if (empty($this->reflection))
+ $this->reflection = new ReflectionMethod(
+ $this->className, $method
+ );
+
+ return $this->reflection;
+ }
+
+ public function runTarget($method, &$params)
+ {
+ if (is_null($this->instance))
+ $this->instance = call_user_func($this->factory);
+
+ if (!$this->instance instanceof Routable)
+ throw new InvalidArgumentException(''); //TODO
+
+ return call_user_func_array(
+ array($this->instance, $method), $params
+ );
+ }
+
+}
42 library/Respect/Rest/Routes/Instance.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Respect\Rest\Routes;
+
+use ReflectionMethod;
+use InvalidArgumentException;
+use Respect\Rest\Routable;
+
+class Instance extends AbstractRoute
+{
+
+ protected $instance = null;
+ /** @var ReflectionMethod */
+ protected $reflection;
+
+ public function __construct($method, $pattern, $instance)
+ {
+ $this->instance = $instance;
+ parent::__construct($method, $pattern);
+ }
+
+ public function getReflection($method)
+ {
+ if (empty($this->reflection))
+ $this->reflection = new ReflectionMethod(
+ $this->instance, $method
+ );
+
+ return $this->reflection;
+ }
+
+ public function runTarget($method, &$params)
+ {
+ if (!$this->instance instanceof Routable)
+ throw new InvalidArgumentException(''); //TODO
+
+ return call_user_func_array(
+ array($this->instance, $method), $params
+ );
+ }
+
+}
37 library/Respect/Rest/Routes/StaticValue.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Respect\Rest\Routes;
+
+use ReflectionMethod;
+
+/** A callback-based route */
+class StaticValue extends AbstractRoute
+{
+
+ protected $value;
+ /** @var ReflectionFunctionAbstract */
+ protected $reflection;
+
+ public function __construct($method, $pattern, $value)
+ {
+ $this->value = $value;
+ parent::__construct($method, $pattern);
+ $this->reflection = new ReflectionMethod($this, 'returnValue');
+ }
+
+ public function getReflection($method)
+ {
+ return $this->reflection;
+ }
+
+ public function runTarget($method, &$params)
+ {
+ return $this->returnValue($method, $params);
+ }
+
+ public function returnValue()
+ {
+ return $this->value;
+ }
+
+}
97 library/Respect/Rest/Routines/AbstractAccept.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use SplObjectStorage;
+use Respect\Rest\Request;
+
+/** Base class for content-negotiation */
+abstract class AbstractAccept extends AbstractRoutine implements ProxyableBy, ProxyableWhen, ProxyableThrough, Unique
+{
+
+ protected $callbacksPerMimeType = array();
+ protected $callbacksPerExtension = array();
+ protected $negotiated = null;
+
+ public function __construct(array $callbacksPerType = array())
+ {
+ $this->negotiated = new SplObjectStorage;
+ $this->parseAcceptMap($callbacksPerType);
+ }
+
+ /** Parses an array of callbacks per accept-type */
+ protected function parseAcceptMap(array $callbacksPerType)
+ {
+ if (!array_filter($callbacksPerType, 'is_callable'))
+ throw new \Exception(''); //TODO
+
+ foreach ($callbacksPerType as $acceptSpec => $callback)
+ if ('.' === $acceptSpec[0])
+ $this->callbacksPerExtension[$acceptSpec] = $callback;
+ else
+ $this->callbacksPerMimeType[$acceptSpec] = $callback;
+ }
+
+ /** Negotiate content with the given Request */
+ protected function negotiate(Request $request)
+ {
+ foreach ($this->callbacksPerExtension as $provided => $callback)
+ if (false !== stripos($request->uri, $provided))
+ return $this->negotiated[$request] = $callback;
+
+ if (!isset($_SERVER[static::ACCEPT_HEADER]))
+ return false;
+
+ $acceptHeader = $_SERVER[static::ACCEPT_HEADER];
+ $acceptParts = explode(',', $acceptHeader);
+ $acceptList = array();
+ foreach ($acceptParts as $k => &$acceptPart) {
+ $parts = explode(';q=', trim($acceptPart));
+ $provided = array_shift($parts);
+ $quality = array_shift($parts) ? : (10000 - $k) / 10000;
+ $acceptList[$provided] = $quality;
+ }
+ arsort($acceptList);
+ foreach ($acceptList as $requested => $quality)
+ foreach ($this->callbacksPerMimeType as $provided => $callback)
+ if ($this->compareItens($requested, $provided))
+ return $this->negotiated[$request] = $callback;
+
+ return false;
+ }
+
+ public function by(Request $request, $params)
+ {
+ $unsyncedParams = $request->params;
+ $extensions = array_keys($this->callbacksPerExtension);
+
+ if (empty($extensions) || empty($unsyncedParams))
+ return;
+
+ $unsyncedParams[] = str_replace(
+ $extensions, '', array_pop($unsyncedParams)
+ );
+ $request->params = $unsyncedParams;
+ }
+
+ public function through(Request $request, $params)
+ {
+ if (!isset($this->negotiated[$request])
+ || false === $this->negotiated[$request])
+ return;
+
+ return $this->negotiated[$request];
+ }
+
+ public function when(Request $request, $params)
+ {
+ return false !== $this->negotiate($request);
+ }
+
+ /** Compares two given content-negotiation elements */
+ protected function compareItens($requested, $provided)
+ {
+ return $requested == $provided;
+ }
+
+}
23 library/Respect/Rest/Routines/AbstractRoutine.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use InvalidArgumentException;
+use ReflectionFunction;
+use ReflectionMethod;
+use Respect\Rest\Routes\AbstractRoute;
+
+/** Base class for callback routines */
+abstract class AbstractRoutine
+{
+
+ protected $callback;
+
+ public function __construct($callback)
+ {
+ if (!is_callable($callback))
+ throw new InvalidArgumentException('Routine callback must be... guess what... callable!');
+ $this->callback = $callback;
+ }
+
+}
30 library/Respect/Rest/Routines/AbstractSyncedRoutine.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use InvalidArgumentException;
+use ReflectionFunction;
+use ReflectionMethod;
+use Respect\Rest\Routes\AbstractRoute;
+
+/** Base class for routines that sync parameters */
+abstract class AbstractSyncedRoutine extends AbstractRoutine implements ParamSynced
+{
+
+ protected $reflection;
+
+ public function getParameters()
+ {
+ return $this->getReflection()->getParameters();
+ }
+
+ /** Returns a concrete ReflectionFunctionAbstract for this routine callback */
+ protected function getReflection()
+ {
+ if (is_array($this->callback))
+ return new ReflectionMethod($this->callback[0], $this->callback[1]);
+ else
+ return new ReflectionFunction($this->callback);
+ }
+
+}
24 library/Respect/Rest/Routines/Accept.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+/** Handles mime type content negotiation */
+class Accept extends AbstractAccept
+{
+ const ACCEPT_HEADER = 'HTTP_ACCEPT';
+
+ protected function compareItens($requested, $provided)
+ {
+ if ($requested === $provided || $requested === '*/*')
+ return true;
+
+ list($requestedA, $requestedB) = explode('/', $requested);
+ list($providedA, ) = explode('/', $provided);
+
+ if ($providedA === $requestedA && $requestedB === '*')
+ return true;
+
+ return false;
+ }
+
+}
9 library/Respect/Rest/Routines/AcceptCharset.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+/** Handles charset content negotiation*/
+class AcceptCharset extends AbstractAccept
+{
+ const ACCEPT_HEADER = 'HTTP_ACCEPT_CHARSET';
+}
10 library/Respect/Rest/Routines/AcceptEncoding.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+/** Handles encoding content negotiation */
+class AcceptEncoding extends AbstractAccept
+{
+ const ACCEPT_HEADER = 'HTTP_ACCEPT_ENCODING';
+}
+
29 library/Respect/Rest/Routines/AcceptLanguage.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+/** Handles Language content negotiation */
+ class AcceptLanguage extends AbstractAccept
+{
+ const ACCEPT_HEADER = 'HTTP_ACCEPT_LANGUAGE';
+
+ protected function compareItens($requested, $provided)
+ {
+ $requested = preg_replace('/^x\-/', '', $requested);
+ $provided = preg_replace('/^x\-/', '', $provided);
+
+ if ($requested == $provided)
+ return true;
+
+ if (stripos($requested, '-') || !stripos($provided, '-'))
+ return false;
+
+ list($providedA, ) = explode('-', $provided);
+
+ if ($requested === $providedA)
+ return true;
+
+ return false;
+ }
+
+}
16 library/Respect/Rest/Routines/By.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use Respect\Rest\Request;
+
+/** Generic routine executed before the route */
+class By extends AbstractSyncedRoutine implements ProxyableBy
+{
+
+ public function by(Request $request, $params)
+ {
+ return call_user_func_array($this->callback, $params);
+ }
+
+}
52 library/Respect/Rest/Routines/ContentType.php
@@ -0,0 +1,52 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use SplObjectStorage;
+use Respect\Rest\Request;
+
+/** Handles content type content negotiation */
+class ContentType extends AbstractRoutine implements ProxyableWhen, ProxyableBy, Unique
+{
+
+ protected $contentMap = array();
+ protected $negotiated = null;
+
+ public function __construct(array $callbacksPerContentType = array())
+ {
+ if (!array_filter($callbacksPerContentType, 'is_callable'))
+ throw new \Exception(''); //TODO
+
+ $this->negotiated = new SplObjectStorage;
+ $this->contentMap = $callbacksPerContentType;
+ }
+
+ /** Negotiates the content type with the given request */
+ protected function negotiate(Request $request)
+ {
+ if (!isset($_SERVER['CONTENT_TYPE']))
+ return false;
+
+ $requested = $_SERVER['CONTENT_TYPE'];
+ foreach ($this->contentMap as $provided => $callback)
+ if ($requested == $provided)
+ return $this->negotiated[$request] = $callback;
+
+ return false;
+ }
+
+ public function by(Request $request, $params)
+ {
+ if (!isset($this->negotiated[$request])
+ || false === $this->negotiated[$request])
+ return;
+
+ return call_user_func($this->negotiated[$request]);
+ }
+
+ public function when(Request $request, $params)
+ {
+ return false !== $this->negotiate($request);
+ }
+
+}
32 library/Respect/Rest/Routines/LastModified.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use DateTime;
+use SplObjectStorage;
+use Respect\Rest\Request;
+
+class LastModified extends AbstractRoutine implements ProxyableBy, ProxyableThrough, Unique
+{
+
+ public function by(Request $request, $params)
+ {
+ if (!isset($_SERVER['IF_MODIFIED_SINCE']))
+ return true;
+
+ $ifModifiedSince = new DateTime($_SERVER['IF_MODIFIED_SINCE']);
+ $lastModifiedOn = call_user_func($this->callback, $params);
+
+ header('Last-Modified: '.$lastModifiedOn->format(DateTime::RFC2822));
+ if ($lastModifiedOn <= $ifModifiedSince) {
+ header('HTTP/1.1 304 Not Modified');
+ return false;
+ }
+
+ }
+
+ public function through(Request $request, $params)
+ {
+ }
+
+}
10 library/Respect/Rest/Routines/ParamSynced.php
@@ -0,0 +1,10 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+/** Callback Routine that sync params */
+interface ParamSynced
+{
+ /** Returns parameters for the callback*/
+ public function getParameters();
+}
12 library/Respect/Rest/Routines/ProxyableBy.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use Respect\Rest\Request;
+
+/** Routine that runs before the route */
+interface ProxyableBy
+{
+ /** Executed before the route */
+ public function by(Request $request, $params);
+}
12 library/Respect/Rest/Routines/ProxyableThrough.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use Respect\Rest\Request;
+
+/** Routine that runs after the route */
+interface ProxyableThrough
+{
+ /** Executed after the route */
+ public function through(Request $request, $params);
+}
12 library/Respect/Rest/Routines/ProxyableWhen.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use Respect\Rest\Request;
+
+/** Routine that runs before the route matching */
+interface ProxyableWhen
+{
+ /** Executed to check if the route matchs */
+ public function when(Request $request, $params);
+}
17 library/Respect/Rest/Routines/Through.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use Respect\Rest\Request;
+
+/** Generic routine executed after the route */
+class Through extends AbstractSyncedRoutine implements ProxyableThrough
+{
+
+ public function through(Request $request, $params)
+ {
+ return call_user_func_array($this->callback, $params);
+ }
+
+}
+
9 library/Respect/Rest/Routines/Unique.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+/** Routine that does not allow multiple instances per route */
+interface Unique
+{
+
+}
16 library/Respect/Rest/Routines/When.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace Respect\Rest\Routines;
+
+use Respect\Rest\Request;
+
+/** Generic routine executed before route matching */
+class When extends AbstractSyncedRoutine implements ProxyableWhen
+{
+
+ public function when(Request $request, $params)
+ {
+ return call_user_func_array($this->callback, $params);
+ }
+
+}
48 library/Respect/Template/Adapter.php
@@ -0,0 +1,48 @@
+<?php
+namespace Respect\Template;
+
+use Respect\Template\Adapters\AbstractAdapter;
+use \DOMNode;
+use \DOMDocument;
+use \UnexpectedValueException as Unexpected;
+class Adapter
+{
+ private static $instance;
+ protected $adapters;
+
+ private function __construct()
+ {
+ $adapters = array('HtmlElement', 'A', 'Dom', 'Traversable', 'String');
+ foreach ($adapters as $className) {
+ $class = 'Respect\Template\Adapters\\'.$className;
+ $this->adapters[$class] = new $class();
+ }
+ }
+
+ public static function getInstance()
+ {
+ if (self::$instance instanceof Adapter)
+ return self::$instance;
+
+ return self::$instance = new Adapter();
+ }
+
+ public static function factory(DOMDocument $dom, $content)
+ {
+ return self::getInstance()->_factory($dom, $content);
+ }
+
+ public function _factory(DOMDocument $dom, $content)
+ {
+ if ($content instanceof AbstractAdapter)
+ return $content;
+
+ foreach ($this->adapters as $class=>$object)
+ if ($object->isValidData($content))
+ return new $class($dom, $content);
+
+ $type = gettype($content);
+ $type .= (!is_object($type)) ? '' : ' of class '.get_class($content);
+ throw new Unexpected('No adapter found for '.$type);
+ }
+}
32 library/Respect/Template/Adapters/A.php
@@ -0,0 +1,32 @@
+<?php
+namespace Respect\Template\Adapters;
+
+use \DOMNode;
+use Respect\Template\Adapter;
+use Respect\Template\Decorators\Append;
+class A extends AbstractAdapter
+{
+ public function isValidData($data)
+ {
+ if ($this->hasProperty($data, 'href'))
+ return true;
+ return false;
+ }
+
+ protected function getDomNode($data, DOMNode $parent)
+ {
+ $element = $this->createElement($parent, 'a');
+ $element->setAttribute('href', $this->getProperty($data, 'href'));
+ if ($this->hasProperty($data, 'innerHtml')) {
+ $inner = $this->getProperty($data, 'innerHtml');
+ $adapter = Adapter::factory($this->getDom(), $inner);
+ new Append($element, $adapter);
+ }
+ return $element;
+ }
+
+ public function getDecorator()
+ {
+ return 'Respect\Template\Decorators\Replace';
+ }
+}
76 library/Respect/Template/Adapters/AbstractAdapter.php
@@ -0,0 +1,76 @@
+<?php
+namespace Respect\Template\Adapters;
+
+use \DOMNode;
+use \DOMDocument;
+use \UnexpectedValueException as Unexpected;
+use Respect\Template\Adapter;
+abstract class AbstractAdapter
+{
+ /**
+ * @var DOMDocument
+ */
+ private $dom;
+ protected $content;
+
+ public function __construct(DOMDocument $dom=null, $content=null)
+ {
+ if (!is_null($dom))
+ $this->dom = $dom;
+
+ if (!is_null($content))
+ $this->content = $content;
+ }
+
+ abstract public function isValidData($data);
+ abstract protected function getDomNode($data, DOMNode $parent);
+
+ public function adaptTo(DOMNode $parent, $content=null)
+ {
+ $content = ($content) ? $content : $this->content;
+ return $this->getDomNode($content, $parent);
+ }
+
+ protected function createElement(DOMNode $parent, $name, $value=null)
+ {
+ if (!$this->dom instanceof DOMDocument)
+ throw new Unexpected('No DOMDocument, cannot create new element');
+
+ return $this->dom->createElement($name, $value);
+ }
+
+ final protected function hasProperty($data, $name)
+ {
+ if (is_array($data))
+ return isset($data[$name]);
+
+ if (is_object($data))
+ return isset($data->$name);
+
+ return false;
+ }
+
+ public function getDecorator()
+ {
+ return 'Respect\Template\Decorators\CleanAppend';
+ }
+
+ /**
+ * @return DOMDocument
+ */
+ public function getDom()
+ {
+ return $this->dom;
+ }
+
+ final protected function getProperty($data, $name)
+ {
+ if (is_array($data))
+ return $data[$name];
+
+ if (is_object($data))
+ return $data->$name;
+
+ return null;
+ }
+}
16 library/Respect/Template/Adapters/Dom.php
@@ -0,0 +1,16 @@
+<?php
+namespace Respect\Template\Adapters;
+
+use \DOMNode;
+class Dom extends AbstractAdapter
+{
+ public function isValidData($data)
+ {
+ return ($data instanceof DOMNode);
+ }
+
+ protected function getDomNode($data, DOMNode $parent)
+ {
+ return $data;
+ }
+}
24 library/Respect/Template/Adapters/HtmlElement.php
@@ -0,0 +1,24 @@
+<?php
+namespace Respect\Template\Adapters;
+
+use \ReflectionObject;
+use \DOMNode;
+use Respect\Template\Adapter;
+class HtmlElement extends AbstractAdapter
+{
+ public function isValidData($data)
+ {
+ return ($data instanceof \Respect\Template\HtmlElement);
+ }
+
+ protected function getDomNode($data, DOMNode $parent)
+ {
+ return $data->getDOMNode($this->getDom());
+ }
+
+ public function getDecorator()
+ {
+ return 'Respect\Template\Decorators\Replace';
+ }
+
+}
24 library/Respect/Template/Adapters/String.php
@@ -0,0 +1,24 @@
+<?php
+namespace Respect\Template\Adapters;
+
+use \DOMNode;
+use \DOMText;
+use \ReflectionObject;
+use Respect\Template\Document;
+class String extends AbstractAdapter
+{
+ public function isValidData($data)
+ {
+ if (is_string($data))
+ return true;
+ if (!is_object($data))
+ return false;
+ $reflection = new ReflectionObject($data);
+ return $reflection->hasMethod('__toString');
+ }
+
+ protected function getDomNode($data, DOMNode $parent)
+ {
+ return new DOMText($data);
+ }
+}
50 library/Respect/Template/Adapters/Traversable.php
@@ -0,0 +1,50 @@
+<?php
+namespace Respect\Template\Adapters;
+
+use \DOMNode;
+use Respect\Template\Adapter;
+
+class Traversable extends AbstractAdapter
+{
+ public function isValidData($data)
+ {
+ return is_array($data);
+ }
+
+ protected function getDomNode($data, DOMNode $parent)
+ {
+ $tag = $this->getChildTag($parent);
+ $container = $parent->ownerDocument->createDocumentFragment();
+ foreach ($data as $analyse) {
+ $value = (is_array($analyse)) ? null : $analyse ;
+ $child = $this->createElement($parent, $tag, $value);
+ $container->appendChild($child);
+ if (is_array($analyse))
+ $child->appendChild($this->getDomNode($analyse, $child));
+ }
+ return $container;
+ }
+
+ protected function getChildTag(DOMNode $node)
+ {
+ switch ($node->nodeName) {
+ case 'ol':
+ case 'ul':
+ case 'li':
+ return 'li';
+ break;
+ case 'tbody':
+ case 'table':
+ case 'thead':
+ return 'tr';
+ break;
+ case 'tr':
+ return 'td';
+ break;
+ default:
+ return 'span';
+ break;
+ }
+ }
+
+}
36 library/Respect/Template/Decorators/AbstractDecorator.php
@@ -0,0 +1,36 @@
+<?php
+namespace Respect\Template\Decorators;
+
+use \DOMNode;
+use \InvalidArgumentException as Argument;
+use \UnexpectedValueException as Unexpected;
+use Respect\Template\Document;
+use Respect\Template\Adapters\AbstractAdapter as Adapter;
+use Respect\Template\Query;
+
+abstract class AbstractDecorator
+{
+ final public function __construct($elements, Adapter $with=null)
+ {
+ if ($elements instanceof DOMNode)
+ $elements = array($elements);
+
+ if ($elements instanceof Query)
+ $elements = $elements->getResult();
+
+ if (!is_array($elements))
+ throw new Argument('Query or Array expected to decorate');
+
+ // Decorate the given elements selected
+ foreach ($elements as $element) {
+ if (!$element instanceof DOMNode)
+ throw new Unexpected('DOMNode expected for decoration');
+
+ if (!is_null($with))
+ $with = $with->adaptTo($element);
+ $this->decorate($element, $with);
+ }
+ }
+
+ abstract protected function decorate(DOMNode $node, DOMNode $with=null);
+}
14 library/Respect/Template/Decorators/Append.php
@@ -0,0 +1,14 @@
+<?php
+namespace Respect\Template\Decorators;
+
+use \DOMNode;
+use \DOMText;
+use \InvalidArgumentException as Argument;
+
+class Append extends AbstractDecorator
+{
+ protected function decorate(DOMNode $node, DOMNode $with=null)
+ {
+ $node->appendChild($with);
+ }
+}
17 library/Respect/Template/Decorators/Clean.php
@@ -0,0 +1,17 @@
+<?php
+namespace Respect\Template\Decorators;
+
+use \DOMNode;
+
+class Clean extends AbstractDecorator
+{
+ protected function decorate(DOMNode $node, DOMNode $with=null)
+ {
+ $remove = array();
+ foreach ($node->childNodes as $child)
+ $remove[] = $child;
+
+ foreach ($remove as $child)
+ $node->removeChild($child);
+ }
+}
15 library/Respect/Template/Decorators/CleanAppend.php
@@ -0,0 +1,15 @@
+<?php
+namespace Respect\Template\Decorators;
+
+use \DOMNode;
+use \UnexpectedValueException as Value;
+use \InvalidArgumentException as Argument;
+
+class CleanAppend extends AbstractDecorator
+{
+ protected function decorate(DOMNode $node, DOMNode $with=null)
+ {
+ new Clean(array($node));
+ $node->appendChild($with);
+ }
+}
16 library/Respect/Template/Decorators/Replace.php
@@ -0,0 +1,16 @@
+<?php
+namespace Respect\Template\Decorators;
+
+use \DOMNode;
+use \UnexpectedValueException as Unexpected;
+class Replace extends AbstractDecorator
+{
+ protected function decorate(DOMNode $node, DOMNode $with=null)
+ {
+ $old = $node;
+ $new = $old->ownerDocument->importNode($with, true);
+ $return = $old->parentNode->replaceChild($new, $old);
+ if ($return !== $old)
+ throw new Unexpected('Unable to replace node');
+ }
+}
89 library/Respect/Template/Document.php
@@ -0,0 +1,89 @@
+<?php
+namespace Respect\Template;
+
+use \DOMDocument;
+use \DOMImplementation;
+use \DOMXPath;
+use \InvalidArgumentException as Argument;
+use \UnexpectedValueException as Unexpected;
+use Zend\Dom\Query as DomQuery;
+/**
+ * Normalizes HTMl into a valid DOM XML document.
+ *
+ * @package Respect\Template
+ * @uses Zend_Dom_Query
+ * @author Augusto Pascutti <augusto@phpsp.org.br>
+ */
+class Document
+{
+ /**
+ * @var DOMDocument
+ */
+ private $dom;
+ /**
+ * @var Zend_Dom_Query
+ */
+ private $queryDocument;
+
+ /**
+ * @param string $htmlDocument
+ */
+ public function __construct($htmlDocument)
+ {
+ $this->dom = new DOMDocument();
+ $this->dom->strictErrorChecking = false;
+ $this->dom->loadHtml($htmlDocument);
+ }
+
+ /**
+ * @return DOMDocument
+ */
+ public function getDom()
+ {
+ return $this->dom;
+ }
+
+ /**
+ * Replaces this dom content with the given array.
+ * The array structure is: $array['Css Selector to Eelement'] = 'content';
+ *
+ * @param array $data
+ * @param string[optional] $decorator Class to be used as decorator
+ * @return Respect\Template\Document
+ */
+ public function decorate(array $data, $decorator = null)
+ {
+ foreach ($data as $selector=>$with) {
+ $adapter = Adapter::factory($this->getDom(), $with);
+ $decorator = $decorator ?: $adapter->getDecorator();
+ $query = new Query($this, $selector);
+ new $decorator($query, $adapter);
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the XML representation of the current DOM tree.
+ *
+ * @return string
+ */
+ public function render($beautiful=false)
+ {
+ $this->dom->formatOutput = $beautiful;
+ return $this->dom->saveHTML();
+ }
+
+ /**
+ * Returns XML to be parsed by CSS the selector.
+ * This will never be the final XML to be rendered.
+ *