123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304 |
- <?php namespace Laravel\Routing; use Closure, Laravel\Str, Laravel\Bundle;
- class Router {
- /**
- * All of the routes that have been registered.
- *
- * @var array
- */
- public static $routes = array();
- /**
- * All of the route names that have been matched with URIs.
- *
- * @var array
- */
- public static $names = array();
- /**
- * The wildcard patterns supported by the router.
- *
- * @var array
- */
- public static $patterns = array(
- '(:num)' => '([0-9]+)',
- '(:any)' => '([a-zA-Z0-9\.\-_]+)',
- );
- /**
- * The optional wildcard patterns supported by the router.
- *
- * @var array
- */
- public static $optional = array(
- '/(:num?)' => '(?:/([0-9]+)',
- '/(:any?)' => '(?:/([a-zA-Z0-9\.\-_]+)',
- );
- /**
- * Register a route with the router.
- *
- * <code>
- * // Register a route with the router
- * Router::register('GET /', function() {return 'Home!';});
- *
- * // Register a route that handles multiple URIs with the router
- * Router::register(array('GET /', 'GET /home'), function() {return 'Home!';});
- * </code>
- *
- * @param string|array $route
- * @param string $action
- * @return void
- */
- public static function register($route, $action)
- {
- foreach ((array) $route as $uri)
- {
- // If the action is a string, it is a pointer to a controller, so we
- // need to add it to the action array as a "uses" clause, which will
- // indicate to the route to call the controller when the route is
- // executed by the application.
- if (is_string($action))
- {
- static::$routes[$uri]['uses'] = $action;
- }
- // If the action is not a string, we can just simply cast it as an
- // array, then we will add all of the URIs to the action array as
- // the "handes" clause so we can easily check which URIs are
- // handled by the route instance.
- else
- {
- if ($action instanceof Closure) $action = array($action);
- static::$routes[$uri] = (array) $action;
- }
- static::$routes[$uri]['handles'] = (array) $route;
- }
- }
- /**
- * Find a route by the route's assigned name.
- *
- * @param string $name
- * @return array
- */
- public static function find($name)
- {
- if (isset(static::$names[$name])) return static::$names[$name];
- // If no route names have been found at all, we will assume no reverse
- // routing has been done, and we will load the routes file for all of
- // the bundle that are installed for the application.
- if (count(static::$names) == 0)
- {
- foreach (Bundle::names() as $bundle)
- {
- Bundle::routes($bundle);
- }
- }
- // To find a named route, we will iterate through every route defined
- // for the application. We will cache the routes by name so we can
- // load them very quickly if we need to find them a second time.
- foreach (static::$routes as $key => $value)
- {
- if (isset($value['name']) and $value['name'] == $name)
- {
- return static::$names[$name] = array($key => $value);
- }
- }
- }
- /**
- * Search the routes for the route matching a method and URI.
- *
- * @param string $method
- * @param string $uri
- * @return Route
- */
- public static function route($method, $uri)
- {
- // First we will make sure the bundle that handles the given URI has
- // been started for the current request. Bundles may handle any URI
- // as long as it begins with the string in the "handles" item of
- // the bundle's registration array.
- Bundle::start($bundle = Bundle::handles($uri));
- // All route URIs begin with the request method and have a leading
- // slash before the URI. We'll put the request method and URI in
- // that format so we can easily check for literal matches.
- $destination = $method.' /'.trim($uri, '/');
- if (array_key_exists($destination, static::$routes))
- {
- return new Route($destination, static::$routes[$destination], array());
- }
- // If we can't find a literal match, we'll iterate through all of
- // the registered routes to find a matching route that uses some
- // regular expressions or wildcards.
- if ( ! is_null($route = static::match($destination)))
- {
- return $route;
- }
- // If the bundle handling the request is not the default bundle,
- // we want to remove the root "handles" string from the URI so
- // it will not interfere with searching for a controller.
- //
- // If we left it on the URI, the root controller for the bundle
- // would need to be nested in directories matching the clause.
- // This will not intefere with the Route::handles method
- // as the destination is used to set the route's URIs.
- if ($bundle !== DEFAULT_BUNDLE)
- {
- $uri = str_replace(Bundle::get($bundle)->handles, '', $uri);
- $uri = ltrim($uri, '/');
- }
- $segments = Str::segments($uri);
- return static::controller($bundle, $method, $destination, $segments);
- }
- /**
- * Iterate through every route to find a matching route.
- *
- * @param string $destination
- * @return Route
- */
- protected static function match($destination)
- {
- foreach (static::$routes as $route => $action)
- {
- // We only need to check routes with regular expressions since
- // all other routes would have been able to be caught by the
- // check for literal matches we just did.
- if (strpos($route, '(') !== false)
- {
- $pattern = '#^'.static::wildcards($route).'$#';
- // If we get a match, we'll return the route and slice off
- // the first parameter match, as preg_match sets the first
- // array item to the full-text match.
- if (preg_match($pattern, $destination, $parameters))
- {
- return new Route($route, $action, array_slice($parameters, 1));
- }
- }
- }
- }
- /**
- * Attempt to find a controller for the incoming request.
- *
- * @param string $bundle
- * @param string $method
- * @param string $destination
- * @param array $segments
- * @return Route
- */
- protected static function controller($bundle, $method, $destination, $segments)
- {
- if (count($segments) == 0)
- {
- $uri = '/';
- // If the bundle is not the default bundle for the application, we'll
- // set the root URI as the root URI registered with the bundle in the
- // bundle configuration file for the application. It's registered in
- // the bundle configuration using the "handles" clause.
- if ($bundle !== DEFAULT_BUNDLE)
- {
- $uri = '/'.Bundle::get($bundle)->handles;
- }
- // We'll generate a default "uses" clause for the route action that
- // points to the default controller and method for the bundle so
- // that the route will execute the default.
- $action = array('uses' => Bundle::prefix($bundle).'home@index');
- return new Route($method.' '.$uri, $action);
- }
- $directory = Bundle::path($bundle).'controllers/';
- if ( ! is_null($key = static::locate($segments, $directory)))
- {
- // First, we'll extract the controller name, then, since we need
- // to extract the method and parameters, we will remove the name
- // of the controller from the URI. Then we can shift the method
- // off of the array of segments. Any remaining segments are the
- // parameters for the method.
- $controller = implode('.', array_slice($segments, 0, $key));
- $segments = array_slice($segments, $key);
- $method = (count($segments) > 0) ? array_shift($segments) : 'index';
- // We need to grab the prefix to the bundle so we can prefix
- // the route identifier with it. This informs the controller
- // class out of which bundle the controller instance should
- // be resolved when it is needed by the application.
- $prefix = Bundle::prefix($bundle);
- $action = array('uses' => $prefix.$controller.'@'.$method);
- return new Route($destination, $action, $segments);
- }
- }
- /**
- * Locate the URI segment matching a controller name.
- *
- * @param string $directory
- * @param array $segments
- * @return int
- */
- protected static function locate($segments, $directory)
- {
- for ($i = count($segments) - 1; $i >= 0; $i--)
- {
- // To find the proper controller, we need to iterate backwards through
- // the URI segments and take the first file that matches. That file
- // should be the deepest possible controller matched by the URI.
- if (file_exists($directory.implode('/', $segments).EXT))
- {
- return $i + 1;
- }
- // If a controller did not exist for the segments, we will pop
- // the last segment off of the array so that on the next run
- // through the loop we'll check one folder up from the one
- // we checked on this iteration.
- array_pop($segments);
- }
- }
- /**
- * Translate route URI wildcards into regular expressions.
- *
- * @param string $key
- * @return string
- */
- protected static function wildcards($key)
- {
- list($search, $replace) = array_divide(static::$optional);
- // For optional parameters, first translate the wildcards to their
- // regex equivalent, sans the ")?" ending. We'll add the endings
- // back on after we know how many replacements we made.
- $key = str_replace($search, $replace, $key, $count);
- if ($count > 0)
- {
- $key .= str_repeat(')?', $count);
- }
- return strtr($key, static::$patterns);
- }
- }
|