Example #1
0
 /**
  * Setup our test
  * (runs before each test)
  * 
  * @access protected
  * @return void
  */
 protected function setUp()
 {
     // Create a new klein app,
     // since we need one pretty much everywhere
     $this->router = new Router(new App());
     $this->var_dir = App::i()->var_dir();
     if (!file_exists($this->var_dir)) {
         mkdir($this->var_dir, 0777);
     }
 }
Example #2
0
 /**
  * @expectedException BadMethodCallException
  */
 public function testCallBadMethod()
 {
     $app = new App();
     $app->random_thing_that_doesnt_exist();
 }
Example #3
0
 public function testRegisterDuplicateMethod()
 {
     $app = new App();
     $app->foo(function () {
         return 'foo';
     });
     $app->foo(function () {
         return 'foo2';
     });
 }
Example #4
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();
     }
 }