AcceptHeaderMatcher.php 2.98 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
<?php

/**
 * @file
 * Contains Drupal\Core\Routing\AcceptHeaderMatcher.
 */

namespace Drupal\Core\Routing;

use Drupal\Component\Utility\String;
use Drupal\Core\ContentNegotiation;
use Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
use Symfony\Component\Routing\RouteCollection;

/**
 * Filters routes based on the media type specified in the HTTP Accept headers.
 */
class AcceptHeaderMatcher implements RouteFilterInterface {

  /**
   * The content negotiation library.
   *
   * @var \Drupal\Core\ContentNegotiation
   */
  protected $contentNegotiation;

  /**
   * Constructs a new AcceptHeaderMatcher.
   *
32
   * @param \Drupal\Core\ContentNegotiation $content_negotiation
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
   *   The content negotiation library.
   */
  public function __construct(ContentNegotiation $content_negotiation) {
    $this->contentNegotiation = $content_negotiation;
  }

  /**
   * {@inheritdoc}
   */
  public function filter(RouteCollection $collection, Request $request) {
    // Generates a list of Symfony formats matching the acceptable MIME types.
    // @todo replace by proper content negotiation library.
    $acceptable_mime_types = $request->getAcceptableContentTypes();
    $acceptable_formats = array_filter(array_map(array($request, 'getFormat'), $acceptable_mime_types));
    $primary_format = $this->contentNegotiation->getContentType($request);

    foreach ($collection as $name => $route) {
      // _format could be a |-delimited list of supported formats.
      $supported_formats = array_filter(explode('|', $route->getRequirement('_format')));

      if (empty($supported_formats)) {
        // No format restriction on the route, so it always matches. Move it to
        // the end of the collection by re-adding it.
        $collection->add($name, $route);
      }
      elseif (in_array($primary_format, $supported_formats)) {
        // Perfect match, which will get a higher priority by leaving the route
        // on top of the list.
      }
      // The route partially matches if it doesn't care about format, if it
      // explicitly allows any format, or if one of its allowed formats is
      // in the request's list of acceptable formats.
      elseif (in_array('*/*', $acceptable_mime_types) || array_intersect($acceptable_formats, $supported_formats)) {
        // Move it to the end of the list.
        $collection->add($name, $route);
      }
      else {
        // Remove the route if it does not match at all.
        $collection->remove($name);
      }
    }

    if (count($collection)) {
      return $collection;
    }

    // We do not throw a
    // \Symfony\Component\Routing\Exception\ResourceNotFoundException here
    // because we don't want to return a 404 status code, but rather a 406.
    throw new NotAcceptableHttpException(String::format('No route found for the specified formats @formats.', array('@formats' => implode(' ', $acceptable_mime_types))));
  }

}