PageRenderTime 45ms CodeModel.GetById 3ms RepoModel.GetById 0ms app.codeStats 0ms

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

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