Пример #1
0
 public function testUniversalParams()
 {
     // Test data
     $params_get = array('page' => 2, 'per_page' => 10, 'num' => 1, 5 => 'ok', 'empty' => null, 'blank' => '');
     $params_post = array('first_name' => 'Trevor', 'last_name' => 'Suarez', 'num' => 2, 3 => 'hmm', 4 => 'thing');
     $cookies = array('user' => 'Rican7', 'PHPSESSID' => 'randomstring', 'num' => 3, 4 => 'dog');
     $named = array('id' => '1f8ae', 'num' => 4);
     // Create the request
     $request = new Request($params_get, $params_post, $cookies);
     // Set our named params
     $request->paramsNamed()->replace($named);
     // Merge our params for our expected results
     $params = array_merge($params_get, $params_post, $cookies, $named);
     $this->assertSame($params, $request->params());
     $this->assertSame($params['num'], $request->param('num'));
     $this->assertSame(null, $request->param('thisdoesntexist'));
 }
Пример #2
0
 /**
  * Dispatch the request to the approriate route(s)
  *
  * Dispatch with optionally injected dependencies
  * This DI allows for easy testing, object mocking, or class extension
  *
  * @param Request $request          The request object to give to each callback
  * @param Response $response        The response object to give to each callback
  * @param boolean $send_response    Whether or not to "send" the response after the last route has been matched
  * @param int $capture              Specify a DISPATCH_* constant to change the output capturing behavior
  * @access public
  * @return void|string
  */
 public function dispatch(Request $request = null, Response $response = null, $send_response = true, $capture = self::DISPATCH_NO_CAPTURE)
 {
     // Set/Initialize our objects to be sent in each callback
     $this->request = $request ?: Request::createFromGlobals();
     $this->response = $response ?: new Response();
     // Bind our objects to our service
     $this->service->bind($this->request, $this->response);
     // Grab some data from the request
     $uri = $this->request->uri(true);
     // Strip the query string
     $req_method = $this->request->method();
     // Set up some variables for matching
     $matched = 0;
     $methods_matched = array();
     $params = array();
     $apc = function_exists('apc_fetch');
     ob_start();
     foreach ($this->routes as $handler) {
         list($method, $_route, $callback, $count_match) = $handler;
         // Keep track of whether this specific request method was matched
         $method_match = null;
         // Was a method specified? If so, check it against the current request method
         if (is_array($method)) {
             foreach ($method as $test) {
                 if (strcasecmp($req_method, $test) === 0) {
                     $method_match = true;
                 } elseif (strcasecmp($req_method, 'HEAD') === 0 && (strcasecmp($test, 'HEAD') === 0 || strcasecmp($test, 'GET') === 0)) {
                     // Test for HEAD request (like GET)
                     $method_match = true;
                 }
             }
             if (null === $method_match) {
                 $method_match = false;
             }
         } elseif (null !== $method && strcasecmp($req_method, $method) !== 0) {
             $method_match = false;
             // Test for HEAD request (like GET)
             if (strcasecmp($req_method, 'HEAD') === 0 && (strcasecmp($method, 'HEAD') === 0 || strcasecmp($method, 'GET') === 0)) {
                 $method_match = true;
             }
         } elseif (null !== $method && strcasecmp($req_method, $method) === 0) {
             $method_match = true;
         }
         // If the method was matched or if it wasn't even passed (in the route callback)
         $possible_match = is_null($method_match) || $method_match;
         // ! is used to negate a match
         if (isset($_route[0]) && $_route[0] === '!') {
             $negate = true;
             $i = 1;
         } else {
             $negate = false;
             $i = 0;
         }
         // Check for a wildcard (match all)
         if ($_route === '*') {
             $match = true;
         } elseif ($_route === '404' && !$matched && count($methods_matched) <= 0) {
             // Easily handle 404's
             try {
                 $this->response->append(call_user_func($callback, $this->request, $this->response, $this->service, $this->app, $matched, $methods_matched));
             } catch (LockedResponseException $e) {
                 // Do nothing, since this is an automated behavior
             } catch (Exception $e) {
                 $this->error($e);
             }
             ++$matched;
             continue;
         } elseif ($_route === '405' && !$matched && count($methods_matched) > 0) {
             // Easily handle 405's
             try {
                 $this->response->append(call_user_func($callback, $this->request, $this->response, $this->service, $this->app, $matched, $methods_matched));
             } catch (LockedResponseException $e) {
                 // Do nothing, since this is an automated behavior
             } catch (Exception $e) {
                 $this->error($e);
             }
             ++$matched;
             continue;
         } elseif (isset($_route[$i]) && $_route[$i] === '@') {
             // @ is used to specify custom regex
             $match = preg_match('`' . substr($_route, $i + 1) . '`', $uri, $params);
         } else {
             // Compiling and matching regular expressions is relatively
             // expensive, so try and match by a substring first
             $route = null;
             $regex = false;
             $j = 0;
             $n = isset($_route[$i]) ? $_route[$i] : null;
             // Find the longest non-regex substring and match it against the URI
             while (true) {
                 if (!isset($_route[$i])) {
                     break;
                 } elseif (false === $regex) {
                     $c = $n;
                     $regex = $c === '[' || $c === '(' || $c === '.';
                     if (false === $regex && false !== isset($_route[$i + 1])) {
                         $n = $_route[$i + 1];
                         $regex = $n === '?' || $n === '+' || $n === '*' || $n === '{';
                     }
                     if (false === $regex && $c !== '/' && (!isset($uri[$j]) || $c !== $uri[$j])) {
                         continue 2;
                     }
                     $j++;
                 }
                 $route .= $_route[$i++];
             }
             // Check if there's a cached regex string
             if (false !== $apc) {
                 $regex = apc_fetch("route:{$route}");
                 if (false === $regex) {
                     $regex = $this->compileRoute($route);
                     apc_store("route:{$route}", $regex);
                 }
             } else {
                 $regex = $this->compileRoute($route);
             }
             $match = preg_match($regex, $uri, $params);
         }
         if (isset($match) && $match ^ $negate) {
             // Keep track of possibly matched methods
             $methods_matched = array_merge($methods_matched, (array) $method);
             $methods_matched = array_filter($methods_matched);
             $methods_matched = array_unique($methods_matched);
             if ($possible_match) {
                 if (!empty($params)) {
                     $this->request->paramsNamed()->merge($params);
                 }
                 // Try and call our route's callback
                 try {
                     $this->response->append(call_user_func($callback, $this->request, $this->response, $this->service, $this->app, $matched, $methods_matched));
                 } catch (LockedResponseException $e) {
                     // Do nothing, since this is an automated behavior
                 } catch (Exception $e) {
                     $this->error($e);
                 }
                 if ($_route !== '*') {
                     $count_match && ++$matched;
                 }
             }
         }
     }
     try {
         if (!$matched && count($methods_matched) > 0) {
             if (strcasecmp($req_method, 'OPTIONS') !== 0) {
                 $this->response->code(405);
             }
             $this->response->header('Allow', implode(', ', $methods_matched));
         } elseif (!$matched) {
             $this->response->code(404);
         }
         if ($this->response->chunked) {
             $this->response->chunk();
         } else {
             // Output capturing behavior
             switch ($capture) {
                 case self::DISPATCH_CAPTURE_AND_RETURN:
                     return ob_get_clean();
                     break;
                 case self::DISPATCH_CAPTURE_AND_REPLACE:
                     $this->response->body(ob_get_clean());
                     break;
                 case self::DISPATCH_CAPTURE_AND_PREPEND:
                     $this->response->prepend(ob_get_clean());
                     break;
                 case self::DISPATCH_CAPTURE_AND_APPEND:
                     $this->response->append(ob_get_clean());
                     break;
                 case self::DISPATCH_NO_CAPTURE:
                 default:
                     ob_end_flush();
                     break;
             }
         }
         // Test for HEAD request (like GET)
         if (strcasecmp($req_method, 'HEAD') === 0) {
             // HEAD requests shouldn't return a body
             $this->response->body('');
             ob_clean();
         }
     } catch (LockedResponseException $e) {
         // Do nothing, since this is an automated behavior
     }
     if ($send_response && !$this->response->isSent()) {
         $this->response->send();
     }
 }
