PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

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

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