vendor/symfony/routing/Route.php line 20

  1. <?php
  2. /*
  3.  * This file is part of the Symfony package.
  4.  *
  5.  * (c) Fabien Potencier <fabien@symfony.com>
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. namespace Symfony\Component\Routing;
  11. /**
  12.  * A Route describes a route and its parameters.
  13.  *
  14.  * @author Fabien Potencier <fabien@symfony.com>
  15.  * @author Tobias Schultze <http://tobion.de>
  16.  */
  17. class Route implements \Serializable
  18. {
  19.     private string $path '/';
  20.     private string $host '';
  21.     private array $schemes = [];
  22.     private array $methods = [];
  23.     private array $defaults = [];
  24.     private array $requirements = [];
  25.     private array $options = [];
  26.     private string $condition '';
  27.     private ?CompiledRoute $compiled null;
  28.     /**
  29.      * Constructor.
  30.      *
  31.      * Available options:
  32.      *
  33.      *  * compiler_class: A class name able to compile this route instance (RouteCompiler by default)
  34.      *  * utf8:           Whether UTF-8 matching is enforced ot not
  35.      *
  36.      * @param string                    $path         The path pattern to match
  37.      * @param array                     $defaults     An array of default parameter values
  38.      * @param array<string|\Stringable> $requirements An array of requirements for parameters (regexes)
  39.      * @param array                     $options      An array of options
  40.      * @param string|null               $host         The host pattern to match
  41.      * @param string|string[]           $schemes      A required URI scheme or an array of restricted schemes
  42.      * @param string|string[]           $methods      A required HTTP method or an array of restricted methods
  43.      * @param string|null               $condition    A condition that should evaluate to true for the route to match
  44.      */
  45.     public function __construct(string $path, array $defaults = [], array $requirements = [], array $options = [], ?string $host ''string|array $schemes = [], string|array $methods = [], ?string $condition '')
  46.     {
  47.         $this->setPath($path);
  48.         $this->addDefaults($defaults);
  49.         $this->addRequirements($requirements);
  50.         $this->setOptions($options);
  51.         $this->setHost($host);
  52.         $this->setSchemes($schemes);
  53.         $this->setMethods($methods);
  54.         $this->setCondition($condition);
  55.     }
  56.     public function __serialize(): array
  57.     {
  58.         return [
  59.             'path' => $this->path,
  60.             'host' => $this->host,
  61.             'defaults' => $this->defaults,
  62.             'requirements' => $this->requirements,
  63.             'options' => $this->options,
  64.             'schemes' => $this->schemes,
  65.             'methods' => $this->methods,
  66.             'condition' => $this->condition,
  67.             'compiled' => $this->compiled,
  68.         ];
  69.     }
  70.     /**
  71.      * @internal
  72.      */
  73.     final public function serialize(): string
  74.     {
  75.         throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
  76.     }
  77.     public function __unserialize(array $data): void
  78.     {
  79.         $this->path $data['path'];
  80.         $this->host $data['host'];
  81.         $this->defaults $data['defaults'];
  82.         $this->requirements $data['requirements'];
  83.         $this->options $data['options'];
  84.         $this->schemes $data['schemes'];
  85.         $this->methods $data['methods'];
  86.         if (isset($data['condition'])) {
  87.             $this->condition $data['condition'];
  88.         }
  89.         if (isset($data['compiled'])) {
  90.             $this->compiled $data['compiled'];
  91.         }
  92.     }
  93.     /**
  94.      * @internal
  95.      */
  96.     final public function unserialize(string $serialized)
  97.     {
  98.         $this->__unserialize(unserialize($serialized));
  99.     }
  100.     public function getPath(): string
  101.     {
  102.         return $this->path;
  103.     }
  104.     /**
  105.      * @return $this
  106.      */
  107.     public function setPath(string $pattern): static
  108.     {
  109.         $pattern $this->extractInlineDefaultsAndRequirements($pattern);
  110.         // A pattern must start with a slash and must not have multiple slashes at the beginning because the
  111.         // generated path for this route would be confused with a network path, e.g. '//domain.com/path'.
  112.         $this->path '/'.ltrim(trim($pattern), '/');
  113.         $this->compiled null;
  114.         return $this;
  115.     }
  116.     public function getHost(): string
  117.     {
  118.         return $this->host;
  119.     }
  120.     /**
  121.      * @return $this
  122.      */
  123.     public function setHost(?string $pattern): static
  124.     {
  125.         $this->host $this->extractInlineDefaultsAndRequirements((string) $pattern);
  126.         $this->compiled null;
  127.         return $this;
  128.     }
  129.     /**
  130.      * Returns the lowercased schemes this route is restricted to.
  131.      * So an empty array means that any scheme is allowed.
  132.      *
  133.      * @return string[]
  134.      */
  135.     public function getSchemes(): array
  136.     {
  137.         return $this->schemes;
  138.     }
  139.     /**
  140.      * Sets the schemes (e.g. 'https') this route is restricted to.
  141.      * So an empty array means that any scheme is allowed.
  142.      *
  143.      * @param string|string[] $schemes The scheme or an array of schemes
  144.      *
  145.      * @return $this
  146.      */
  147.     public function setSchemes(string|array $schemes): static
  148.     {
  149.         $this->schemes array_map('strtolower', (array) $schemes);
  150.         $this->compiled null;
  151.         return $this;
  152.     }
  153.     /**
  154.      * Checks if a scheme requirement has been set.
  155.      */
  156.     public function hasScheme(string $scheme): bool
  157.     {
  158.         return \in_array(strtolower($scheme), $this->schemestrue);
  159.     }
  160.     /**
  161.      * Returns the uppercased HTTP methods this route is restricted to.
  162.      * So an empty array means that any method is allowed.
  163.      *
  164.      * @return string[]
  165.      */
  166.     public function getMethods(): array
  167.     {
  168.         return $this->methods;
  169.     }
  170.     /**
  171.      * Sets the HTTP methods (e.g. 'POST') this route is restricted to.
  172.      * So an empty array means that any method is allowed.
  173.      *
  174.      * @param string|string[] $methods The method or an array of methods
  175.      *
  176.      * @return $this
  177.      */
  178.     public function setMethods(string|array $methods): static
  179.     {
  180.         $this->methods array_map('strtoupper', (array) $methods);
  181.         $this->compiled null;
  182.         return $this;
  183.     }
  184.     public function getOptions(): array
  185.     {
  186.         return $this->options;
  187.     }
  188.     /**
  189.      * @return $this
  190.      */
  191.     public function setOptions(array $options): static
  192.     {
  193.         $this->options = [
  194.             'compiler_class' => RouteCompiler::class,
  195.         ];
  196.         return $this->addOptions($options);
  197.     }
  198.     /**
  199.      * @return $this
  200.      */
  201.     public function addOptions(array $options): static
  202.     {
  203.         foreach ($options as $name => $option) {
  204.             $this->options[$name] = $option;
  205.         }
  206.         $this->compiled null;
  207.         return $this;
  208.     }
  209.     /**
  210.      * Sets an option value.
  211.      *
  212.      * @return $this
  213.      */
  214.     public function setOption(string $namemixed $value): static
  215.     {
  216.         $this->options[$name] = $value;
  217.         $this->compiled null;
  218.         return $this;
  219.     }
  220.     /**
  221.      * Returns the option value or null when not found.
  222.      */
  223.     public function getOption(string $name): mixed
  224.     {
  225.         return $this->options[$name] ?? null;
  226.     }
  227.     public function hasOption(string $name): bool
  228.     {
  229.         return \array_key_exists($name$this->options);
  230.     }
  231.     public function getDefaults(): array
  232.     {
  233.         return $this->defaults;
  234.     }
  235.     /**
  236.      * @return $this
  237.      */
  238.     public function setDefaults(array $defaults): static
  239.     {
  240.         $this->defaults = [];
  241.         return $this->addDefaults($defaults);
  242.     }
  243.     /**
  244.      * @return $this
  245.      */
  246.     public function addDefaults(array $defaults): static
  247.     {
  248.         if (isset($defaults['_locale']) && $this->isLocalized()) {
  249.             unset($defaults['_locale']);
  250.         }
  251.         foreach ($defaults as $name => $default) {
  252.             $this->defaults[$name] = $default;
  253.         }
  254.         $this->compiled null;
  255.         return $this;
  256.     }
  257.     public function getDefault(string $name): mixed
  258.     {
  259.         return $this->defaults[$name] ?? null;
  260.     }
  261.     public function hasDefault(string $name): bool
  262.     {
  263.         return \array_key_exists($name$this->defaults);
  264.     }
  265.     /**
  266.      * @return $this
  267.      */
  268.     public function setDefault(string $namemixed $default): static
  269.     {
  270.         if ('_locale' === $name && $this->isLocalized()) {
  271.             return $this;
  272.         }
  273.         $this->defaults[$name] = $default;
  274.         $this->compiled null;
  275.         return $this;
  276.     }
  277.     public function getRequirements(): array
  278.     {
  279.         return $this->requirements;
  280.     }
  281.     /**
  282.      * @return $this
  283.      */
  284.     public function setRequirements(array $requirements): static
  285.     {
  286.         $this->requirements = [];
  287.         return $this->addRequirements($requirements);
  288.     }
  289.     /**
  290.      * @return $this
  291.      */
  292.     public function addRequirements(array $requirements): static
  293.     {
  294.         if (isset($requirements['_locale']) && $this->isLocalized()) {
  295.             unset($requirements['_locale']);
  296.         }
  297.         foreach ($requirements as $key => $regex) {
  298.             $this->requirements[$key] = $this->sanitizeRequirement($key$regex);
  299.         }
  300.         $this->compiled null;
  301.         return $this;
  302.     }
  303.     public function getRequirement(string $key): ?string
  304.     {
  305.         return $this->requirements[$key] ?? null;
  306.     }
  307.     public function hasRequirement(string $key): bool
  308.     {
  309.         return \array_key_exists($key$this->requirements);
  310.     }
  311.     /**
  312.      * @return $this
  313.      */
  314.     public function setRequirement(string $keystring $regex): static
  315.     {
  316.         if ('_locale' === $key && $this->isLocalized()) {
  317.             return $this;
  318.         }
  319.         $this->requirements[$key] = $this->sanitizeRequirement($key$regex);
  320.         $this->compiled null;
  321.         return $this;
  322.     }
  323.     public function getCondition(): string
  324.     {
  325.         return $this->condition;
  326.     }
  327.     /**
  328.      * @return $this
  329.      */
  330.     public function setCondition(?string $condition): static
  331.     {
  332.         $this->condition = (string) $condition;
  333.         $this->compiled null;
  334.         return $this;
  335.     }
  336.     /**
  337.      * Compiles the route.
  338.      *
  339.      * @throws \LogicException If the Route cannot be compiled because the
  340.      *                         path or host pattern is invalid
  341.      *
  342.      * @see RouteCompiler which is responsible for the compilation process
  343.      */
  344.     public function compile(): CompiledRoute
  345.     {
  346.         if (null !== $this->compiled) {
  347.             return $this->compiled;
  348.         }
  349.         $class $this->getOption('compiler_class');
  350.         return $this->compiled $class::compile($this);
  351.     }
  352.     private function extractInlineDefaultsAndRequirements(string $pattern): string
  353.     {
  354.         if (false === strpbrk($pattern'?<')) {
  355.             return $pattern;
  356.         }
  357.         return preg_replace_callback('#\{(!?)([\w\x80-\xFF]++)(<.*?>)?(\?[^\}]*+)?\}#', function ($m) {
  358.             if (isset($m[4][0])) {
  359.                 $this->setDefault($m[2], '?' !== $m[4] ? substr($m[4], 1) : null);
  360.             }
  361.             if (isset($m[3][0])) {
  362.                 $this->setRequirement($m[2], substr($m[3], 1, -1));
  363.             }
  364.             return '{'.$m[1].$m[2].'}';
  365.         }, $pattern);
  366.     }
  367.     private function sanitizeRequirement(string $keystring $regex)
  368.     {
  369.         if ('' !== $regex) {
  370.             if ('^' === $regex[0]) {
  371.                 $regex substr($regex1);
  372.             } elseif (str_starts_with($regex'\\A')) {
  373.                 $regex substr($regex2);
  374.             }
  375.         }
  376.         if (str_ends_with($regex'$')) {
  377.             $regex substr($regex0, -1);
  378.         } elseif (\strlen($regex) - === strpos($regex'\\z')) {
  379.             $regex substr($regex0, -2);
  380.         }
  381.         if ('' === $regex) {
  382.             throw new \InvalidArgumentException(sprintf('Routing requirement for "%s" cannot be empty.'$key));
  383.         }
  384.         return $regex;
  385.     }
  386.     private function isLocalized(): bool
  387.     {
  388.         return isset($this->defaults['_locale']) && isset($this->defaults['_canonical_route']) && ($this->requirements['_locale'] ?? null) === preg_quote($this->defaults['_locale']);
  389.     }
  390. }