Пример #3
0
 /**
  * Dispatch the request to the approriate route(s)
  *
  * Dispatch with optionally injected dependencies
  * This DI allows for easy testing, object mocking, or class extension
  *
  * @param Request $request              The request object to give to each callback
  * @param AbstractResponse $response    The response object to give to each callback
  * @param boolean $send_response        Whether or not to "send" the response after the last route has been matched
  * @param int $capture                  Specify a DISPATCH_* constant to change the output capturing behavior
  * @access public
  * @return void|string
  */
 public function dispatch(Request $request = null, AbstractResponse $response = null, $send_response = true, $capture = self::DISPATCH_NO_CAPTURE)
 {
     // Set/Initialize our objects to be sent in each callback
     $this->request = $request = $request ?: Request::createFromGlobals();
     $this->response = $response = $response ?: new Response();
     // Bind our objects to our service
     $this->app->request(function () use($request) {
         return $request;
     });
     $this->app->response(function () use($response) {
         return $response;
     });
     // Prepare any named routes
     $this->routes->prepareNamed();
     // Grab some data from the request
     $uri = $this->request->pathname();
     $req_method = $this->request->method();
     // Set up some variables for matching
     $skip_num = 0;
     $matched = $this->routes->cloneEmpty();
     // Get a clone of the routes collection, as it may have been injected
     $methods_matched = array();
     $params = array();
     $apc = function_exists('apc_fetch');
     ob_start();
     foreach ($this->routes as $route) {
         // Are we skipping any matches?
         if ($skip_num > 0) {
             $skip_num--;
             continue;
         }
         // Grab the properties of the route handler
         $method = $route->getMethod();
         $path = $route->getPath();
         $count_match = $route->getCountMatch();
         // Keep track of whether this specific request method was matched
         $method_match = null;
         // Was a method specified? If so, check it against the current request method
         if (is_array($method)) {
             foreach ($method as $test) {
                 if (strcasecmp($req_method, $test) === 0) {
                     $method_match = true;
                 } elseif (strcasecmp($req_method, 'HEAD') === 0 && (strcasecmp($test, 'HEAD') === 0 || strcasecmp($test, 'GET') === 0)) {
                     // Test for HEAD request (like GET)
                     $method_match = true;
                 }
             }
             if (null === $method_match) {
                 $method_match = false;
             }
         } elseif (null !== $method && strcasecmp($req_method, $method) !== 0) {
             $method_match = false;
             // Test for HEAD request (like GET)
             if (strcasecmp($req_method, 'HEAD') === 0 && (strcasecmp($method, 'HEAD') === 0 || strcasecmp($method, 'GET') === 0)) {
                 $method_match = true;
             }
         } elseif (null !== $method && strcasecmp($req_method, $method) === 0) {
             $method_match = true;
         }
         // If the method was matched or if it wasn't even passed (in the route callback)
         $possible_match = null === $method_match || $method_match;
         // ! is used to negate a match
         if (isset($path[0]) && $path[0] === '!') {
             $negate = true;
             $i = 1;
         } else {
             $negate = false;
             $i = 0;
         }
         // Check for a wildcard (match all)
         if ($path === '*') {
             $match = true;
         } elseif ($path === '404' && $matched->isEmpty() && count($methods_matched) <= 0 || $path === '405' && $matched->isEmpty() && count($methods_matched) > 0) {
             // Easily handle 40x's
             // TODO: Possibly remove in future, here for backwards compatibility
             $this->onHttpError($route);
             continue;
         } elseif (isset($path[$i]) && $path[$i] === '@') {
             // @ is used to specify custom regex
             $match = preg_match('`' . substr($path, $i + 1) . '`', $uri, $params);
         } else {
             // Compiling and matching regular expressions is relatively
             // expensive, so try and match by a substring first
             $expression = null;
             $regex = false;
             $j = 0;
             $n = isset($path[$i]) ? $path[$i] : null;
             // Find the longest non-regex substring and match it against the URI
             while (true) {
                 if (!isset($path[$i])) {
                     break;
                 } elseif (false === $regex) {
                     $c = $n;
                     $regex = $c === '[' || $c === '(' || $c === '.';
                     if (false === $regex && false !== isset($path[$i + 1])) {
                         $n = $path[$i + 1];
                         $regex = $n === '?' || $n === '+' || $n === '*' || $n === '{';
                     }
                     if (false === $regex && $c !== '/' && (!isset($uri[$j]) || $c !== $uri[$j])) {
                         continue 2;
                     }
                     $j++;
                 }
                 $expression .= $path[$i++];
             }
             // Check if there's a cached regex string
             if (false !== $apc) {
                 $regex = apc_fetch("route:{$expression}");
                 if (false === $regex) {
                     $regex = $this->compileRoute($expression);
                     apc_store("route:{$expression}", $regex);
                 }
             } else {
                 $regex = $this->compileRoute($expression);
             }
             $match = preg_match($regex, $uri, $params);
         }
         if (isset($match) && $match ^ $negate) {
             if ($possible_match) {
                 if (!empty($params)) {
                     /**
                      * URL Decode the params according to RFC 3986
                      * @link http://www.faqs.org/rfcs/rfc3986
                      *
                      * Decode here AFTER matching as per @chriso's suggestion
                      * @link https://github.com/chriso/klein.php/issues/117#issuecomment-21093915
                      */
                     $params = array_map('rawurldecode', $params);
                     $this->request->paramsNamed()->merge($params);
                 }
                 // Handle our response callback
                 try {
                     $this->handleRouteCallback($route, $matched, $methods_matched);
                 } catch (DispatchHaltedException $e) {
                     switch ($e->getCode()) {
                         case DispatchHaltedException::SKIP_THIS:
                             continue 2;
                             break;
                         case DispatchHaltedException::SKIP_NEXT:
                             $skip_num = $e->getNumberOfSkips();
                             break;
                         case DispatchHaltedException::SKIP_REMAINING:
                             break 2;
                         default:
                             throw $e;
                     }
                 }
                 if ($path !== '*') {
                     $count_match && $matched->add($route);
                 }
             }
             // Keep track of possibly matched methods
             $methods_matched = array_merge($methods_matched, (array) $method);
             $methods_matched = array_filter($methods_matched);
             $methods_matched = array_unique($methods_matched);
         }
     }
     // Handle our 404/405 conditions
     try {
         if ($matched->isEmpty() && count($methods_matched) > 0) {
             // Add our methods to our allow header
             $this->response->header('Allow', implode(', ', $methods_matched));
             if (strcasecmp($req_method, 'OPTIONS') !== 0) {
                 throw HttpException::createFromCode(405);
             }
         } elseif ($matched->isEmpty()) {
             throw HttpException::createFromCode(404);
         }
     } catch (HttpExceptionInterface $e) {
         // Grab our original response lock state
         $locked = $this->response->isLocked();
         // Call our http error handlers
         $this->httpError($e, $matched, $methods_matched);
         // Make sure we return our response to its original lock state
         if (!$locked) {
             $this->response->unlock();
         }
     }
     try {
         if ($this->response->chunked) {
             $this->response->chunk();
         } else {
             // Output capturing behavior
             switch ($capture) {
                 case self::DISPATCH_CAPTURE_AND_RETURN:
                     $buffed_content = null;
                     if (ob_get_level()) {
                         $buffed_content = ob_get_clean();
                     }
                     return $buffed_content;
                     break;
                 case self::DISPATCH_CAPTURE_AND_REPLACE:
                     if (ob_get_level()) {
                         $this->response->body(ob_get_clean());
                     }
                     break;
                 case self::DISPATCH_CAPTURE_AND_PREPEND:
                     if (ob_get_level()) {
                         $this->response->prepend(ob_get_clean());
                     }
                     break;
                 case self::DISPATCH_CAPTURE_AND_APPEND:
                     if (ob_get_level()) {
                         $this->response->append(ob_get_clean());
                     }
                     break;
                 case self::DISPATCH_NO_CAPTURE:
                 default:
                     if (ob_get_level()) {
                         ob_end_flush();
                     }
             }
         }
         // Test for HEAD request (like GET)
         if (strcasecmp($req_method, 'HEAD') === 0) {
             // HEAD requests shouldn't return a body
             $this->response->body('');
             if (ob_get_level()) {
                 ob_clean();
             }
         }
     } catch (LockedResponseException $e) {
         // Do nothing, since this is an automated behavior
     }
     // Run our after dispatch callbacks
     $this->callAfterDispatchCallbacks();
     if ($send_response && !$this->response->isSent()) {
         $this->response->send();
     }
 }