PageRenderTime 28ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/laravel/framework/src/Illuminate/Routing/Route.php

https://gitlab.com/ealexis.t/trends
PHP | 945 lines | 409 code | 127 blank | 409 comment | 30 complexity | f74dba95e27ecc42bf7821ee2e07c227 MD5 | raw file
  1. <?php
  2. namespace Illuminate\Routing;
  3. use Closure;
  4. use LogicException;
  5. use ReflectionMethod;
  6. use ReflectionFunction;
  7. use Illuminate\Support\Arr;
  8. use Illuminate\Support\Str;
  9. use Illuminate\Http\Request;
  10. use UnexpectedValueException;
  11. use Illuminate\Container\Container;
  12. use Illuminate\Routing\Matching\UriValidator;
  13. use Illuminate\Routing\Matching\HostValidator;
  14. use Illuminate\Routing\Matching\MethodValidator;
  15. use Illuminate\Routing\Matching\SchemeValidator;
  16. use Symfony\Component\Routing\Route as SymfonyRoute;
  17. use Illuminate\Http\Exception\HttpResponseException;
  18. class Route
  19. {
  20. use RouteDependencyResolverTrait;
  21. /**
  22. * The URI pattern the route responds to.
  23. *
  24. * @var string
  25. */
  26. protected $uri;
  27. /**
  28. * The HTTP methods the route responds to.
  29. *
  30. * @var array
  31. */
  32. protected $methods;
  33. /**
  34. * The route action array.
  35. *
  36. * @var array
  37. */
  38. protected $action;
  39. /**
  40. * The default values for the route.
  41. *
  42. * @var array
  43. */
  44. protected $defaults = [];
  45. /**
  46. * The regular expression requirements.
  47. *
  48. * @var array
  49. */
  50. protected $wheres = [];
  51. /**
  52. * The array of matched parameters.
  53. *
  54. * @var array
  55. */
  56. protected $parameters;
  57. /**
  58. * The parameter names for the route.
  59. *
  60. * @var array|null
  61. */
  62. protected $parameterNames;
  63. /**
  64. * The compiled version of the route.
  65. *
  66. * @var \Symfony\Component\Routing\CompiledRoute
  67. */
  68. protected $compiled;
  69. /**
  70. * The router instance used by the route.
  71. *
  72. * @var \Illuminate\Routing\Router
  73. */
  74. protected $router;
  75. /**
  76. * The container instance used by the route.
  77. *
  78. * @var \Illuminate\Container\Container
  79. */
  80. protected $container;
  81. /**
  82. * The validators used by the routes.
  83. *
  84. * @var array
  85. */
  86. public static $validators;
  87. /**
  88. * Create a new Route instance.
  89. *
  90. * @param array|string $methods
  91. * @param string $uri
  92. * @param \Closure|array $action
  93. * @return void
  94. */
  95. public function __construct($methods, $uri, $action)
  96. {
  97. $this->uri = $uri;
  98. $this->methods = (array) $methods;
  99. $this->action = $this->parseAction($action);
  100. if (in_array('GET', $this->methods) && ! in_array('HEAD', $this->methods)) {
  101. $this->methods[] = 'HEAD';
  102. }
  103. if (isset($this->action['prefix'])) {
  104. $this->prefix($this->action['prefix']);
  105. }
  106. }
  107. /**
  108. * Run the route action and return the response.
  109. *
  110. * @param \Illuminate\Http\Request $request
  111. * @return mixed
  112. */
  113. public function run(Request $request)
  114. {
  115. $this->container = $this->container ?: new Container;
  116. try {
  117. if (! is_string($this->action['uses'])) {
  118. return $this->runCallable($request);
  119. }
  120. return $this->runController($request);
  121. } catch (HttpResponseException $e) {
  122. return $e->getResponse();
  123. }
  124. }
  125. /**
  126. * Run the route action and return the response.
  127. *
  128. * @param \Illuminate\Http\Request $request
  129. * @return mixed
  130. */
  131. protected function runCallable(Request $request)
  132. {
  133. $parameters = $this->resolveMethodDependencies(
  134. $this->parametersWithoutNulls(), new ReflectionFunction($this->action['uses'])
  135. );
  136. return call_user_func_array($this->action['uses'], $parameters);
  137. }
  138. /**
  139. * Run the route action and return the response.
  140. *
  141. * @param \Illuminate\Http\Request $request
  142. * @return mixed
  143. *
  144. * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
  145. */
  146. protected function runController(Request $request)
  147. {
  148. list($class, $method) = explode('@', $this->action['uses']);
  149. return (new ControllerDispatcher($this->router, $this->container))
  150. ->dispatch($this, $request, $class, $method);
  151. }
  152. /**
  153. * Determine if the route matches given request.
  154. *
  155. * @param \Illuminate\Http\Request $request
  156. * @param bool $includingMethod
  157. * @return bool
  158. */
  159. public function matches(Request $request, $includingMethod = true)
  160. {
  161. $this->compileRoute();
  162. foreach ($this->getValidators() as $validator) {
  163. if (! $includingMethod && $validator instanceof MethodValidator) {
  164. continue;
  165. }
  166. if (! $validator->matches($this, $request)) {
  167. return false;
  168. }
  169. }
  170. return true;
  171. }
  172. /**
  173. * Compile the route into a Symfony CompiledRoute instance.
  174. *
  175. * @return void
  176. */
  177. protected function compileRoute()
  178. {
  179. $optionals = $this->extractOptionalParameters();
  180. $uri = preg_replace('/\{(\w+?)\?\}/', '{$1}', $this->uri);
  181. $this->compiled = (
  182. new SymfonyRoute($uri, $optionals, $this->wheres, [], $this->domain() ?: '')
  183. )->compile();
  184. }
  185. /**
  186. * Get the optional parameters for the route.
  187. *
  188. * @return array
  189. */
  190. protected function extractOptionalParameters()
  191. {
  192. preg_match_all('/\{(\w+?)\?\}/', $this->uri, $matches);
  193. return isset($matches[1]) ? array_fill_keys($matches[1], null) : [];
  194. }
  195. /**
  196. * Get or set the middlewares attached to the route.
  197. *
  198. * @param array|string|null $middleware
  199. * @return $this|array
  200. */
  201. public function middleware($middleware = null)
  202. {
  203. if (is_null($middleware)) {
  204. return (array) Arr::get($this->action, 'middleware', []);
  205. }
  206. if (is_string($middleware)) {
  207. $middleware = [$middleware];
  208. }
  209. $this->action['middleware'] = array_merge(
  210. (array) Arr::get($this->action, 'middleware', []), $middleware
  211. );
  212. return $this;
  213. }
  214. /**
  215. * Get the controller middleware for the route.
  216. *
  217. * @return array
  218. */
  219. protected function controllerMiddleware()
  220. {
  221. list($class, $method) = explode('@', $this->action['uses']);
  222. $controller = $this->container->make($class);
  223. return (new ControllerDispatcher($this->router, $this->container))
  224. ->getMiddleware($controller, $method);
  225. }
  226. /**
  227. * Get the parameters that are listed in the route / controller signature.
  228. *
  229. * @param string|null $subClass
  230. * @return array
  231. */
  232. public function signatureParameters($subClass = null)
  233. {
  234. $action = $this->getAction();
  235. if (is_string($action['uses'])) {
  236. list($class, $method) = explode('@', $action['uses']);
  237. $parameters = (new ReflectionMethod($class, $method))->getParameters();
  238. } else {
  239. $parameters = (new ReflectionFunction($action['uses']))->getParameters();
  240. }
  241. return is_null($subClass) ? $parameters : array_filter($parameters, function ($p) use ($subClass) {
  242. return $p->getClass() && $p->getClass()->isSubclassOf($subClass);
  243. });
  244. }
  245. /**
  246. * Determine if the route has parameters.
  247. *
  248. * @return bool
  249. */
  250. public function hasParameters()
  251. {
  252. return isset($this->parameters);
  253. }
  254. /**
  255. * Determine a given parameter exists from the route.
  256. *
  257. * @param string $name
  258. * @return bool
  259. */
  260. public function hasParameter($name)
  261. {
  262. if (! $this->hasParameters()) {
  263. return false;
  264. }
  265. return array_key_exists($name, $this->parameters());
  266. }
  267. /**
  268. * Get a given parameter from the route.
  269. *
  270. * @param string $name
  271. * @param mixed $default
  272. * @return string|object
  273. */
  274. public function getParameter($name, $default = null)
  275. {
  276. return $this->parameter($name, $default);
  277. }
  278. /**
  279. * Get a given parameter from the route.
  280. *
  281. * @param string $name
  282. * @param mixed $default
  283. * @return string|object
  284. */
  285. public function parameter($name, $default = null)
  286. {
  287. return Arr::get($this->parameters(), $name, $default);
  288. }
  289. /**
  290. * Set a parameter to the given value.
  291. *
  292. * @param string $name
  293. * @param mixed $value
  294. * @return void
  295. */
  296. public function setParameter($name, $value)
  297. {
  298. $this->parameters();
  299. $this->parameters[$name] = $value;
  300. }
  301. /**
  302. * Unset a parameter on the route if it is set.
  303. *
  304. * @param string $name
  305. * @return void
  306. */
  307. public function forgetParameter($name)
  308. {
  309. $this->parameters();
  310. unset($this->parameters[$name]);
  311. }
  312. /**
  313. * Get the key / value list of parameters for the route.
  314. *
  315. * @return array
  316. *
  317. * @throws \LogicException
  318. */
  319. public function parameters()
  320. {
  321. if (isset($this->parameters)) {
  322. return array_map(function ($value) {
  323. return is_string($value) ? rawurldecode($value) : $value;
  324. }, $this->parameters);
  325. }
  326. throw new LogicException('Route is not bound.');
  327. }
  328. /**
  329. * Get the key / value list of parameters without null values.
  330. *
  331. * @return array
  332. */
  333. public function parametersWithoutNulls()
  334. {
  335. return array_filter($this->parameters(), function ($p) {
  336. return ! is_null($p);
  337. });
  338. }
  339. /**
  340. * Get all of the parameter names for the route.
  341. *
  342. * @return array
  343. */
  344. public function parameterNames()
  345. {
  346. if (isset($this->parameterNames)) {
  347. return $this->parameterNames;
  348. }
  349. return $this->parameterNames = $this->compileParameterNames();
  350. }
  351. /**
  352. * Get the parameter names for the route.
  353. *
  354. * @return array
  355. */
  356. protected function compileParameterNames()
  357. {
  358. preg_match_all('/\{(.*?)\}/', $this->domain().$this->uri, $matches);
  359. return array_map(function ($m) {
  360. return trim($m, '?');
  361. }, $matches[1]);
  362. }
  363. /**
  364. * Bind the route to a given request for execution.
  365. *
  366. * @param \Illuminate\Http\Request $request
  367. * @return $this
  368. */
  369. public function bind(Request $request)
  370. {
  371. $this->compileRoute();
  372. $this->bindParameters($request);
  373. return $this;
  374. }
  375. /**
  376. * Extract the parameter list from the request.
  377. *
  378. * @param \Illuminate\Http\Request $request
  379. * @return array
  380. */
  381. public function bindParameters(Request $request)
  382. {
  383. // If the route has a regular expression for the host part of the URI, we will
  384. // compile that and get the parameter matches for this domain. We will then
  385. // merge them into this parameters array so that this array is completed.
  386. $params = $this->matchToKeys(
  387. array_slice($this->bindPathParameters($request), 1)
  388. );
  389. // If the route has a regular expression for the host part of the URI, we will
  390. // compile that and get the parameter matches for this domain. We will then
  391. // merge them into this parameters array so that this array is completed.
  392. if (! is_null($this->compiled->getHostRegex())) {
  393. $params = $this->bindHostParameters(
  394. $request, $params
  395. );
  396. }
  397. return $this->parameters = $this->replaceDefaults($params);
  398. }
  399. /**
  400. * Get the parameter matches for the path portion of the URI.
  401. *
  402. * @param \Illuminate\Http\Request $request
  403. * @return array
  404. */
  405. protected function bindPathParameters(Request $request)
  406. {
  407. preg_match($this->compiled->getRegex(), '/'.$request->decodedPath(), $matches);
  408. return $matches;
  409. }
  410. /**
  411. * Extract the parameter list from the host part of the request.
  412. *
  413. * @param \Illuminate\Http\Request $request
  414. * @param array $parameters
  415. * @return array
  416. */
  417. protected function bindHostParameters(Request $request, $parameters)
  418. {
  419. preg_match($this->compiled->getHostRegex(), $request->getHost(), $matches);
  420. return array_merge($this->matchToKeys(array_slice($matches, 1)), $parameters);
  421. }
  422. /**
  423. * Combine a set of parameter matches with the route's keys.
  424. *
  425. * @param array $matches
  426. * @return array
  427. */
  428. protected function matchToKeys(array $matches)
  429. {
  430. if (empty($parameterNames = $this->parameterNames())) {
  431. return [];
  432. }
  433. $parameters = array_intersect_key($matches, array_flip($parameterNames));
  434. return array_filter($parameters, function ($value) {
  435. return is_string($value) && strlen($value) > 0;
  436. });
  437. }
  438. /**
  439. * Replace null parameters with their defaults.
  440. *
  441. * @param array $parameters
  442. * @return array
  443. */
  444. protected function replaceDefaults(array $parameters)
  445. {
  446. foreach ($parameters as $key => $value) {
  447. $parameters[$key] = isset($value) ? $value : Arr::get($this->defaults, $key);
  448. }
  449. foreach ($this->defaults as $key => $value) {
  450. if (! isset($parameters[$key])) {
  451. $parameters[$key] = $value;
  452. }
  453. }
  454. return $parameters;
  455. }
  456. /**
  457. * Parse the route action into a standard array.
  458. *
  459. * @param callable|array|null $action
  460. * @return array
  461. *
  462. * @throws \UnexpectedValueException
  463. */
  464. protected function parseAction($action)
  465. {
  466. // If no action is passed in right away, we assume the user will make use of
  467. // fluent routing. In that case, we set a default closure, to be executed
  468. // if the user never explicitly sets an action to handle the given uri.
  469. if (is_null($action)) {
  470. return ['uses' => function () {
  471. throw new LogicException("Route for [{$this->uri}] has no action.");
  472. }];
  473. }
  474. // If the action is already a Closure instance, we will just set that instance
  475. // as the "uses" property, because there is nothing else we need to do when
  476. // it is available. Otherwise we will need to find it in the action list.
  477. if (is_callable($action)) {
  478. return ['uses' => $action];
  479. }
  480. // If no "uses" property has been set, we will dig through the array to find a
  481. // Closure instance within this list. We will set the first Closure we come
  482. // across into the "uses" property that will get fired off by this route.
  483. elseif (! isset($action['uses'])) {
  484. $action['uses'] = $this->findCallable($action);
  485. }
  486. if (is_string($action['uses']) && ! Str::contains($action['uses'], '@')) {
  487. throw new UnexpectedValueException(sprintf(
  488. 'Invalid route action: [%s]', $action['uses']
  489. ));
  490. }
  491. return $action;
  492. }
  493. /**
  494. * Find the callable in an action array.
  495. *
  496. * @param array $action
  497. * @return callable
  498. */
  499. protected function findCallable(array $action)
  500. {
  501. return Arr::first($action, function ($key, $value) {
  502. return is_callable($value) && is_numeric($key);
  503. });
  504. }
  505. /**
  506. * Get the route validators for the instance.
  507. *
  508. * @return array
  509. */
  510. public static function getValidators()
  511. {
  512. if (isset(static::$validators)) {
  513. return static::$validators;
  514. }
  515. // To match the route, we will use a chain of responsibility pattern with the
  516. // validator implementations. We will spin through each one making sure it
  517. // passes and then we will know if the route as a whole matches request.
  518. return static::$validators = [
  519. new MethodValidator, new SchemeValidator,
  520. new HostValidator, new UriValidator,
  521. ];
  522. }
  523. /**
  524. * Set a default value for the route.
  525. *
  526. * @param string $key
  527. * @param mixed $value
  528. * @return $this
  529. */
  530. public function defaults($key, $value)
  531. {
  532. $this->defaults[$key] = $value;
  533. return $this;
  534. }
  535. /**
  536. * Set a regular expression requirement on the route.
  537. *
  538. * @param array|string $name
  539. * @param string $expression
  540. * @return $this
  541. */
  542. public function where($name, $expression = null)
  543. {
  544. foreach ($this->parseWhere($name, $expression) as $name => $expression) {
  545. $this->wheres[$name] = $expression;
  546. }
  547. return $this;
  548. }
  549. /**
  550. * Parse arguments to the where method into an array.
  551. *
  552. * @param array|string $name
  553. * @param string $expression
  554. * @return array
  555. */
  556. protected function parseWhere($name, $expression)
  557. {
  558. return is_array($name) ? $name : [$name => $expression];
  559. }
  560. /**
  561. * Set a list of regular expression requirements on the route.
  562. *
  563. * @param array $wheres
  564. * @return $this
  565. */
  566. protected function whereArray(array $wheres)
  567. {
  568. foreach ($wheres as $name => $expression) {
  569. $this->where($name, $expression);
  570. }
  571. return $this;
  572. }
  573. /**
  574. * Add a prefix to the route URI.
  575. *
  576. * @param string $prefix
  577. * @return $this
  578. */
  579. public function prefix($prefix)
  580. {
  581. $uri = rtrim($prefix, '/').'/'.ltrim($this->uri, '/');
  582. $this->uri = trim($uri, '/');
  583. return $this;
  584. }
  585. /**
  586. * Get the URI associated with the route.
  587. *
  588. * @return string
  589. */
  590. public function getPath()
  591. {
  592. return $this->uri();
  593. }
  594. /**
  595. * Get the URI associated with the route.
  596. *
  597. * @return string
  598. */
  599. public function uri()
  600. {
  601. return $this->uri;
  602. }
  603. /**
  604. * Get the HTTP verbs the route responds to.
  605. *
  606. * @return array
  607. */
  608. public function getMethods()
  609. {
  610. return $this->methods();
  611. }
  612. /**
  613. * Get the HTTP verbs the route responds to.
  614. *
  615. * @return array
  616. */
  617. public function methods()
  618. {
  619. return $this->methods;
  620. }
  621. /**
  622. * Determine if the route only responds to HTTP requests.
  623. *
  624. * @return bool
  625. */
  626. public function httpOnly()
  627. {
  628. return in_array('http', $this->action, true);
  629. }
  630. /**
  631. * Determine if the route only responds to HTTPS requests.
  632. *
  633. * @return bool
  634. */
  635. public function httpsOnly()
  636. {
  637. return $this->secure();
  638. }
  639. /**
  640. * Determine if the route only responds to HTTPS requests.
  641. *
  642. * @return bool
  643. */
  644. public function secure()
  645. {
  646. return in_array('https', $this->action, true);
  647. }
  648. /**
  649. * Get the domain defined for the route.
  650. *
  651. * @return string|null
  652. */
  653. public function domain()
  654. {
  655. return isset($this->action['domain']) ? $this->action['domain'] : null;
  656. }
  657. /**
  658. * Get the URI that the route responds to.
  659. *
  660. * @return string
  661. */
  662. public function getUri()
  663. {
  664. return $this->uri;
  665. }
  666. /**
  667. * Set the URI that the route responds to.
  668. *
  669. * @param string $uri
  670. * @return $this
  671. */
  672. public function setUri($uri)
  673. {
  674. $this->uri = $uri;
  675. return $this;
  676. }
  677. /**
  678. * Get the prefix of the route instance.
  679. *
  680. * @return string
  681. */
  682. public function getPrefix()
  683. {
  684. return isset($this->action['prefix']) ? $this->action['prefix'] : null;
  685. }
  686. /**
  687. * Get the name of the route instance.
  688. *
  689. * @return string
  690. */
  691. public function getName()
  692. {
  693. return isset($this->action['as']) ? $this->action['as'] : null;
  694. }
  695. /**
  696. * Add or change the route name.
  697. *
  698. * @param string $name
  699. * @return $this
  700. */
  701. public function name($name)
  702. {
  703. $this->action['as'] = isset($this->action['as']) ? $this->action['as'].$name : $name;
  704. return $this;
  705. }
  706. /**
  707. * Set the handler for the route.
  708. *
  709. * @param \Closure|string $action
  710. * @return $this
  711. */
  712. public function uses($action)
  713. {
  714. $action = is_string($action) ? $this->addGroupNamespaceToStringUses($action) : $action;
  715. return $this->setAction(array_merge($this->action, $this->parseAction([
  716. 'uses' => $action,
  717. 'controller' => $action,
  718. ])));
  719. }
  720. /**
  721. * Parse a string based action for the "uses" fluent method.
  722. *
  723. * @param string $action
  724. * @return string
  725. */
  726. protected function addGroupNamespaceToStringUses($action)
  727. {
  728. $groupStack = last($this->router->getGroupStack());
  729. if (isset($groupStack['namespace']) && strpos($action, '\\') !== 0) {
  730. return $groupStack['namespace'].'\\'.$action;
  731. }
  732. return $action;
  733. }
  734. /**
  735. * Get the action name for the route.
  736. *
  737. * @return string
  738. */
  739. public function getActionName()
  740. {
  741. return isset($this->action['controller']) ? $this->action['controller'] : 'Closure';
  742. }
  743. /**
  744. * Get the action array for the route.
  745. *
  746. * @return array
  747. */
  748. public function getAction()
  749. {
  750. return $this->action;
  751. }
  752. /**
  753. * Set the action array for the route.
  754. *
  755. * @param array $action
  756. * @return $this
  757. */
  758. public function setAction(array $action)
  759. {
  760. $this->action = $action;
  761. return $this;
  762. }
  763. /**
  764. * Get the compiled version of the route.
  765. *
  766. * @return \Symfony\Component\Routing\CompiledRoute
  767. */
  768. public function getCompiled()
  769. {
  770. return $this->compiled;
  771. }
  772. /**
  773. * Set the router instance on the route.
  774. *
  775. * @param \Illuminate\Routing\Router $router
  776. * @return $this
  777. */
  778. public function setRouter(Router $router)
  779. {
  780. $this->router = $router;
  781. return $this;
  782. }
  783. /**
  784. * Set the container instance on the route.
  785. *
  786. * @param \Illuminate\Container\Container $container
  787. * @return $this
  788. */
  789. public function setContainer(Container $container)
  790. {
  791. $this->container = $container;
  792. return $this;
  793. }
  794. /**
  795. * Prepare the route instance for serialization.
  796. *
  797. * @return void
  798. *
  799. * @throws \LogicException
  800. */
  801. public function prepareForSerialization()
  802. {
  803. if ($this->action['uses'] instanceof Closure) {
  804. throw new LogicException("Unable to prepare route [{$this->uri}] for serialization. Uses Closure.");
  805. }
  806. unset($this->router, $this->container, $this->compiled);
  807. }
  808. /**
  809. * Dynamically access route parameters.
  810. *
  811. * @param string $key
  812. * @return mixed
  813. */
  814. public function __get($key)
  815. {
  816. return $this->parameter($key);
  817. }
  818. }