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

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

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