merged skunkworks into develop.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
<?php namespace Laravel\Routing;
|
||||
|
||||
use Laravel\IoC;
|
||||
use Laravel\Str;
|
||||
use Laravel\View;
|
||||
use Laravel\Bundle;
|
||||
use Laravel\Request;
|
||||
use Laravel\Redirect;
|
||||
use Laravel\Response;
|
||||
@@ -30,13 +32,15 @@ abstract class Controller {
|
||||
protected $filters = array();
|
||||
|
||||
/**
|
||||
* Handle the delegation of a route to a controller method.
|
||||
* Call an action method on a controller.
|
||||
*
|
||||
* The controller destination should follow a {controller}@{method} convention.
|
||||
* Nested controllers may be delegated to using dot syntax.
|
||||
* <code>
|
||||
* // Call the "show" method on the "user" controller
|
||||
* $response = Controller::call('user@show');
|
||||
*
|
||||
* For example, a destination of "user.profile@show" would call the User_Profile
|
||||
* controller's show method with the given parameters.
|
||||
* // Call the "profile" method on the "user/admin" controller and pass parameters
|
||||
* $response = Controller::call('user.admin@profile', array($username));
|
||||
* </code>
|
||||
*
|
||||
* @param string $destination
|
||||
* @param array $parameters
|
||||
@@ -44,43 +48,47 @@ abstract class Controller {
|
||||
*/
|
||||
public static function call($destination, $parameters = array())
|
||||
{
|
||||
if (strpos($destination, '@') === false)
|
||||
{
|
||||
throw new \InvalidArgumentException("Route delegate [{$destination}] has an invalid format.");
|
||||
}
|
||||
list($bundle, $destination) = Bundle::parse($destination);
|
||||
|
||||
// We will always start the bundle, just in case the developer is pointing
|
||||
// a route to another bundle. This allows us to lazy load the bundle and
|
||||
// improve performance since the bundle is not loaded on every request.
|
||||
Bundle::start($bundle);
|
||||
|
||||
list($controller, $method) = explode('@', $destination);
|
||||
|
||||
$controller = static::resolve($controller);
|
||||
$controller = static::resolve($bundle, $controller);
|
||||
|
||||
if (is_null($controller))
|
||||
{
|
||||
return Response::error('404');
|
||||
}
|
||||
// If the controller could not be resolved, we're out of options and will
|
||||
// return the 404 error response. Of course, if we found the controller,
|
||||
// we can go ahead and execute the requested method on the instance.
|
||||
if (is_null($controller)) return Response::error('404');
|
||||
|
||||
return $controller->execute($method, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a controller name to a controller instance.
|
||||
* Resolve a bundle and controller name to a controller instance.
|
||||
*
|
||||
* @param string $container
|
||||
* @param string $bundle
|
||||
* @param string $controller
|
||||
* @return Controller
|
||||
*/
|
||||
public static function resolve($controller)
|
||||
public static function resolve($bundle, $controller)
|
||||
{
|
||||
if ( ! static::load($controller)) return;
|
||||
if ( ! static::load($bundle, $controller)) return;
|
||||
|
||||
// If the controller is registered in the IoC container, we will resolve
|
||||
// it out of the container. Using constructor injection on controllers
|
||||
// via the container allows more flexible and testable applications.
|
||||
if (IoC::registered('controllers.'.$controller))
|
||||
$resolver = 'controller: '.Bundle::identifier($bundle, $controller);
|
||||
|
||||
if (IoC::registered($resolver))
|
||||
{
|
||||
return IoC::resolve('controllers.'.$controller);
|
||||
return IoC::resolve($resolver);
|
||||
}
|
||||
|
||||
$controller = str_replace(' ', '_', ucwords(str_replace('.', ' ', $controller))).'_Controller';
|
||||
$controller = static::format($bundle, $controller);
|
||||
|
||||
$controller = new $controller;
|
||||
|
||||
@@ -89,7 +97,7 @@ abstract class Controller {
|
||||
// layout property, replacing the string layout name.
|
||||
if ( ! is_null($controller->layout))
|
||||
{
|
||||
$controller->layout = View::make($controller->layout);
|
||||
$controller->layout = $controller->layout();
|
||||
}
|
||||
|
||||
return $controller;
|
||||
@@ -98,14 +106,15 @@ abstract class Controller {
|
||||
/**
|
||||
* Load the file for a given controller.
|
||||
*
|
||||
* @param string $bundle
|
||||
* @param string $controller
|
||||
* @return bool
|
||||
*/
|
||||
protected static function load($controller)
|
||||
protected static function load($bundle, $controller)
|
||||
{
|
||||
$controller = strtolower(str_replace('.', '/', $controller));
|
||||
|
||||
if (file_exists($path = CONTROLLER_PATH.$controller.EXT))
|
||||
if (file_exists($path = Bundle::path($bundle).'controllers/'.$controller.EXT))
|
||||
{
|
||||
require_once $path;
|
||||
|
||||
@@ -115,6 +124,24 @@ abstract class Controller {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a bundle and controller identifier into the controller's class name.
|
||||
*
|
||||
* @param string $bundle
|
||||
* @param string $controller
|
||||
* @return string
|
||||
*/
|
||||
protected static function format($bundle, $controller)
|
||||
{
|
||||
// If the controller's bundle is not the application bundle, we will
|
||||
// prepend the bundle to the identifier so the bundle is prefixed to
|
||||
// the class name when it is formatted. Bundle controllers are
|
||||
// always prefixed with the bundle's name by convention.
|
||||
if ($bundle !== DEFAULT_BUNDLE) $controller = $bundle.'.'.$controller;
|
||||
|
||||
return Str::classify($controller).'_Controller';
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a controller method with the given parameters.
|
||||
*
|
||||
@@ -124,47 +151,18 @@ abstract class Controller {
|
||||
*/
|
||||
public function execute($method, $parameters = array())
|
||||
{
|
||||
// Again, as was the case with route closures, if the controller
|
||||
// "before" filters return a response, it will be considered the
|
||||
// response to the request and the controller method will not be
|
||||
// used to handle the request to the application.
|
||||
// Again, as was the case with route closures, if the controller "before"
|
||||
// filters return a response, it will be considered the response to the
|
||||
// request and the controller method will not be used to handle the
|
||||
// request to the application.
|
||||
$response = Filter::run($this->filters('before', $method), array(), true);
|
||||
|
||||
if (is_null($response))
|
||||
{
|
||||
// The developer may mark the controller as being "RESTful" which
|
||||
// indicates that the controller actions are prefixed with the
|
||||
// HTTP verb they respond to rather than the word "action".
|
||||
if ($this->restful)
|
||||
{
|
||||
$action = strtolower(Request::method()).'_'.$method;
|
||||
}
|
||||
else
|
||||
{
|
||||
$action = "action_{$method}";
|
||||
}
|
||||
|
||||
$response = call_user_func_array(array($this, $action), $parameters);
|
||||
|
||||
// If the controller has specified a layout view. The response
|
||||
// returned by the controller method will be bound to that view
|
||||
// and the layout will be considered the response.
|
||||
if (is_null($response) and ! is_null($this->layout))
|
||||
{
|
||||
$response = $this->layout;
|
||||
}
|
||||
$response = $this->response($method, $parameters);
|
||||
}
|
||||
|
||||
if ( ! $response instanceof Response)
|
||||
{
|
||||
$response = new Response($response);
|
||||
}
|
||||
|
||||
// Stringify the response. We need to force the response to be
|
||||
// stringed before closing the session, since the developer may
|
||||
// be using the session within their views, so we cannot age
|
||||
// the session data until the view is rendered.
|
||||
$response->content = $response->render();
|
||||
$response = Response::prepare($response);
|
||||
|
||||
Filter::run($this->filters('after', $method), array($response));
|
||||
|
||||
@@ -172,9 +170,45 @@ abstract class Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register filters on the controller's methods.
|
||||
* Execute a controller action and return the response.
|
||||
*
|
||||
* Generally, this method will be used in the controller's constructor.
|
||||
* Unlike the "execute" method, no filters will be run and the response
|
||||
* from the controller action will not be changed in any way before it
|
||||
* is returned to the consumer.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $parameters
|
||||
* @return mixed
|
||||
*/
|
||||
public function response($method, $parameters = array())
|
||||
{
|
||||
// The developer may mark the controller as being "RESTful" which
|
||||
// indicates that the controller actions are prefixed with the
|
||||
// HTTP verb they respond to rather than the word "action".
|
||||
if ($this->restful)
|
||||
{
|
||||
$action = strtolower(Request::method()).'_'.$method;
|
||||
}
|
||||
else
|
||||
{
|
||||
$action = "action_{$method}";
|
||||
}
|
||||
|
||||
$response = call_user_func_array(array($this, $action), $parameters);
|
||||
|
||||
// If the controller has specified a layout view. The response
|
||||
// returned by the controller method will be bound to that
|
||||
// view and the layout will be considered the response.
|
||||
if (is_null($response) and ! is_null($this->layout))
|
||||
{
|
||||
$response = $this->layout;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register filters on the controller's methods.
|
||||
*
|
||||
* <code>
|
||||
* // Set a "foo" after filter on the controller
|
||||
@@ -184,46 +218,54 @@ abstract class Controller {
|
||||
* $this->filter('after', 'foo|bar')->only(array('user', 'profile'));
|
||||
* </code>
|
||||
*
|
||||
* @param string $event
|
||||
* @param string|array $filters
|
||||
* @param mixed $parameters
|
||||
* @return Filter_Collection
|
||||
*/
|
||||
protected function filter($name, $filters)
|
||||
protected function filter($event, $filters, $parameters = null)
|
||||
{
|
||||
$this->filters[$name][] = new Filter_Collection($name, $filters);
|
||||
$this->filters[$event][] = new Filter_Collection($filters, $parameters);
|
||||
|
||||
return $this->filters[$name][count($this->filters[$name]) - 1];
|
||||
return $this->filters[$event][count($this->filters[$event]) - 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of filter names defined for the destination.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $event
|
||||
* @param string $method
|
||||
* @return array
|
||||
*/
|
||||
protected function filters($name, $method)
|
||||
protected function filters($event, $method)
|
||||
{
|
||||
if ( ! isset($this->filters[$name])) return array();
|
||||
if ( ! isset($this->filters[$event])) return array();
|
||||
|
||||
$filters = array();
|
||||
|
||||
foreach ($this->filters[$name] as $filter)
|
||||
foreach ($this->filters[$event] as $collection)
|
||||
{
|
||||
if ($filter->applies($method))
|
||||
if ($collection->applies($method))
|
||||
{
|
||||
$filters = array_merge($filters, $filter->filters);
|
||||
$filters[] = $collection;
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($filters);
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the layout that is assigned to the controller.
|
||||
*
|
||||
* @return View
|
||||
*/
|
||||
public function layout()
|
||||
{
|
||||
return View::make($this->layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Magic Method to handle calls to undefined functions on the controller.
|
||||
*
|
||||
* By default, the 404 response will be returned for an calls to undefined
|
||||
* methods on the controller. However, this method may also be overridden
|
||||
* and used as a pseudo-router by the controller.
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
@@ -243,12 +285,9 @@ abstract class Controller {
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
if (IoC::registered($key))
|
||||
{
|
||||
return IoC::resolve($key);
|
||||
}
|
||||
if (IoC::registered($key)) return IoC::resolve($key);
|
||||
|
||||
throw new \OutOfBoundsException("Attempting to access undefined property [$key] on controller.");
|
||||
throw new \Exception("Accessing undefined property [$key] on controller.");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php namespace Laravel\Routing;
|
||||
|
||||
use Closure;
|
||||
use Laravel\Bundle;
|
||||
use Laravel\Request;
|
||||
|
||||
class Filter {
|
||||
@@ -9,60 +11,53 @@ class Filter {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $filters = array();
|
||||
public static $filters = array();
|
||||
|
||||
/**
|
||||
* Register an array of route filters.
|
||||
* All of the registered filter aliases.
|
||||
*
|
||||
* @param array $filters
|
||||
* @var array
|
||||
*/
|
||||
public static $aliases = array();
|
||||
|
||||
/**
|
||||
* Register a filter for the application.
|
||||
*
|
||||
* <code>
|
||||
* // Register a closure as a filter
|
||||
* Filter::register('before', function() {});
|
||||
*
|
||||
* // Register a class callback as a filter
|
||||
* Filter::register('before', array('Class', 'method'));
|
||||
* </code>
|
||||
*
|
||||
* @param string $name
|
||||
* @param Closure $callback
|
||||
* @return void
|
||||
*/
|
||||
public static function register($filters)
|
||||
public static function register($name, Closure $callback)
|
||||
{
|
||||
static::$filters = array_merge(static::$filters, $filters);
|
||||
if (isset(static::$aliases[$name])) $name = static::$aliases[$name];
|
||||
|
||||
static::$filters[$name] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a filter or set of filters.
|
||||
* Alias a filter so it can be used by another name.
|
||||
*
|
||||
* @param array|string $filters
|
||||
* @param array $pass
|
||||
* @param bool $override
|
||||
* @return mixed
|
||||
* This is convenient for shortening filters that are registered by bundles.
|
||||
*
|
||||
* @param string $filter
|
||||
* @param string $alias
|
||||
* @return void
|
||||
*/
|
||||
public static function run($filters, $pass = array(), $override = false)
|
||||
public static function alias($filter, $alias)
|
||||
{
|
||||
foreach (static::parse($filters) as $filter)
|
||||
{
|
||||
$parameters = array();
|
||||
|
||||
// Parameters may be passed into routes by specifying the list of
|
||||
// parameters after a colon. If parameters are present, we will
|
||||
// merge them into the parameter array that was passed to the
|
||||
// method and slice the parameters off of the filter string.
|
||||
if (($colon = strpos($filter, ':')) !== false)
|
||||
{
|
||||
$parameters = explode(',', substr($filter, $colon + 1));
|
||||
|
||||
$filter = substr($filter, 0, $colon);
|
||||
}
|
||||
|
||||
if ( ! isset(static::$filters[$filter])) continue;
|
||||
|
||||
$parameters = array_merge($pass, $parameters);
|
||||
|
||||
$response = call_user_func_array(static::$filters[$filter], $parameters);
|
||||
|
||||
// "Before" filters may override the request cycle. For example,
|
||||
// an authentication filter may redirect a user to a login view
|
||||
// if they are not logged in. Because of this, we will return
|
||||
// the first filter response if overriding is enabled.
|
||||
if ( ! is_null($response) and $override) return $response;
|
||||
}
|
||||
static::$aliases[$alias] = $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string of filters into an array.
|
||||
* Parse a filter definition into an array of filters.
|
||||
*
|
||||
* @param string|array $filters
|
||||
* @return array
|
||||
@@ -72,16 +67,67 @@ class Filter {
|
||||
return (is_string($filters)) ? explode('|', $filters) : (array) $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a filter or set of filters.
|
||||
*
|
||||
* @param array $collections
|
||||
* @param array $pass
|
||||
* @param bool $override
|
||||
* @return mixed
|
||||
*/
|
||||
public static function run($collections, $pass = array(), $override = false)
|
||||
{
|
||||
foreach ($collections as $collection)
|
||||
{
|
||||
foreach ($collection->filters as $filter)
|
||||
{
|
||||
list($filter, $parameters) = $collection->get($filter);
|
||||
|
||||
// We will also go ahead and start the bundle for the developer. This allows
|
||||
// the developer to specify bundle filters on routes without starting the
|
||||
// bundle manually, and performance is improved since the bundle is only
|
||||
// started when needed.
|
||||
Bundle::start(Bundle::name($filter));
|
||||
|
||||
if ( ! isset(static::$filters[$filter])) continue;
|
||||
|
||||
$callback = static::$filters[$filter];
|
||||
|
||||
// Parameters may be passed into filters by specifying the list of parameters
|
||||
// as an array, or by registering a Closure which will return the array of
|
||||
// parameters. If parameters are present, we will merge them with the
|
||||
// parameters that were given to the method.
|
||||
$response = call_user_func_array($callback, array_merge($pass, $parameters));
|
||||
|
||||
// "Before" filters may override the request cycle. For example, an auth
|
||||
// filter may redirect a user to a login view if they are not logged in.
|
||||
// Because of this, we will return the first filter response if
|
||||
// overriding is enabled for the filter collections
|
||||
if ( ! is_null($response) and $override)
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Filter_Collection {
|
||||
|
||||
/**
|
||||
* The event being filtered.
|
||||
* The filters contained by the collection.
|
||||
*
|
||||
* @var string
|
||||
* @var string|array
|
||||
*/
|
||||
public $name;
|
||||
public $filters = array();
|
||||
|
||||
/**
|
||||
* The parameters specified for the filter.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $parameters;
|
||||
|
||||
/**
|
||||
* The included controller methods.
|
||||
@@ -97,13 +143,6 @@ class Filter_Collection {
|
||||
*/
|
||||
public $except = array();
|
||||
|
||||
/**
|
||||
* The filters contained by the collection.
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
public $filters = array();
|
||||
|
||||
/**
|
||||
* The HTTP methods for which the filter applies.
|
||||
*
|
||||
@@ -114,21 +153,73 @@ class Filter_Collection {
|
||||
/**
|
||||
* Create a new filter collection instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string|array $filters
|
||||
* @param mixed $parameters
|
||||
*/
|
||||
public function __construct($name, $filters)
|
||||
public function __construct($filters, $parameters = null)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->parameters = $parameters;
|
||||
$this->filters = Filter::parse($filters);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this collection's filters apply to a given method.
|
||||
* Parse the filter string, returning the filter name and parameters.
|
||||
*
|
||||
* Methods may be included / excluded using the "only" and "except" methods on the
|
||||
* filter collection. Also, the "on" method may be used to set certain filters to
|
||||
* only run when the request uses a given HTTP verb.
|
||||
* @param string $filter
|
||||
* @return array
|
||||
*/
|
||||
public function get($filter)
|
||||
{
|
||||
// If the parameters were specified by passing an array into the collection,
|
||||
// then we will simply return those parameters. Combining passed parameters
|
||||
// with parameters specified directly in the filter attachment is not
|
||||
// currently supported by the framework.
|
||||
if ( ! is_null($this->parameters))
|
||||
{
|
||||
return array($filter, $this->parameters());
|
||||
}
|
||||
|
||||
// If no parameters were specified when the collection was created, we will
|
||||
// check the filter string itself to see if the parameters were injected
|
||||
// into the string as raw values, such as "role:admin".
|
||||
if (($colon = strpos(Bundle::element($filter), ':')) !== false)
|
||||
{
|
||||
$parameters = explode(',', substr(Bundle::element($filter), $colon + 1));
|
||||
|
||||
// If the filter belongs to a bundle, we need to re-calculate the position
|
||||
// of the parameter colon, since we originally calculated it without the
|
||||
// bundle identifier because the identifier uses colons as well.
|
||||
if (($bundle = Bundle::name($filter)) !== DEFAULT_BUNDLE)
|
||||
{
|
||||
$colon = strlen($bundle.'::') + $colon;
|
||||
}
|
||||
|
||||
return array(substr($filter, 0, $colon), $parameters);
|
||||
}
|
||||
|
||||
// If no parameters were specified when the collection was created or
|
||||
// in the filter string, we will just return the filter name as is
|
||||
// and give back an empty array of parameters.
|
||||
return array($filter, array());
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate the collection's parameters and return a parameters array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parameters()
|
||||
{
|
||||
if ($this->parameters instanceof Closure)
|
||||
{
|
||||
$this->parameters = call_user_func($this->parameters);
|
||||
}
|
||||
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this collection's filters apply to a given method.
|
||||
*
|
||||
* @param string $method
|
||||
* @return bool
|
||||
@@ -145,7 +236,9 @@ class Filter_Collection {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count($this->methods) > 0 and ! in_array(strtolower(Request::method()), $this->methods))
|
||||
$request = strtolower(Request::method());
|
||||
|
||||
if (count($this->methods) > 0 and ! in_array($request, $this->methods))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -156,15 +249,12 @@ class Filter_Collection {
|
||||
/**
|
||||
* Set the excluded controller methods.
|
||||
*
|
||||
* When methods are excluded, the collection's filters will be run for each
|
||||
* controller method except those explicitly specified via this method.
|
||||
*
|
||||
* <code>
|
||||
* // Specify a filter for all methods except "index"
|
||||
* $this->filter('before', 'auth')->except('index');
|
||||
*
|
||||
* // Specify a filter for all methods except "index" and "home"
|
||||
* $this->filter('before', 'auth')->except('index', 'home');
|
||||
* $this->filter('before', 'auth')->except(array('index', 'home'));
|
||||
* </code>
|
||||
*
|
||||
* @param array $methods
|
||||
@@ -172,23 +262,19 @@ class Filter_Collection {
|
||||
*/
|
||||
public function except($methods)
|
||||
{
|
||||
$this->except = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods;
|
||||
$this->except = (array) $methods;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the included controller methods.
|
||||
*
|
||||
* This method is the inverse of the "except" methods. The methods specified
|
||||
* via this method are the only controller methods on which the collection's
|
||||
* filters will be run.
|
||||
*
|
||||
* <code>
|
||||
* // Specify a filter for only the "index" method
|
||||
* $this->filter('before', 'auth')->only('index');
|
||||
*
|
||||
* // Specify a filter for only the "index" and "home" methods
|
||||
* $this->filter('before', 'auth')->only('index', 'home');
|
||||
* $this->filter('before', 'auth')->only(array('index', 'home'));
|
||||
* </code>
|
||||
*
|
||||
* @param array $methods
|
||||
@@ -196,23 +282,19 @@ class Filter_Collection {
|
||||
*/
|
||||
public function only($methods)
|
||||
{
|
||||
$this->only = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods;
|
||||
$this->only = (array) $methods;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the HTTP methods for which the filter applies.
|
||||
*
|
||||
* Since some filters, such as the CSRF filter, only make sense in a POST
|
||||
* request context, this method allows you to limit which HTTP methods
|
||||
* the filter will apply to.
|
||||
*
|
||||
* <code>
|
||||
* // Specify that a filter only applies on POST requests
|
||||
* $this->filter('before', 'csrf')->on('post');
|
||||
*
|
||||
* // Specify that a filter applies for multiple HTTP request methods
|
||||
* $this->filter('before', 'csrf')->on('post', 'put');
|
||||
* $this->filter('before', 'csrf')->on(array('post', 'put'));
|
||||
* </code>
|
||||
*
|
||||
* @param array $methods
|
||||
@@ -220,13 +302,7 @@ class Filter_Collection {
|
||||
*/
|
||||
public function on($methods)
|
||||
{
|
||||
$methods = (count(func_get_args()) > 1) ? func_get_args() : (array) $methods;
|
||||
|
||||
foreach ($methods as $method)
|
||||
{
|
||||
$this->methods[] = strtolower($method);
|
||||
}
|
||||
|
||||
$method = array_map('strtolower', (array) $methods);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<?php namespace Laravel\Routing;
|
||||
|
||||
use Laravel\Arr;
|
||||
use RecursiveIteratorIterator as Iterator;
|
||||
use RecursiveDirectoryIterator as DirectoryIterator;
|
||||
|
||||
class Loader {
|
||||
|
||||
/**
|
||||
* The location of the base routes file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $base;
|
||||
|
||||
/**
|
||||
* The directory containing nested route files.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $nest;
|
||||
|
||||
/**
|
||||
* A cache for all of the routes defined for the entire application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $everything;
|
||||
|
||||
/**
|
||||
* Create a new route loader instance.
|
||||
*
|
||||
* @param string $base
|
||||
* @param string $nest
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($base, $nest)
|
||||
{
|
||||
$this->base = $base;
|
||||
$this->nest = $nest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the applicable routes for a given URI.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return array
|
||||
*/
|
||||
public function load($uri)
|
||||
{
|
||||
$segments = Arr::without(explode('/', $uri), '');
|
||||
|
||||
return array_merge($this->nested($segments), require $this->base.'routes'.EXT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate routes from the routes directory for a given URI.
|
||||
*
|
||||
* This method works backwards through the URI segments until we find the
|
||||
* deepest possible matching route directory. Once the deepest directory
|
||||
* is found, all of the applicable routes will be returend.
|
||||
*
|
||||
* @param array $segments
|
||||
* @return array
|
||||
*/
|
||||
protected function nested($segments)
|
||||
{
|
||||
foreach (array_reverse($segments, true) as $key => $value)
|
||||
{
|
||||
$path = $this->nest.implode('/', array_slice($segments, 0, $key + 1)).EXT;
|
||||
|
||||
if (file_exists($path)) return require $path;
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get every route defined for the application.
|
||||
*
|
||||
* The entire routes directory will be searched recursively to gather
|
||||
* every route for the application. Of course, the routes in the root
|
||||
* routes file will be returned as well.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function everything()
|
||||
{
|
||||
if ( ! is_null($this->everything)) return $this->everything;
|
||||
|
||||
$routes = array();
|
||||
|
||||
// First, we'll grab the base routes from the application directory.
|
||||
// Once we have these, we'll merge all of the nested routes in the
|
||||
// routes directory into this array of routes.
|
||||
if (file_exists($path = $this->base.'routes'.EXT))
|
||||
{
|
||||
$routes = array_merge($routes, require $path);
|
||||
}
|
||||
|
||||
if ( ! is_dir($this->nest)) return $routes;
|
||||
|
||||
$iterator = new Iterator(new DirectoryIterator($this->nest), Iterator::SELF_FIRST);
|
||||
|
||||
foreach ($iterator as $file)
|
||||
{
|
||||
// Since some Laravel developers may place HTML files in the route
|
||||
// directories, we will check for the PHP extension before merging
|
||||
// the file. Typically, the HTML files are present in installations
|
||||
// that are not using mod_rewrite and the public directory.
|
||||
if (filetype($file) === 'file' and strpos($file, EXT) !== false)
|
||||
{
|
||||
$routes = array_merge(require $file, $routes);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->everything = $routes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php namespace Laravel\Routing;
|
||||
|
||||
use Closure;
|
||||
use Laravel\Arr;
|
||||
use Laravel\Bundle;
|
||||
use Laravel\Response;
|
||||
|
||||
class Route {
|
||||
@@ -14,18 +14,25 @@ class Route {
|
||||
public $key;
|
||||
|
||||
/**
|
||||
* The URIs the route responds to.
|
||||
* The URI the route responds to.
|
||||
*
|
||||
* @var array
|
||||
* @var string
|
||||
*/
|
||||
public $uris;
|
||||
|
||||
/**
|
||||
* The route callback or array.
|
||||
* The bundle in which the route was registered.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $bundle;
|
||||
|
||||
/**
|
||||
* The action that is assigned to the route.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
public $callback;
|
||||
public $action;
|
||||
|
||||
/**
|
||||
* The parameters that will passed to the route callback.
|
||||
@@ -38,62 +45,43 @@ class Route {
|
||||
* Create a new Route instance.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $callback
|
||||
* @param array $action
|
||||
* @param array $parameters
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($key, $callback, $parameters = array())
|
||||
public function __construct($key, $action, $parameters = array())
|
||||
{
|
||||
$this->key = $key;
|
||||
$this->callback = $callback;
|
||||
$this->action = $action;
|
||||
$this->parameters = $parameters;
|
||||
|
||||
// Extract each URI from the route key. Since the route key has the
|
||||
// request method, we will extract that from the string. If the URI
|
||||
// points to the root of the application, a single forward slash
|
||||
// will be returned since that is used for the root route.
|
||||
if (strpos($key, ', ') === false)
|
||||
{
|
||||
$this->uris = array($this->extract($this->key));
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->uris = array_map(array($this, 'extract'), explode(', ', $key));
|
||||
}
|
||||
// Extract each URI from the route key. Since the route key has the request
|
||||
// method, we will extract that from the string. If the URI points to the
|
||||
// root of the application, a single forward slash will be returned.
|
||||
$uris = array_get($action, 'handles', array());
|
||||
|
||||
if ( ! $this->callable($callback))
|
||||
{
|
||||
throw new \InvalidArgumentException('Invalid route defined for URI ['.$this->key.']');
|
||||
}
|
||||
}
|
||||
$this->uris = array_map(array($this, 'extract'), $uris);
|
||||
|
||||
/**
|
||||
* Determine if the given route callback is callable.
|
||||
*
|
||||
* Route callbacks must be either a Closure, array, or string.
|
||||
*
|
||||
* @param mixed $callback
|
||||
* @return bool
|
||||
*/
|
||||
protected function callable($callback)
|
||||
{
|
||||
return $callback instanceof Closure or is_array($callback) or is_string($callback);
|
||||
// Determine the bundle in which the route was registered. We will know
|
||||
// the bundle by the first segment of the route's URI. We need to know
|
||||
// the bundle so we know if we need to run a bundle's global filters
|
||||
// when executing the route.
|
||||
$this->bundle = Bundle::resolve(head(explode('/', $this->uris[0])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the URI from a given route destination.
|
||||
*
|
||||
* If the request is to the root of the application, a single slash
|
||||
* will be returned, otherwise the leading slash will be removed.
|
||||
* If the request is to the application root, a slash is returned.
|
||||
*
|
||||
* @param string $segment
|
||||
* @return string
|
||||
*/
|
||||
protected function extract($segment)
|
||||
protected static function extract($segment)
|
||||
{
|
||||
$segment = substr($segment, strpos($segment, ' ') + 1);
|
||||
$uri = substr($segment, strpos($segment, ' ') + 1);
|
||||
|
||||
return ($segment !== '/') ? trim($segment, '/') : $segment;
|
||||
return ($uri !== '/') ? trim($uri, '/') : $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,116 +91,119 @@ class Route {
|
||||
*/
|
||||
public function call()
|
||||
{
|
||||
// Since "before" filters can halt the request cycle, we will return
|
||||
// any response from the before filters. Allowing filters to halt the
|
||||
// request cycle makes tasks like authorization convenient.
|
||||
//
|
||||
// The route is responsible for running the global filters, and any
|
||||
// filters defined on the route itself. Since all incoming requests
|
||||
// come through a route (either defined or ad-hoc), it makes sense
|
||||
// to let the route handle the global filters. If the route uses
|
||||
// a controller, the controller will only call its own filters.
|
||||
$before = array_merge(array('before'), $this->filters('before'));
|
||||
// to let the route handle the global filters.
|
||||
$response = Filter::run($this->filters('before'), array(), true);
|
||||
|
||||
$response = Filter::run($before, array(), true);
|
||||
|
||||
if (is_null($response) and ! is_null($response = $this->response()))
|
||||
if (is_null($response))
|
||||
{
|
||||
if ($response instanceof Delegate)
|
||||
{
|
||||
$response = Controller::call($response->destination, $this->parameters);
|
||||
}
|
||||
$response = $this->response();
|
||||
}
|
||||
|
||||
if ( ! $response instanceof Response)
|
||||
{
|
||||
$response = new Response($response);
|
||||
}
|
||||
$response = Response::prepare($response);
|
||||
|
||||
// Stringify the response. We need to force the response to be
|
||||
// stringed before closing the session, since the developer may
|
||||
// be using the session within their views, so we cannot age
|
||||
// the session data until the view is rendered.
|
||||
$response->content = $response->render();
|
||||
|
||||
$filters = array_merge($this->filters('after'), array('after'));
|
||||
|
||||
Filter::run($filters, array($response));
|
||||
Filter::run($this->filters('after'), array($response));
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the closure defined for the route, or get the route delegator.
|
||||
* Execute the route action and return the response.
|
||||
*
|
||||
* Note that this method differs from the "call" method in that it does
|
||||
* not resolve the controller or prepare the response. Delegating to
|
||||
* controller's is handled by the "call" method.
|
||||
* Unlike the "call" method, none of the attached filters will be run.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function response()
|
||||
public function response()
|
||||
{
|
||||
// If the route callback is an instance of a Closure, we can call the
|
||||
// route function directly. There are no before or after filters to
|
||||
// parse out of the route.
|
||||
if ($this->callback instanceof Closure)
|
||||
// If the action is a string, it is simply pointing the route to a
|
||||
// controller action, and we can just call the action and return
|
||||
// its response. This is the most basic form of route, and is
|
||||
// the simplest to handle.
|
||||
if ( ! is_null($delegate = $this->delegate()))
|
||||
{
|
||||
return call_user_func_array($this->callback, $this->parameters);
|
||||
return Controller::call($delegate, $this->parameters);
|
||||
}
|
||||
// If the route is an array, we will return the first value with a
|
||||
// key of "uses", or the first instance of a Closure. If the value
|
||||
// is a string, the route is delegating the responsibility for
|
||||
// for handling the request to a controller.
|
||||
elseif (is_array($this->callback))
|
||||
{
|
||||
$callback = Arr::first($this->callback, function($key, $value)
|
||||
{
|
||||
return $key == 'uses' or $value instanceof Closure;
|
||||
});
|
||||
|
||||
if ($callback instanceof Closure)
|
||||
{
|
||||
return call_user_func_array($callback, $this->parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Delegate($callback);
|
||||
}
|
||||
}
|
||||
elseif (is_string($this->callback))
|
||||
// If the route does not have a delegate, it should either be a
|
||||
// Closure instance or have a Closure in its action array, so
|
||||
// we will attempt to get the Closure and call it.
|
||||
elseif ( ! is_null($handler = $this->handler()))
|
||||
{
|
||||
return new Delegate($this->callback);
|
||||
return call_user_func_array($handler, $this->parameters);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of filter names defined for the route.
|
||||
* Get the filters that are attached to the route for a given event.
|
||||
*
|
||||
* @param string $name
|
||||
* If the route belongs to a bundle, the bundle's global filters are returned too.
|
||||
*
|
||||
* @param string $filter
|
||||
* @return array
|
||||
*/
|
||||
public function filters($name)
|
||||
protected function filters($event)
|
||||
{
|
||||
if (is_array($this->callback) and isset($this->callback[$name]))
|
||||
{
|
||||
$filters = $this->callback[$name];
|
||||
// Add the global filters to the array. We will also attempt to add
|
||||
// the bundle's global filter as well. However, we'll need to keep
|
||||
// the array unique since the default bundle's global filter will
|
||||
// be the same as the application's global filter.
|
||||
$filters = array_unique(array($event, Bundle::prefix($this->bundle).$event));
|
||||
|
||||
return (is_string($filters)) ? explode('|', $filters) : (array) $filters;
|
||||
// Next wee will check to see if there are any filters attached
|
||||
// for the given event. If there are, we'll merge them in with
|
||||
// the global filters for the application event.
|
||||
if (isset($this->action[$event]))
|
||||
{
|
||||
$filters = array_merge($filters, Filter::parse($this->action[$event]));
|
||||
}
|
||||
|
||||
return array();
|
||||
return array(new Filter_Collection($filters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the controller action delegate assigned to the route.
|
||||
*
|
||||
* If no delegate is assigned, null will be returned by the method.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function delegate()
|
||||
{
|
||||
return array_get($this->action, 'uses');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the anonymous function assigned to handle the route.
|
||||
*
|
||||
* If no anonymous function is assigned, null will be returned by the method.
|
||||
*
|
||||
* @return Closure
|
||||
*/
|
||||
protected function handler()
|
||||
{
|
||||
return array_first($this->action, function($key, $value)
|
||||
{
|
||||
return $value instanceof Closure;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the route has a given name.
|
||||
*
|
||||
* <code>
|
||||
* // Determine if the route is the "login" route
|
||||
* $login = Request::route()->is('login');
|
||||
* </code>
|
||||
*
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function is($name)
|
||||
{
|
||||
return is_array($this->callback) and Arr::get($this->callback, 'name') === $name;
|
||||
return is_array($this->action) and array_get($this->action, 'name') === $name;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -223,20 +214,12 @@ class Route {
|
||||
*/
|
||||
public function handles($uri)
|
||||
{
|
||||
return in_array($uri, $this->uris);
|
||||
}
|
||||
$pattern = '#'.str_replace('*', '(.*)', $uri).'#';
|
||||
|
||||
/**
|
||||
* Magic Method to handle dynamic method calls to determine the name of the route.
|
||||
*/
|
||||
public function __call($method, $parameters)
|
||||
{
|
||||
if (strpos($method, 'is_') === 0)
|
||||
return ! is_null(array_first($this->uris, function($key, $uri) use ($pattern)
|
||||
{
|
||||
return $this->is(substr($method, 3));
|
||||
}
|
||||
|
||||
throw new \BadMethodCallException("Call to undefined method [$method] on Route class.");
|
||||
return preg_match($pattern, $uri);
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,56 +1,27 @@
|
||||
<?php namespace Laravel\Routing; use Laravel\Request;
|
||||
|
||||
class Delegate {
|
||||
|
||||
/**
|
||||
* The destination of the route delegate.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $destination;
|
||||
|
||||
/**
|
||||
* Create a new route delegate instance.
|
||||
*
|
||||
* @param string $destination
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($destination)
|
||||
{
|
||||
$this->destination = $destination;
|
||||
}
|
||||
|
||||
}
|
||||
<?php namespace Laravel\Routing; use Closure, Laravel\Bundle;
|
||||
|
||||
class Router {
|
||||
|
||||
/**
|
||||
* The route loader instance.
|
||||
*
|
||||
* @var Loader
|
||||
*/
|
||||
public $loader;
|
||||
|
||||
/**
|
||||
* The named routes that have been found so far.
|
||||
* All of the routes that have been registered.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $names = array();
|
||||
public static $routes = array();
|
||||
|
||||
/**
|
||||
* The path the application controllers.
|
||||
* All of the route names that have been matched with URIs.
|
||||
*
|
||||
* @var string
|
||||
* @var array
|
||||
*/
|
||||
protected $controllers;
|
||||
public static $names = array();
|
||||
|
||||
/**
|
||||
* The wildcard patterns supported by the router.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $patterns = array(
|
||||
public static $patterns = array(
|
||||
'(:num)' => '([0-9]+)',
|
||||
'(:any)' => '([a-zA-Z0-9\.\-_]+)',
|
||||
);
|
||||
@@ -60,44 +31,91 @@ class Router {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $optional = array(
|
||||
public static $optional = array(
|
||||
'/(:num?)' => '(?:/([0-9]+)',
|
||||
'/(:any?)' => '(?:/([a-zA-Z0-9\.\-_]+)',
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a new router for a request method and URI.
|
||||
* Register a route with the router.
|
||||
*
|
||||
* @param Loader $loader
|
||||
* @param string $controllers
|
||||
* <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 function __construct(Loader $loader, $controllers)
|
||||
public static function register($route, $action)
|
||||
{
|
||||
$this->loader = $loader;
|
||||
$this->controllers = $controllers;
|
||||
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.
|
||||
//
|
||||
// Note that all route actions are converted to arrays. This just
|
||||
// gives us a convenient and consistent way of accessing it since
|
||||
// we can always make an assumption that the action is an array,
|
||||
// and it lets us store the URIs on the action for each route.
|
||||
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
|
||||
{
|
||||
// PHP 5.3.2 has a bug that causes closures cast as arrays
|
||||
// to yield an empty array. We will work around this by
|
||||
// manually adding the Closure instance to a new array.
|
||||
if ($action instanceof Closure) $action = array($action);
|
||||
|
||||
static::$routes[$uri] = (array) $action;
|
||||
}
|
||||
|
||||
static::$routes[$uri]['handles'] = (array) $route;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a route by name.
|
||||
*
|
||||
* The returned array will be identical the array defined in the routes.php file.
|
||||
*
|
||||
* @param string $name
|
||||
* @return array
|
||||
*/
|
||||
public function find($name)
|
||||
public static function find($name)
|
||||
{
|
||||
if (array_key_exists($name, $this->names)) return $this->names[$name];
|
||||
if (isset(static::$names[$name])) return static::$names[$name];
|
||||
|
||||
// To find a named route, we need to 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 ($this->loader->everything() as $key => $value)
|
||||
// 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)
|
||||
{
|
||||
if (is_array($value) and isset($value['name']) and $value['name'] === $name)
|
||||
foreach (Bundle::all() as $bundle)
|
||||
{
|
||||
return $this->names[$name] = array($key => $value);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -109,59 +127,55 @@ class Router {
|
||||
* @param string $uri
|
||||
* @return Route
|
||||
*/
|
||||
public function route($method, $uri)
|
||||
public static function route($method, $uri)
|
||||
{
|
||||
$routes = $this->loader->load($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 into
|
||||
// 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 (isset($routes[$destination]))
|
||||
if (array_key_exists($destination, static::$routes))
|
||||
{
|
||||
return new Route($destination, $routes[$destination], array());
|
||||
return new Route($destination, static::$routes[$destination], array());
|
||||
}
|
||||
|
||||
// If no literal route match was found, we will iterate through all
|
||||
// of the routes and check each of them one at a time, translating
|
||||
// any wildcards in the route into actual regular expressions.
|
||||
foreach ($routes as $keys => $callback)
|
||||
// If we can't find a literal match, we'll iterate through all of
|
||||
// the registered routes attempting to find a matching route that
|
||||
// uses wildcards or regular expressions.
|
||||
if ( ! is_null($route = static::search($destination)))
|
||||
{
|
||||
// Only check the routes that couldn't be matched literally...
|
||||
if (strpos($keys, '(') !== false or strpos($keys, ',') !== false)
|
||||
{
|
||||
if ( ! is_null($route = $this->match($destination, $keys, $callback)))
|
||||
{
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
|
||||
return $this->controller($method, $uri, $destination);
|
||||
// If there are no literal matches and no routes that match the
|
||||
// request, we'll use convention to search for a controller to
|
||||
// handle the request. If no controller can be found, the 404
|
||||
// error response will be returned by the application.
|
||||
$segments = array_diff(explode('/', trim($uri, '/')), array(''));
|
||||
|
||||
return static::controller(DEFAULT_BUNDLE, $method, $destination, $segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to match a given route destination to a given route.
|
||||
*
|
||||
* The destination's methods and URIs will be compared against the route's.
|
||||
* If there is a match, the Route instance will be returned, otherwise null
|
||||
* will be returned by the method.
|
||||
* Attempt to match a destination to one of the registered routes.
|
||||
*
|
||||
* @param string $destination
|
||||
* @param array $keys
|
||||
* @param mixed $callback
|
||||
* @return mixed
|
||||
* @return Route
|
||||
*/
|
||||
protected function match($destination, $keys, $callback)
|
||||
protected static function search($destination)
|
||||
{
|
||||
foreach (explode(', ', $keys) as $key)
|
||||
foreach (static::$routes as $route => $action)
|
||||
{
|
||||
if (preg_match('#^'.$this->wildcards($key).'$#', $destination, $parameters))
|
||||
// Since routes that don't use wildcards or regular expressions
|
||||
// should have been caught by the literal route check, we will
|
||||
// only check routes that have a parentheses, indicating that
|
||||
// there are wildcards or regular expressions.
|
||||
if (strpos($route, '(') !== false)
|
||||
{
|
||||
array_shift($parameters);
|
||||
|
||||
return new Route($keys, $callback, $parameters);
|
||||
if (preg_match('#^'.static::wildcards($route).'$#', $destination, $parameters))
|
||||
{
|
||||
return new Route($route, $action, array_slice($parameters, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,32 +183,47 @@ class Router {
|
||||
/**
|
||||
* Attempt to find a controller for the incoming request.
|
||||
*
|
||||
* @param string $bundle
|
||||
* @param string $method
|
||||
* @param string $uri
|
||||
* @param string $destination
|
||||
* @param array $segments
|
||||
* @return Route
|
||||
*/
|
||||
protected function controller($method, $uri, $destination)
|
||||
protected static function controller($bundle, $method, $destination, $segments)
|
||||
{
|
||||
// If the request is to the root of the application, an ad-hoc route
|
||||
// will be generated to the home controller's "index" method, making
|
||||
// it the default controller method.
|
||||
if ($uri === '/') return new Route($method.' /', 'home@index');
|
||||
|
||||
$segments = explode('/', trim($uri, '/'));
|
||||
|
||||
// If there are more than 20 request segments, we will halt the request
|
||||
// and throw an exception. This is primarily to protect against DDoS
|
||||
// attacks which could overwhelm the server by feeding it too many
|
||||
// segments in the URI, causing the loops in this class to bog.
|
||||
if (count($segments) > 20)
|
||||
// If there are no more segments in the URI, we will just create a route
|
||||
// for the default controller of the bundle, which is "home". We'll also
|
||||
// use the default method, which is "index".
|
||||
if (count($segments) == 0)
|
||||
{
|
||||
throw new \Exception("Invalid request. There are more than 20 URI segments.");
|
||||
$uri = ($bundle == DEFAULT_BUNDLE) ? '/' : "/{$bundle}";
|
||||
|
||||
$action = array('uses' => Bundle::prefix($bundle).'home@index');
|
||||
|
||||
return new Route($method.' '.$uri, $action);
|
||||
}
|
||||
|
||||
if ( ! is_null($key = $this->controller_key($segments)))
|
||||
$directory = Bundle::path($bundle).'controllers/';
|
||||
|
||||
// We need to determine in which directory to look for the controllers.
|
||||
// If the first segment of the request corresponds to a bundle that
|
||||
// is installed for the application, we will use that bundle's
|
||||
// controller path, otherwise we'll use the application's.
|
||||
if (Bundle::routable($segments[0]))
|
||||
{
|
||||
$bundle = $segments[0];
|
||||
|
||||
// We shift the bundle name off of the URI segments because it will not
|
||||
// be used to find a controller within the bundle. If we were to leave
|
||||
// it in the segments, every bundle controller would need to be nested
|
||||
// within a sub-directory matching the bundle name.
|
||||
array_shift($segments);
|
||||
|
||||
return static::controller($bundle, $method, $destination, $segments);
|
||||
}
|
||||
|
||||
if ( ! is_null($key = static::controller_key($segments, $directory)))
|
||||
{
|
||||
// Extract the various parts of the controller call from the URI.
|
||||
// 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
|
||||
@@ -206,31 +235,37 @@ class Router {
|
||||
|
||||
$method = (count($segments) > 0) ? array_shift($segments) : 'index';
|
||||
|
||||
return new Route($destination, $controller.'@'.$method, $segments);
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a controller that can handle the current request.
|
||||
* Get the URI index for the controller that should handle the request.
|
||||
*
|
||||
* If a controller is found, the array key for the controller name in the URI
|
||||
* segments will be returned by the method, otherwise NULL will be returned.
|
||||
* The deepest possible controller will be considered the controller that
|
||||
* should handle the request.
|
||||
*
|
||||
* @param array $segments
|
||||
* @param string $directory
|
||||
* @param array $segments
|
||||
* @return int
|
||||
*/
|
||||
protected function controller_key($segments)
|
||||
protected static function controller_key($segments, $directory)
|
||||
{
|
||||
// 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 controller matched by the URI.
|
||||
foreach (array_reverse($segments, true) as $key => $value)
|
||||
// should be the deepest possible controller matched by the URI.
|
||||
$reverse = array_reverse($segments, true);
|
||||
|
||||
foreach ($reverse as $key => $value)
|
||||
{
|
||||
$controller = implode('/', array_slice($segments, 0, $key + 1)).EXT;
|
||||
|
||||
if (file_exists($path = $this->controllers.$controller))
|
||||
if (file_exists($directory.$controller))
|
||||
{
|
||||
return $key + 1;
|
||||
}
|
||||
@@ -243,16 +278,16 @@ class Router {
|
||||
* @param string $key
|
||||
* @return string
|
||||
*/
|
||||
protected function wildcards($key)
|
||||
protected static function wildcards($key)
|
||||
{
|
||||
// For optional parameters, first translate the wildcards to their
|
||||
// regex equivalent, sans the ")?" ending. We will add the endings
|
||||
// regex equivalent, sans the ")?" ending. We'll add the endings
|
||||
// back on after we know how many replacements we made.
|
||||
$key = str_replace(array_keys($this->optional), array_values($this->optional), $key, $count);
|
||||
$key = str_replace(array_keys(static::$optional), array_values(static::$optional), $key, $count);
|
||||
|
||||
$key .= ($count > 0) ? str_repeat(')?', $count) : '';
|
||||
|
||||
return str_replace(array_keys($this->patterns), array_values($this->patterns), $key);
|
||||
return str_replace(array_keys(static::$patterns), array_values(static::$patterns), $key);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user