Insphpect

This tool is currently proof-of-concept. Your feedback and evaluation is valuable in helping to improve it and ensure its reports are meaninful.

Please click here to complete a short survey to tell us what you think. It should take less than 5 minutes and help further this research project!

Symfony\Component\Routing\Matcher\UrlMatcher

Detected issues

Issue Method Line number

Code

Click highlighted lines for details

<?php/* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */namespace Symfony\Component\Routing\Matcher;use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface;use Symfony\Component\ExpressionLanguage\ExpressionLanguage;use Symfony\Component\HttpFoundation\Request;use Symfony\Component\Routing\Exception\MethodNotAllowedException;use Symfony\Component\Routing\Exception\NoConfigurationException;use Symfony\Component\Routing\Exception\ResourceNotFoundException;use Symfony\Component\Routing\RequestContext;use Symfony\Component\Routing\Route;use Symfony\Component\Routing\RouteCollection;/** * UrlMatcher matches URL based on a set of routes. * * @author Fabien Potencier <fabien@symfony.com> */class UrlMatcher implements UrlMatcherInterface, RequestMatcherInterface{    public const REQUIREMENT_MATCH = 0;    public const REQUIREMENT_MISMATCH = 1;    public const ROUTE_MATCH = 2;    /** @var RequestContext */    protected $context;    /**     * Collects HTTP methods that would be allowed for the request.     */    protected $allow = [];    /**     * Collects URI schemes that would be allowed for the request.     *     * @internal     */    protected $allowSchemes = [];    protected $routes;    protected $request;    protected $expressionLanguage;    /**     * @var ExpressionFunctionProviderInterface[]     */    protected $expressionLanguageProviders = [];    public function __construct(RouteCollection $routes, RequestContext $context)    {        $this->routes = $routes;        $this->context = $context;    }    /**     * {@inheritdoc}     */    public function setContext(RequestContext $context)    {        $this->context = $context;    }    /**     * {@inheritdoc}     */    public function getContext()    {        return $this->context;    }    /**     * {@inheritdoc}     */    public function match(string $pathinfo)    {        $this->allow = $this->allowSchemes = [];        if ($ret = $this->matchCollection(rawurldecode($pathinfo) ?: '/', $this->routes)) {            return $ret;        }        if ('/' === $pathinfo && !$this->allow && !$this->allowSchemes) {            throw new NoConfigurationException();        }        throw 0 < \count($this->allow) ? new MethodNotAllowedException(array_unique($this->allow)) : new ResourceNotFoundException(sprintf('No routes found for "%s".', $pathinfo));    }    /**     * {@inheritdoc}     */    public function matchRequest(Request $request)    {        $this->request = $request;        $ret = $this->match($request->getPathInfo());        $this->request = null;        return $ret;    }    public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider)    {        $this->expressionLanguageProviders[] = $provider;    }    /**     * Tries to match a URL with a set of routes.     *     * @param string $pathinfo The path info to be parsed     *     * @return array An array of parameters     *     * @throws NoConfigurationException  If no routing configuration could be found     * @throws ResourceNotFoundException If the resource could not be found     * @throws MethodNotAllowedException If the resource was found but the request method is not allowed     */    protected function matchCollection(string $pathinfo, RouteCollection $routes)    {        // HEAD and GET are equivalent as per RFC        if ('HEAD' === $method = $this->context->getMethod()) {            $method = 'GET';        }        $supportsTrailingSlash = 'GET' === $method && $this instanceof RedirectableUrlMatcherInterface;        $trimmedPathinfo = rtrim($pathinfo, '/') ?: '/';        foreach ($routes as $name => $route) {            $compiledRoute = $route->compile();            $staticPrefix = rtrim($compiledRoute->getStaticPrefix(), '/');            $requiredMethods = $route->getMethods();            // check the static prefix of the URL first. Only use the more expensive preg_match when it matches            if ('' !== $staticPrefix && !str_starts_with($trimmedPathinfo, $staticPrefix)) {                continue;            }            $regex = $compiledRoute->getRegex();            $pos = strrpos($regex, '$');            $hasTrailingSlash = '/' === $regex[$pos - 1];            $regex = substr_replace($regex, '/?$', $pos - $hasTrailingSlash, 1 + $hasTrailingSlash);            if (!preg_match($regex, $pathinfo, $matches)) {                continue;            }            $hasTrailingVar = $trimmedPathinfo !== $pathinfo && preg_match('#\{\w+\}/?$#', $route->getPath());            if ($hasTrailingVar && ($hasTrailingSlash || (null === $m = $matches[\count($compiledRoute->getPathVariables())] ?? null) || '/' !== ($m[-1] ?? '/')) && preg_match($regex, $trimmedPathinfo, $m)) {                if ($hasTrailingSlash) {                    $matches = $m;                } else {                    $hasTrailingVar = false;                }            }            $hostMatches = [];            if ($compiledRoute->getHostRegex() && !preg_match($compiledRoute->getHostRegex(), $this->context->getHost(), $hostMatches)) {                continue;            }            $status = $this->handleRouteRequirements($pathinfo, $name, $route);            if (self::REQUIREMENT_MISMATCH === $status[0]) {                continue;            }            if ('/' !== $pathinfo && !$hasTrailingVar && $hasTrailingSlash === ($trimmedPathinfo === $pathinfo)) {                if ($supportsTrailingSlash && (!$requiredMethods || \in_array('GET', $requiredMethods))) {                    return $this->allow = $this->allowSchemes = [];                }                continue;            }            if ($route->getSchemes() && !$route->hasScheme($this->context->getScheme())) {                $this->allowSchemes = array_merge($this->allowSchemes, $route->getSchemes());                continue;            }            if ($requiredMethods && !\in_array($method, $requiredMethods)) {                $this->allow = array_merge($this->allow, $requiredMethods);                continue;            }            return $this->getAttributes($route, $name, array_replace($matches, $hostMatches, $status[1] ?? []));        }        return [];    }    /**     * Returns an array of values to use as request attributes.     *     * As this method requires the Route object, it is not available     * in matchers that do not have access to the matched Route instance     * (like the PHP and Apache matcher dumpers).     *     * @return array An array of parameters     */    protected function getAttributes(Route $route, string $name, array $attributes)    {        $defaults = $route->getDefaults();        if (isset($defaults['_canonical_route'])) {            $name = $defaults['_canonical_route'];            unset($defaults['_canonical_route']);        }        $attributes['_route'] = $name;        return $this->mergeDefaults($attributes, $defaults);    }    /**     * Handles specific route requirements.     *     * @return array The first element represents the status, the second contains additional information     */    protected function handleRouteRequirements(string $pathinfo, string $name, Route $route)    {        // expression condition        if ($route->getCondition() && !$this->getExpressionLanguage()->evaluate($route->getCondition(), ['context' => $this->context, 'request' => $this->request ?: $this->createRequest($pathinfo)])) {            return [self::REQUIREMENT_MISMATCH, null];        }        return [self::REQUIREMENT_MATCH, null];    }    /**     * Get merged default parameters.     *     * @return array Merged default parameters     */    protected function mergeDefaults(array $params, array $defaults)    {        foreach ($params as $key => $value) {            if (!\is_int($key) && null !== $value) {                $defaults[$key] = $value;            }        }        return $defaults;    }    protected function getExpressionLanguage()    {        if (null === $this->expressionLanguage) {            if (!class_exists(ExpressionLanguage::class)) {                throw new \LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.');            }            $this->expressionLanguage = new ExpressionLanguage(null, $this->expressionLanguageProviders);        }        return $this->expressionLanguage;    }    /**     * @internal     */    protected function createRequest(string $pathinfo): ?Request    {        if (!class_exists(Request::class)) {            return null;        }        return Request::create($this->context->getScheme().'://'.$this->context->getHost().$this->context->getBaseUrl().$pathinfo, $this->context->getMethod(), $this->context->getParameters(), [], [], [            'SCRIPT_FILENAME' => $this->context->getBaseUrl(),            'SCRIPT_NAME' => $this->context->getBaseUrl(),        ]);    }}