Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
68 contributors

Users who have contributed to this file

@taylorotwell @GrahamCampbell @lucasmichot @themsaid @Glutnix @JosephSilber @ralphschindler @KennedyTedesco @jasonlewis @crynobone @findstar @snjypl @meowcakes @patrickbrouwers @ThorbenG @gayanhewa @franzliedke @dannykopping @barryvdh @AdenFraser @tillkruss @phannaly @helmut @m1guelpf @sisve @d3jn
executable file 936 lines (810 sloc) 20.2 KB
<?php
namespace Illuminate\Routing;
use Closure;
use Illuminate\Container\Container;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\Request;
use Illuminate\Routing\Contracts\ControllerDispatcher as ControllerDispatcherContract;
use Illuminate\Routing\Matching\HostValidator;
use Illuminate\Routing\Matching\MethodValidator;
use Illuminate\Routing\Matching\SchemeValidator;
use Illuminate\Routing\Matching\UriValidator;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Support\Traits\Macroable;
use LogicException;
use ReflectionFunction;
class Route
{
use Macroable, RouteDependencyResolverTrait;
/**
* The URI pattern the route responds to.
*
* @var string
*/
public $uri;
/**
* The HTTP methods the route responds to.
*
* @var array
*/
public $methods;
/**
* The route action array.
*
* @var array
*/
public $action;
/**
* Indicates whether the route is a fallback route.
*
* @var bool
*/
public $isFallback = false;
/**
* The controller instance.
*
* @var mixed
*/
public $controller;
/**
* The default values for the route.
*
* @var array
*/
public $defaults = [];
/**
* The regular expression requirements.
*
* @var array
*/
public $wheres = [];
/**
* The array of matched parameters.
*
* @var array
*/
public $parameters;
/**
* The parameter names for the route.
*
* @var array|null
*/
public $parameterNames;
/**
* The array of the matched parameters' original values.
*
* @var array
*/
protected $originalParameters;
/**
* The computed gathered middleware.
*
* @var array|null
*/
public $computedMiddleware;
/**
* The compiled version of the route.
*
* @var \Symfony\Component\Routing\CompiledRoute
*/
public $compiled;
/**
* The router instance used by the route.
*
* @var \Illuminate\Routing\Router
*/
protected $router;
/**
* The container instance used by the route.
*
* @var \Illuminate\Container\Container
*/
protected $container;
/**
* The validators used by the routes.
*
* @var array
*/
public static $validators;
/**
* Create a new Route instance.
*
* @param array|string $methods
* @param string $uri
* @param \Closure|array $action
* @return void
*/
public function __construct($methods, $uri, $action)
{
$this->uri = $uri;
$this->methods = (array) $methods;
$this->action = $this->parseAction($action);
if (in_array('GET', $this->methods) && ! in_array('HEAD', $this->methods)) {
$this->methods[] = 'HEAD';
}
if (isset($this->action['prefix'])) {
$this->prefix($this->action['prefix']);
}
}
/**
* Parse the route action into a standard array.
*
* @param callable|array|null $action
* @return array
*
* @throws \UnexpectedValueException
*/
protected function parseAction($action)
{
return RouteAction::parse($this->uri, $action);
}
/**
* Run the route action and return the response.
*
* @return mixed
*/
public function run()
{
$this->container = $this->container ?: new Container;
try {
if ($this->isControllerAction()) {
return $this->runController();
}
return $this->runCallable();
} catch (HttpResponseException $e) {
return $e->getResponse();
}
}
/**
* Checks whether the route's action is a controller.
*
* @return bool
*/
protected function isControllerAction()
{
return is_string($this->action['uses']);
}
/**
* Run the route action and return the response.
*
* @return mixed
*/
protected function runCallable()
{
$callable = $this->action['uses'];
return $callable(...array_values($this->resolveMethodDependencies(
$this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
)));
}
/**
* Run the route action and return the response.
*
* @return mixed
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function runController()
{
return $this->controllerDispatcher()->dispatch(
$this, $this->getController(), $this->getControllerMethod()
);
}
/**
* Get the controller instance for the route.
*
* @return mixed
*/
public function getController()
{
if (! $this->controller) {
$class = $this->parseControllerCallback()[0];
$this->controller = $this->container->make(ltrim($class, '\\'));
}
return $this->controller;
}
/**
* Get the controller method used for the route.
*
* @return string
*/
protected function getControllerMethod()
{
return $this->parseControllerCallback()[1];
}
/**
* Parse the controller.
*
* @return array
*/
protected function parseControllerCallback()
{
return Str::parseCallback($this->action['uses']);
}
/**
* Determine if the route matches given request.
*
* @param \Illuminate\Http\Request $request
* @param bool $includingMethod
* @return bool
*/
public function matches(Request $request, $includingMethod = true)
{
$this->compileRoute();
foreach ($this->getValidators() as $validator) {
if (! $includingMethod && $validator instanceof MethodValidator) {
continue;
}
if (! $validator->matches($this, $request)) {
return false;
}
}
return true;
}
/**
* Compile the route into a Symfony CompiledRoute instance.
*
* @return \Symfony\Component\Routing\CompiledRoute
*/
protected function compileRoute()
{
if (! $this->compiled) {
$this->compiled = (new RouteCompiler($this))->compile();
}
return $this->compiled;
}
/**
* Bind the route to a given request for execution.
*
* @param \Illuminate\Http\Request $request
* @return $this
*/
public function bind(Request $request)
{
$this->compileRoute();
$this->parameters = (new RouteParameterBinder($this))
->parameters($request);
$this->originalParameters = $this->parameters;
return $this;
}
/**
* Determine if the route has parameters.
*
* @return bool
*/
public function hasParameters()
{
return isset($this->parameters);
}
/**
* Determine a given parameter exists from the route.
*
* @param string $name
* @return bool
*/
public function hasParameter($name)
{
if ($this->hasParameters()) {
return array_key_exists($name, $this->parameters());
}
return false;
}
/**
* Get a given parameter from the route.
*
* @param string $name
* @param mixed $default
* @return string|object
*/
public function parameter($name, $default = null)
{
return Arr::get($this->parameters(), $name, $default);
}
/**
* Get original value of a given parameter from the route.
*
* @param string $name
* @param mixed $default
* @return string
*/
public function originalParameter($name, $default = null)
{
return Arr::get($this->originalParameters(), $name, $default);
}
/**
* Set a parameter to the given value.
*
* @param string $name
* @param mixed $value
* @return void
*/
public function setParameter($name, $value)
{
$this->parameters();
$this->parameters[$name] = $value;
}
/**
* Unset a parameter on the route if it is set.
*
* @param string $name
* @return void
*/
public function forgetParameter($name)
{
$this->parameters();
unset($this->parameters[$name]);
}
/**
* Get the key / value list of parameters for the route.
*
* @return array
*
* @throws \LogicException
*/
public function parameters()
{
if (isset($this->parameters)) {
return $this->parameters;
}
throw new LogicException('Route is not bound.');
}
/**
* Get the key / value list of original parameters for the route.
*
* @return array
*
* @throws \LogicException
*/
public function originalParameters()
{
if (isset($this->originalParameters)) {
return $this->originalParameters;
}
throw new LogicException('Route is not bound.');
}
/**
* Get the key / value list of parameters without null values.
*
* @return array
*/
public function parametersWithoutNulls()
{
return array_filter($this->parameters(), function ($p) {
return ! is_null($p);
});
}
/**
* Get all of the parameter names for the route.
*
* @return array
*/
public function parameterNames()
{
if (isset($this->parameterNames)) {
return $this->parameterNames;
}
return $this->parameterNames = $this->compileParameterNames();
}
/**
* Get the parameter names for the route.
*
* @return array
*/
protected function compileParameterNames()
{
preg_match_all('/\{(.*?)\}/', $this->getDomain().$this->uri, $matches);
return array_map(function ($m) {
return trim($m, '?');
}, $matches[1]);
}
/**
* Get the parameters that are listed in the route / controller signature.
*
* @param string|null $subClass
* @return array
*/
public function signatureParameters($subClass = null)
{
return RouteSignatureParameters::fromAction($this->action, $subClass);
}
/**
* Set a default value for the route.
*
* @param string $key
* @param mixed $value
* @return $this
*/
public function defaults($key, $value)
{
$this->defaults[$key] = $value;
return $this;
}
/**
* Set a regular expression requirement on the route.
*
* @param array|string $name
* @param string|null $expression
* @return $this
*/
public function where($name, $expression = null)
{
foreach ($this->parseWhere($name, $expression) as $name => $expression) {
$this->wheres[$name] = $expression;
}
return $this;
}
/**
* Parse arguments to the where method into an array.
*
* @param array|string $name
* @param string $expression
* @return array
*/
protected function parseWhere($name, $expression)
{
return is_array($name) ? $name : [$name => $expression];
}
/**
* Set a list of regular expression requirements on the route.
*
* @param array $wheres
* @return $this
*/
protected function whereArray(array $wheres)
{
foreach ($wheres as $name => $expression) {
$this->where($name, $expression);
}
return $this;
}
/**
* Mark this route as a fallback route.
*
* @return $this
*/
public function fallback()
{
$this->isFallback = true;
return $this;
}
/**
* Get the HTTP verbs the route responds to.
*
* @return array
*/
public function methods()
{
return $this->methods;
}
/**
* Determine if the route only responds to HTTP requests.
*
* @return bool
*/
public function httpOnly()
{
return in_array('http', $this->action, true);
}
/**
* Determine if the route only responds to HTTPS requests.
*
* @return bool
*/
public function httpsOnly()
{
return $this->secure();
}
/**
* Determine if the route only responds to HTTPS requests.
*
* @return bool
*/
public function secure()
{
return in_array('https', $this->action, true);
}
/**
* Get or set the domain for the route.
*
* @param string|null $domain
* @return $this|string|null
*/
public function domain($domain = null)
{
if (is_null($domain)) {
return $this->getDomain();
}
$this->action['domain'] = $domain;
return $this;
}
/**
* Get the domain defined for the route.
*
* @return string|null
*/
public function getDomain()
{
return isset($this->action['domain'])
? str_replace(['http://', 'https://'], '', $this->action['domain']) : null;
}
/**
* Get the prefix of the route instance.
*
* @return string
*/
public function getPrefix()
{
return $this->action['prefix'] ?? null;
}
/**
* Add a prefix to the route URI.
*
* @param string $prefix
* @return $this
*/
public function prefix($prefix)
{
$uri = rtrim($prefix, '/').'/'.ltrim($this->uri, '/');
$this->uri = trim($uri, '/');
return $this;
}
/**
* Get the URI associated with the route.
*
* @return string
*/
public function uri()
{
return $this->uri;
}
/**
* Set the URI that the route responds to.
*
* @param string $uri
* @return $this
*/
public function setUri($uri)
{
$this->uri = $uri;
return $this;
}
/**
* Get the name of the route instance.
*
* @return string
*/
public function getName()
{
return $this->action['as'] ?? null;
}
/**
* Add or change the route name.
*
* @param string $name
* @return $this
*/
public function name($name)
{
$this->action['as'] = isset($this->action['as']) ? $this->action['as'].$name : $name;
return $this;
}
/**
* Determine whether the route's name matches the given patterns.
*
* @param mixed ...$patterns
* @return bool
*/
public function named(...$patterns)
{
if (is_null($routeName = $this->getName())) {
return false;
}
foreach ($patterns as $pattern) {
if (Str::is($pattern, $routeName)) {
return true;
}
}
return false;
}
/**
* Set the handler for the route.
*
* @param \Closure|string $action
* @return $this
*/
public function uses($action)
{
$action = is_string($action) ? $this->addGroupNamespaceToStringUses($action) : $action;
return $this->setAction(array_merge($this->action, $this->parseAction([
'uses' => $action,
'controller' => $action,
])));
}
/**
* Parse a string based action for the "uses" fluent method.
*
* @param string $action
* @return string
*/
protected function addGroupNamespaceToStringUses($action)
{
$groupStack = last($this->router->getGroupStack());
if (isset($groupStack['namespace']) && strpos($action, '\\') !== 0) {
return $groupStack['namespace'].'\\'.$action;
}
return $action;
}
/**
* Get the action name for the route.
*
* @return string
*/
public function getActionName()
{
return $this->action['controller'] ?? 'Closure';
}
/**
* Get the method name of the route action.
*
* @return string
*/
public function getActionMethod()
{
return Arr::last(explode('@', $this->getActionName()));
}
/**
* Get the action array or one of its properties for the route.
*
* @param string|null $key
* @return mixed
*/
public function getAction($key = null)
{
return Arr::get($this->action, $key);
}
/**
* Set the action array for the route.
*
* @param array $action
* @return $this
*/
public function setAction(array $action)
{
$this->action = $action;
return $this;
}
/**
* Get all middleware, including the ones from the controller.
*
* @return array
*/
public function gatherMiddleware()
{
if (! is_null($this->computedMiddleware)) {
return $this->computedMiddleware;
}
$this->computedMiddleware = [];
return $this->computedMiddleware = array_unique(array_merge(
$this->middleware(), $this->controllerMiddleware()
), SORT_REGULAR);
}
/**
* Get or set the middlewares attached to the route.
*
* @param array|string|null $middleware
* @return $this|array
*/
public function middleware($middleware = null)
{
if (is_null($middleware)) {
return (array) ($this->action['middleware'] ?? []);
}
if (is_string($middleware)) {
$middleware = func_get_args();
}
$this->action['middleware'] = array_merge(
(array) ($this->action['middleware'] ?? []), $middleware
);
return $this;
}
/**
* Get the middleware for the route's controller.
*
* @return array
*/
public function controllerMiddleware()
{
if (! $this->isControllerAction()) {
return [];
}
return $this->controllerDispatcher()->getMiddleware(
$this->getController(), $this->getControllerMethod()
);
}
/**
* Get the dispatcher for the route's controller.
*
* @return \Illuminate\Routing\Contracts\ControllerDispatcher
*/
public function controllerDispatcher()
{
if ($this->container->bound(ControllerDispatcherContract::class)) {
return $this->container->make(ControllerDispatcherContract::class);
}
return new ControllerDispatcher($this->container);
}
/**
* Get the route validators for the instance.
*
* @return array
*/
public static function getValidators()
{
if (isset(static::$validators)) {
return static::$validators;
}
// To match the route, we will use a chain of responsibility pattern with the
// validator implementations. We will spin through each one making sure it
// passes and then we will know if the route as a whole matches request.
return static::$validators = [
new UriValidator, new MethodValidator,
new SchemeValidator, new HostValidator,
];
}
/**
* Get the compiled version of the route.
*
* @return \Symfony\Component\Routing\CompiledRoute
*/
public function getCompiled()
{
return $this->compiled;
}
/**
* Set the router instance on the route.
*
* @param \Illuminate\Routing\Router $router
* @return $this
*/
public function setRouter(Router $router)
{
$this->router = $router;
return $this;
}
/**
* Set the container instance on the route.
*
* @param \Illuminate\Container\Container $container
* @return $this
*/
public function setContainer(Container $container)
{
$this->container = $container;
return $this;
}
/**
* Prepare the route instance for serialization.
*
* @return void
*
* @throws \LogicException
*/
public function prepareForSerialization()
{
if ($this->action['uses'] instanceof Closure) {
throw new LogicException("Unable to prepare route [{$this->uri}] for serialization. Uses Closure.");
}
$this->compileRoute();
unset($this->router, $this->container);
}
/**
* Dynamically access route parameters.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
return $this->parameter($key);
}
}
You can’t perform that action at this time.