ResourceRoutes.php 5.5 KB
Newer Older
1 2
<?php

3
namespace Drupal\rest\Routing;
4

5
use Drupal\Core\Entity\EntityTypeManagerInterface;
6 7
use Drupal\Core\Routing\RouteBuildEvent;
use Drupal\Core\Routing\RoutingEvents;
8
use Drupal\rest\Plugin\Type\ResourcePluginManager;
9
use Drupal\rest\RestResourceConfigInterface;
10
use Psr\Log\LoggerInterface;
11
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
12
use Symfony\Component\Routing\RouteCollection;
13 14 15 16

/**
 * Subscriber for REST-style routes.
 */
17
class ResourceRoutes implements EventSubscriberInterface {
18 19 20 21 22 23 24 25 26

  /**
   * The plugin manager for REST plugins.
   *
   * @var \Drupal\rest\Plugin\Type\ResourcePluginManager
   */
  protected $manager;

  /**
27
   * The REST resource config storage.
28
   *
29
   * @var \Drupal\Core\Entity\EntityManagerInterface
30
   */
31
  protected $resourceConfigStorage;
32

33 34 35 36 37 38 39
  /**
   * A logger instance.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

40 41 42 43 44
  /**
   * Constructs a RouteSubscriber object.
   *
   * @param \Drupal\rest\Plugin\Type\ResourcePluginManager $manager
   *   The resource plugin manager.
45 46
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager
47 48
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
49
   */
50
  public function __construct(ResourcePluginManager $manager, EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger) {
51
    $this->manager = $manager;
52
    $this->resourceConfigStorage = $entity_type_manager->getStorage('rest_resource_config');
53
    $this->logger = $logger;
54 55 56
  }

  /**
57
   * Alters existing routes for a specific collection.
58
   *
59 60
   * @param \Drupal\Core\Routing\RouteBuildEvent $event
   *   The route build event.
61
   * @return array
62
   */
63
  public function onDynamicRouteEvent(RouteBuildEvent $event) {
64
    // Iterate over all enabled REST resource config entities.
65 66 67
    /** @var \Drupal\rest\RestResourceConfigInterface[] $resource_configs */
    $resource_configs = $this->resourceConfigStorage->loadMultiple();
    foreach ($resource_configs as $resource_config) {
68 69
      if ($resource_config->status()) {
        $resource_routes = $this->getRoutesForResourceConfig($resource_config);
70
        $event->getRouteCollection()->addCollection($resource_routes);
71
      }
72
    }
73
  }
74

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
  /**
   * Provides all routes for a given REST resource config.
   *
   * This method determines where a resource is reachable, what path
   * replacements are used, the required HTTP method for the operation etc.
   *
   * @param \Drupal\rest\RestResourceConfigInterface $rest_resource_config
   *   The rest resource config.
   *
   * @return \Symfony\Component\Routing\RouteCollection
   *   The route collection.
   */
  protected function getRoutesForResourceConfig(RestResourceConfigInterface $rest_resource_config) {
    $plugin = $rest_resource_config->getResourcePlugin();
    $collection = new RouteCollection();

    foreach ($plugin->routes() as $name => $route) {
      /** @var \Symfony\Component\Routing\Route $route */
      // @todo: Are multiple methods possible here?
      $methods = $route->getMethods();
95 96 97 98 99
      // Only expose routes
      // - that have an explicit method and allow >=1 format for that method
      // - that exist for BC
      // @see \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC
      if (($methods && ($method = $methods[0]) && $supported_formats = $rest_resource_config->getFormats($method)) || $route->hasOption('bc_route')) {
100
        $route->setRequirement('_csrf_request_header_token', 'TRUE');
101 102 103

        // Check that authentication providers are defined.
        if (empty($rest_resource_config->getAuthenticationProviders($method))) {
104
          $this->logger->error('At least one authentication provider must be defined for resource @id', [':id' => $rest_resource_config->id()]);
105 106 107 108 109
          continue;
        }

        // Check that formats are defined.
        if (empty($rest_resource_config->getFormats($method))) {
110
          $this->logger->error('At least one format must be defined for resource @id', [':id' => $rest_resource_config->id()]);
111
          continue;
112
        }
113

114 115 116 117 118 119
        // Remove BC routes for unsupported formats.
        if ($route->getOption('bc_route') === TRUE) {
          $format_requirement = $route->getRequirement('_format');
          if ($format_requirement && !in_array($format_requirement, $rest_resource_config->getFormats($method))) {
            continue;
          }
120 121
        }

122
        // The configuration has been validated, so we update the route to:
123 124
        // - set the allowed response body content types/formats for methods
        //   that may send response bodies
125 126 127
        // - set the allowed request body content types/formats for methods that
        //   allow request bodies to be sent
        // - set the allowed authentication providers
128 129 130
        if (in_array($method, ['GET', 'HEAD', 'POST', 'PUT', 'PATCH'], TRUE)) {
          $route->addRequirements(['_format' => implode('|', $rest_resource_config->getFormats($method))]);
        }
131 132 133
        if (in_array($method, ['POST', 'PATCH', 'PUT'], TRUE)) {
          $route->addRequirements(['_content_type_format' => implode('|', $rest_resource_config->getFormats($method))]);
        }
134 135 136
        $route->setOption('_auth', $rest_resource_config->getAuthenticationProviders($method));
        $route->setDefault('_rest_resource_config', $rest_resource_config->id());
        $collection->add("rest.$name", $route);
137
      }
138

139
    }
140
    return $collection;
141 142
  }

143 144 145 146 147 148 149 150
  /**
   * {@inheritdoc}
   */
  public static function getSubscribedEvents() {
    $events[RoutingEvents::DYNAMIC] = 'onDynamicRouteEvent';
    return $events;
  }

151
}