annotate core/modules/rest/src/Plugin/ResourceBase.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 1fec387a4317
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\rest\Plugin;
Chris@0 4
Chris@0 5 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
Chris@0 6 use Drupal\Core\Plugin\PluginBase;
Chris@14 7 use Drupal\Core\Routing\BcRoute;
Chris@0 8 use Psr\Log\LoggerInterface;
Chris@0 9 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 10 use Symfony\Component\Routing\Route;
Chris@0 11 use Symfony\Component\Routing\RouteCollection;
Chris@0 12
Chris@0 13 /**
Chris@0 14 * Common base class for resource plugins.
Chris@0 15 *
Chris@0 16 * Note that this base class' implementation of the permissions() method
Chris@0 17 * generates a permission for every method for a resource. If your resource
Chris@0 18 * already has its own access control mechanism, you should opt out from this
Chris@0 19 * default permissions() method by overriding it.
Chris@0 20 *
Chris@0 21 * @see \Drupal\rest\Annotation\RestResource
Chris@0 22 * @see \Drupal\rest\Plugin\Type\ResourcePluginManager
Chris@0 23 * @see \Drupal\rest\Plugin\ResourceInterface
Chris@0 24 * @see plugin_api
Chris@0 25 *
Chris@0 26 * @ingroup third_party
Chris@0 27 */
Chris@0 28 abstract class ResourceBase extends PluginBase implements ContainerFactoryPluginInterface, ResourceInterface {
Chris@0 29
Chris@0 30 /**
Chris@0 31 * The available serialization formats.
Chris@0 32 *
Chris@0 33 * @var array
Chris@0 34 */
Chris@0 35 protected $serializerFormats = [];
Chris@0 36
Chris@0 37 /**
Chris@0 38 * A logger instance.
Chris@0 39 *
Chris@0 40 * @var \Psr\Log\LoggerInterface
Chris@0 41 */
Chris@0 42 protected $logger;
Chris@0 43
Chris@0 44 /**
Chris@0 45 * Constructs a Drupal\rest\Plugin\ResourceBase object.
Chris@0 46 *
Chris@0 47 * @param array $configuration
Chris@0 48 * A configuration array containing information about the plugin instance.
Chris@0 49 * @param string $plugin_id
Chris@0 50 * The plugin_id for the plugin instance.
Chris@0 51 * @param mixed $plugin_definition
Chris@0 52 * The plugin implementation definition.
Chris@0 53 * @param array $serializer_formats
Chris@0 54 * The available serialization formats.
Chris@0 55 * @param \Psr\Log\LoggerInterface $logger
Chris@0 56 * A logger instance.
Chris@0 57 */
Chris@0 58 public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger) {
Chris@0 59 parent::__construct($configuration, $plugin_id, $plugin_definition);
Chris@0 60 $this->serializerFormats = $serializer_formats;
Chris@0 61 $this->logger = $logger;
Chris@0 62 }
Chris@0 63
Chris@0 64 /**
Chris@0 65 * {@inheritdoc}
Chris@0 66 */
Chris@0 67 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
Chris@0 68 return new static(
Chris@0 69 $configuration,
Chris@0 70 $plugin_id,
Chris@0 71 $plugin_definition,
Chris@0 72 $container->getParameter('serializer.formats'),
Chris@0 73 $container->get('logger.factory')->get('rest')
Chris@0 74 );
Chris@0 75 }
Chris@0 76
Chris@0 77 /**
Chris@0 78 * Implements ResourceInterface::permissions().
Chris@0 79 *
Chris@0 80 * Every plugin operation method gets its own user permission. Example:
Chris@0 81 * "restful delete entity:node" with the title "Access DELETE on Node
Chris@0 82 * resource".
Chris@0 83 */
Chris@0 84 public function permissions() {
Chris@0 85 $permissions = [];
Chris@0 86 $definition = $this->getPluginDefinition();
Chris@0 87 foreach ($this->availableMethods() as $method) {
Chris@0 88 $lowered_method = strtolower($method);
Chris@0 89 $permissions["restful $lowered_method $this->pluginId"] = [
Chris@0 90 'title' => $this->t('Access @method on %label resource', ['@method' => $method, '%label' => $definition['label']]),
Chris@0 91 ];
Chris@0 92 }
Chris@0 93 return $permissions;
Chris@0 94 }
Chris@0 95
Chris@0 96 /**
Chris@0 97 * {@inheritdoc}
Chris@0 98 */
Chris@0 99 public function routes() {
Chris@0 100 $collection = new RouteCollection();
Chris@0 101
Chris@0 102 $definition = $this->getPluginDefinition();
Chris@0 103 $canonical_path = isset($definition['uri_paths']['canonical']) ? $definition['uri_paths']['canonical'] : '/' . strtr($this->pluginId, ':', '/') . '/{id}';
Chris@0 104 $create_path = isset($definition['uri_paths']['create']) ? $definition['uri_paths']['create'] : '/' . strtr($this->pluginId, ':', '/');
Chris@0 105 // BC: the REST module originally created the POST URL for a resource by
Chris@0 106 // reading the 'https://www.drupal.org/link-relations/create' URI path from
Chris@0 107 // the plugin annotation. For consistency with entity type definitions, that
Chris@0 108 // then changed to reading the 'create' URI path. For any REST Resource
Chris@0 109 // plugins that were using the old mechanism, we continue to support that.
Chris@0 110 if (!isset($definition['uri_paths']['create']) && isset($definition['uri_paths']['https://www.drupal.org/link-relations/create'])) {
Chris@0 111 $create_path = $definition['uri_paths']['https://www.drupal.org/link-relations/create'];
Chris@0 112 }
Chris@0 113
Chris@0 114 $route_name = strtr($this->pluginId, ':', '.');
Chris@0 115
Chris@0 116 $methods = $this->availableMethods();
Chris@0 117 foreach ($methods as $method) {
Chris@14 118 $path = $method === 'POST'
Chris@14 119 ? $create_path
Chris@14 120 : $canonical_path;
Chris@14 121 $route = $this->getBaseRoute($path, $method);
Chris@0 122
Chris@14 123 // Note that '_format' and '_content_type_format' route requirements are
Chris@14 124 // added in ResourceRoutes::getRoutesForResourceConfig().
Chris@14 125 $collection->add("$route_name.$method", $route);
Chris@0 126
Chris@14 127 // BC: the REST module originally created per-format GET routes, instead
Chris@14 128 // of a single route. To minimize the surface of this BC layer, this uses
Chris@14 129 // route definitions that are as empty as possible, plus an outbound route
Chris@14 130 // processor.
Chris@14 131 // @see \Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC
Chris@14 132 if ($method === 'GET' || $method === 'HEAD') {
Chris@14 133 foreach ($this->serializerFormats as $format_name) {
Chris@14 134 $collection->add("$route_name.$method.$format_name", (new BcRoute())->setRequirement('_format', $format_name));
Chris@14 135 }
Chris@0 136 }
Chris@0 137 }
Chris@0 138
Chris@0 139 return $collection;
Chris@0 140 }
Chris@0 141
Chris@0 142 /**
Chris@0 143 * Provides predefined HTTP request methods.
Chris@0 144 *
Chris@0 145 * Plugins can override this method to provide additional custom request
Chris@0 146 * methods.
Chris@0 147 *
Chris@0 148 * @return array
Chris@0 149 * The list of allowed HTTP request method strings.
Chris@0 150 */
Chris@0 151 protected function requestMethods() {
Chris@0 152 return [
Chris@0 153 'HEAD',
Chris@0 154 'GET',
Chris@0 155 'POST',
Chris@0 156 'PUT',
Chris@0 157 'DELETE',
Chris@0 158 'TRACE',
Chris@0 159 'OPTIONS',
Chris@0 160 'CONNECT',
Chris@0 161 'PATCH',
Chris@0 162 ];
Chris@0 163 }
Chris@0 164
Chris@0 165 /**
Chris@0 166 * {@inheritdoc}
Chris@0 167 */
Chris@0 168 public function availableMethods() {
Chris@0 169 $methods = $this->requestMethods();
Chris@0 170 $available = [];
Chris@0 171 foreach ($methods as $method) {
Chris@0 172 // Only expose methods where the HTTP request method exists on the plugin.
Chris@0 173 if (method_exists($this, strtolower($method))) {
Chris@0 174 $available[] = $method;
Chris@0 175 }
Chris@0 176 }
Chris@0 177 return $available;
Chris@0 178 }
Chris@0 179
Chris@0 180 /**
Chris@0 181 * Gets the base route for a particular method.
Chris@0 182 *
Chris@0 183 * @param string $canonical_path
Chris@0 184 * The canonical path for the resource.
Chris@0 185 * @param string $method
Chris@0 186 * The HTTP method to be used for the route.
Chris@0 187 *
Chris@0 188 * @return \Symfony\Component\Routing\Route
Chris@0 189 * The created base route.
Chris@0 190 */
Chris@0 191 protected function getBaseRoute($canonical_path, $method) {
Chris@0 192 return new Route($canonical_path, [
Chris@0 193 '_controller' => 'Drupal\rest\RequestHandler::handle',
Chris@0 194 ],
Chris@0 195 $this->getBaseRouteRequirements($method),
Chris@0 196 [],
Chris@0 197 '',
Chris@0 198 [],
Chris@0 199 // The HTTP method is a requirement for this route.
Chris@0 200 [$method]
Chris@0 201 );
Chris@0 202 }
Chris@0 203
Chris@0 204 /**
Chris@0 205 * Gets the base route requirements for a particular method.
Chris@0 206 *
Chris@0 207 * @param $method
Chris@0 208 * The HTTP method to be used for the route.
Chris@0 209 *
Chris@0 210 * @return array
Chris@0 211 * An array of requirements for parameters.
Chris@0 212 */
Chris@0 213 protected function getBaseRouteRequirements($method) {
Chris@0 214 $lower_method = strtolower($method);
Chris@0 215 // Every route MUST have requirements that result in the access manager
Chris@0 216 // having access checks to check. If it does not, the route is made
Chris@0 217 // inaccessible. So, we default to granting access to everyone. If a
Chris@0 218 // permission exists, then we add that below. The access manager requires
Chris@0 219 // that ALL access checks must grant access, so this still results in
Chris@0 220 // correct behavior.
Chris@0 221 $requirements = [
Chris@0 222 '_access' => 'TRUE',
Chris@0 223 ];
Chris@0 224
Chris@0 225 // Only specify route requirements if the default permission exists. For any
Chris@0 226 // more advanced route definition, resource plugins extending this base
Chris@0 227 // class must override this method.
Chris@0 228 $permission = "restful $lower_method $this->pluginId";
Chris@0 229 if (isset($this->permissions()[$permission])) {
Chris@0 230 $requirements['_permission'] = $permission;
Chris@0 231 }
Chris@0 232
Chris@0 233 return $requirements;
Chris@0 234 }
Chris@0 235
Chris@0 236 }