* // Register a closure as a filter
* Filter::register('before', function() {});
*
* // Register a class callback as a filter
* Filter::register('before', array('Class', 'method'));
*
*
* @param string $name
* @param mixed $callback
* @return void
*/
public static function register($name, $callback)
{
if (isset(static::$aliases[$name])) $name = static::$aliases[$name];
// If the filter starts with "pattern: ", the filter is being setup to match on
// all requests that match a given pattern. This is nice for defining filters
// that handle all URIs beginning with "admin" for example.
if (starts_with($name, 'pattern: '))
{
foreach (explode(', ', substr($name, 9)) as $pattern)
{
static::$patterns[$pattern] = $callback;
}
}
else
{
static::$filters[$name] = $callback;
}
}
/**
* Alias a filter so it can be used by another name.
*
* This is convenient for shortening filters that are registered by bundles.
*
* @param string $filter
* @param string $alias
* @return void
*/
public static function alias($filter, $alias)
{
static::$aliases[$alias] = $filter;
}
/**
* Parse a filter definition into an array of filters.
*
* @param string|array $filters
* @return array
*/
public static function parse($filters)
{
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 by lazy-loading.
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 filters contained by the collection.
*
* @var string|array
*/
public $filters = array();
/**
* The parameters specified for the filter.
*
* @var mixed
*/
public $parameters;
/**
* The included controller methods.
*
* @var array
*/
public $only = array();
/**
* The excluded controller methods.
*
* @var array
*/
public $except = array();
/**
* The HTTP methods for which the filter applies.
*
* @var array
*/
public $methods = array();
/**
* Create a new filter collection instance.
*
* @param string|array $filters
* @param mixed $parameters
* @return void
*/
public function __construct($filters, $parameters = null)
{
$this->parameters = $parameters;
$this->filters = Filter::parse($filters);
}
/**
* Parse the filter string, returning the filter name and parameters.
*
* @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
*/
public function applies($method)
{
if (count($this->only) > 0 and ! in_array($method, $this->only))
{
return false;
}
if (count($this->except) > 0 and in_array($method, $this->except))
{
return false;
}
$request = strtolower(Request::method());
if (count($this->methods) > 0 and ! in_array($request, $this->methods))
{
return false;
}
return true;
}
/**
* Set the excluded controller methods.
*
*
* // 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(array('index', 'home'));
*
*
* @param array $methods
* @return Filter_Collection
*/
public function except($methods)
{
$this->except = (array) $methods;
return $this;
}
/**
* Set the included controller methods.
*
*
* // 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(array('index', 'home'));
*
*
* @param array $methods
* @return Filter_Collection
*/
public function only($methods)
{
$this->only = (array) $methods;
return $this;
}
/**
* Set the HTTP methods for which the filter applies.
*
*
* // 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(array('post', 'put'));
*
*
* @param array $methods
* @return Filter_Collection
*/
public function on($methods)
{
$this->methods = array_map('strtolower', (array) $methods);
return $this;
}
}