Skip to content

Commit

Permalink
[routing] Added negotiation to UrlMatcher.
Browse files Browse the repository at this point in the history
  • Loading branch information
jfsimon committed Oct 11, 2012
1 parent 8c02e8a commit 532720a
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 2 deletions.
35 changes: 35 additions & 0 deletions src/Symfony/Component/Routing/Exception/NotAcceptableException.php
@@ -0,0 +1,35 @@
<?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\Exception;

use Symfony\Component\Routing\RequestAcceptance;

/**
* The resource was found but the Accept-* header does not match requirement.
*
* This exception should trigger an HTTP 405 response in your application code.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
class NotAcceptableException extends \RuntimeException implements ExceptionInterface
{
protected $acceptance;
protected $requirement;

public function __construct(RequestAcceptance $acceptance, $requirement, $message = null, $code = 0, \Exception $previous = null)
{
$this->acceptance = $acceptance;
$this->requirement = $requirement;
$message = sprintf('None of the accepted values "%s" match route requirement "%s".', implode(', ', $acceptance->getValues()), $requirement);
parent::__construct($message, $code, $previous);
}
}
19 changes: 19 additions & 0 deletions src/Symfony/Component/Routing/Matcher/UrlMatcher.php
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Routing\Matcher;

use Symfony\Component\Routing\Exception\MethodNotAllowedException;
use Symfony\Component\Routing\Exception\NotAcceptableException;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RequestContext;
Expand All @@ -32,6 +33,7 @@ class UrlMatcher implements UrlMatcherInterface

protected $context;
protected $allow;
protected $negotiatedVariables;

private $routes;

Expand All @@ -47,6 +49,7 @@ public function __construct(RouteCollection $routes, RequestContext $context)
{
$this->routes = $routes;
$this->context = $context;
$this->negotiatedVariables = array('_locale', '_format', '_charset', '_encoding');
}

/**
Expand Down Expand Up @@ -94,6 +97,7 @@ public function match($pathinfo)
*/
protected function matchCollection($pathinfo, RouteCollection $routes)
{
/** @var Route $route */
foreach ($routes as $name => $route) {
if ($route instanceof RouteCollection) {
if (false === strpos($route->getPrefix(), '{') && $route->getPrefix() !== substr($pathinfo, 0, strlen($route->getPrefix()))) {
Expand Down Expand Up @@ -132,6 +136,21 @@ protected function matchCollection($pathinfo, RouteCollection $routes)
}
}

if ($route->getOption('negotiate')) {
foreach ($this->negotiatedVariables as $variable) {
if (in_array($variable, $compiledRoute->getVariables()) && null !== $acceptance = $this->context->getAcceptance($variable)) {
if (null !== $requirement = $route->getRequirement($variable)) {
$acceptance = $acceptance->filter($requirement);
}
if (null !== $default = $acceptance->getBestValue()) {
$route->setDefault($variable, $default);
} else {
throw new NotAcceptableException($this->context->getAcceptance($variable), $requirement);
}
}
}
}

$status = $this->handleRouteRequirements($pathinfo, $name, $route);

if (self::ROUTE_MATCH === $status[0]) {
Expand Down
35 changes: 33 additions & 2 deletions src/Symfony/Component/Routing/RequestAcceptance.php
Expand Up @@ -18,10 +18,31 @@ class RequestAcceptance

/**
* Constructor.
*
* @param array $qualities
*/
public function __construct()
public function __construct(array $qualities = array())
{
$this->qualities = array();
$this->qualities = $qualities;
}

/**
* Filters value with given regex.
*
* @param string $regex
*
* @return RequestAcceptance
*/
public function filter($regex)
{
$qualities = array();
foreach ($this->qualities as $value => $quality) {
if (preg_match(sprintf('~%s~i', $regex), $value)) {
$qualities[$value] = $quality;
}
}

return new self($qualities);
}

/**
Expand Down Expand Up @@ -66,4 +87,14 @@ public function getQuality($value)
{
return isset($this->qualities[$value]) ? $this->qualities[$value] : 0;
}

/**
* Returns the values.
*
* @return array
*/
public function getValues()
{
return array_keys($this->qualities);
}
}
27 changes: 27 additions & 0 deletions src/Symfony/Component/Routing/RequestContext.php
Expand Up @@ -12,6 +12,7 @@
namespace Symfony\Component\Routing;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesser;

/**
* Holds information about the current request.
Expand Down Expand Up @@ -63,6 +64,32 @@ public function fromRequest(Request $request)
$this->setScheme($request->getScheme());
$this->setHttpPort($request->isSecure() ? $this->httpPort : $request->getPort());
$this->setHttpsPort($request->isSecure() ? $request->getPort() : $this->httpsPort);

if ($request->headers->has('Accept')) {
$types = $request->headers->get('Accept');
$typeToFormat = function($type) { return ExtensionGuesser::getInstance()->guess($type); };
$this->acceptances['_format'] = new RequestAcceptance(array_combine(array_map($typeToFormat, array_keys($types)), array_values($types)));
} else {
unset($this->acceptances['_format']);
}

if ($request->headers->has('Accept-Language')) {
$this->acceptances['_locale'] = new RequestAcceptance($request->headers->get('Accept-Language'));
} else {
unset($this->acceptances['_locale']);
}

if ($request->headers->has('Accept-Charset')) {
$this->acceptances['_charset'] = new RequestAcceptance($request->headers->get('Accept-Charset'));
} else {
unset($this->acceptances['_charset']);
}

if ($request->headers->has('Accept-Encoding')) {
$this->acceptances['_encoding'] = new RequestAcceptance($request->headers->get('Accept-Encoding'));
} else {
unset($this->acceptances['_encoding']);
}
}

/**
Expand Down

0 comments on commit 532720a

Please sign in to comment.