annotate vendor/chi-teck/drupal-code-generator/templates/d8/plugin/rest-resource.twig @ 4:a9cd425dd02b

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:11:55 +0000
parents c75dbcec494b
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\{{ machine_name }}\Plugin\rest\resource;
Chris@0 4
Chris@0 5 use Drupal\Component\Plugin\DependentPluginInterface;
Chris@0 6 use Drupal\Core\Database\Connection;
Chris@4 7 use Drupal\Core\Routing\BcRoute;
Chris@0 8 use Drupal\rest\ModifiedResourceResponse;
Chris@0 9 use Drupal\rest\Plugin\ResourceBase;
Chris@0 10 use Drupal\rest\ResourceResponse;
Chris@0 11 use Psr\Log\LoggerInterface;
Chris@0 12 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 13 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
Chris@0 14 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
Chris@0 15
Chris@0 16 /**
Chris@0 17 * Represents {{ plugin_label }} records as resources.
Chris@0 18 *
Chris@0 19 * @RestResource (
Chris@0 20 * id = "{{ plugin_id }}",
Chris@0 21 * label = @Translation("{{ plugin_label }}"),
Chris@0 22 * uri_paths = {
Chris@0 23 * "canonical" = "/api/{{ plugin_id|u2h }}/{id}",
Chris@4 24 * "https://www.drupal.org/link-relations/create" = "/api/{{ plugin_id|u2h }}"
Chris@0 25 * }
Chris@0 26 * )
Chris@0 27 *
Chris@0 28 * @DCG
Chris@0 29 * This plugin exposes database records as REST resources. In order to enable it
Chris@0 30 * import the resource configuration into active configuration storage. You may
Chris@0 31 * find an example of such configuration in the following file:
Chris@0 32 * core/modules/rest/config/optional/rest.resource.entity.node.yml.
Chris@0 33 * Alternatively you can make use of REST UI module.
Chris@0 34 * @see https://www.drupal.org/project/restui
Chris@0 35 * For accessing Drupal entities through REST interface use
Chris@0 36 * \Drupal\rest\Plugin\rest\resource\EntityResource plugin.
Chris@0 37 */
Chris@0 38 class {{ class }} extends ResourceBase implements DependentPluginInterface {
Chris@0 39
Chris@0 40 /**
Chris@0 41 * The database connection.
Chris@0 42 *
Chris@0 43 * @var \Drupal\Core\Database\Connection
Chris@0 44 */
Chris@0 45 protected $dbConnection;
Chris@0 46
Chris@0 47 /**
Chris@0 48 * Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object.
Chris@0 49 *
Chris@0 50 * @param array $configuration
Chris@0 51 * A configuration array containing information about the plugin instance.
Chris@0 52 * @param string $plugin_id
Chris@0 53 * The plugin_id for the plugin instance.
Chris@0 54 * @param mixed $plugin_definition
Chris@0 55 * The plugin implementation definition.
Chris@0 56 * @param array $serializer_formats
Chris@0 57 * The available serialization formats.
Chris@0 58 * @param \Psr\Log\LoggerInterface $logger
Chris@0 59 * A logger instance.
Chris@0 60 * @param \Drupal\Core\Database\Connection $db_connection
Chris@0 61 * The database connection.
Chris@0 62 */
Chris@0 63 public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, Connection $db_connection) {
Chris@0 64 parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
Chris@0 65 $this->dbConnection = $db_connection;
Chris@0 66 }
Chris@0 67
Chris@0 68 /**
Chris@0 69 * {@inheritdoc}
Chris@0 70 */
Chris@0 71 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
Chris@0 72 return new static(
Chris@0 73 $configuration,
Chris@0 74 $plugin_id,
Chris@0 75 $plugin_definition,
Chris@0 76 $container->getParameter('serializer.formats'),
Chris@0 77 $container->get('logger.factory')->get('rest'),
Chris@0 78 $container->get('database')
Chris@0 79 );
Chris@0 80 }
Chris@0 81
Chris@0 82 /**
Chris@0 83 * Responds to GET requests.
Chris@0 84 *
Chris@0 85 * @param int $id
Chris@0 86 * The ID of the record.
Chris@0 87 *
Chris@0 88 * @return \Drupal\rest\ResourceResponse
Chris@0 89 * The response containing the record.
Chris@0 90 *
Chris@0 91 * @throws \Symfony\Component\HttpKernel\Exception\HttpException
Chris@0 92 */
Chris@0 93 public function get($id) {
Chris@0 94 return new ResourceResponse($this->loadRecord($id));
Chris@0 95 }
Chris@0 96
Chris@0 97 /**
Chris@0 98 * Responds to POST requests and saves the new record.
Chris@0 99 *
Chris@0 100 * @param mixed $record
Chris@0 101 * Data to write into the database.
Chris@0 102 *
Chris@0 103 * @return \Drupal\rest\ModifiedResourceResponse
Chris@0 104 * The HTTP response object.
Chris@0 105 */
Chris@0 106 public function post($record) {
Chris@0 107
Chris@0 108 $this->validate($record);
Chris@0 109
Chris@0 110 $id = $this->dbConnection->insert('{{ plugin_id }}')
Chris@0 111 ->fields($record)
Chris@0 112 ->execute();
Chris@0 113
Chris@0 114 $this->logger->notice('New {{ plugin_label|lower }} record has been created.');
Chris@0 115
Chris@0 116 $created_record = $this->loadRecord($id);
Chris@0 117
Chris@0 118 // Return the newly created record in the response body.
Chris@0 119 return new ModifiedResourceResponse($created_record, 201);
Chris@0 120 }
Chris@0 121
Chris@0 122 /**
Chris@0 123 * Responds to entity PATCH requests.
Chris@0 124 *
Chris@0 125 * @param int $id
Chris@0 126 * The ID of the record.
Chris@0 127 * @param mixed $record
Chris@0 128 * Data to write into the database.
Chris@0 129 *
Chris@0 130 * @return \Drupal\rest\ModifiedResourceResponse
Chris@0 131 * The HTTP response object.
Chris@0 132 */
Chris@0 133 public function patch($id, $record) {
Chris@0 134 $this->validate($record);
Chris@0 135 return $this->updateRecord($id, $record);
Chris@0 136 }
Chris@0 137
Chris@0 138 /**
Chris@0 139 * Responds to entity PUT requests.
Chris@0 140 *
Chris@0 141 * @param int $id
Chris@0 142 * The ID of the record.
Chris@0 143 * @param mixed $record
Chris@0 144 * Data to write into the database.
Chris@0 145 *
Chris@0 146 * @return \Drupal\rest\ModifiedResourceResponse
Chris@0 147 * The HTTP response object.
Chris@0 148 */
Chris@0 149 public function put($id, $record) {
Chris@0 150
Chris@0 151 $this->validate($record);
Chris@0 152
Chris@0 153 // Provide default values to make sure the record is completely replaced.
Chris@0 154 $record += [
Chris@0 155 'title' => '',
Chris@0 156 'description' => '',
Chris@0 157 'price' => 0,
Chris@0 158 ];
Chris@0 159
Chris@0 160 return $this->updateRecord($id, $record);
Chris@0 161 }
Chris@0 162
Chris@0 163 /**
Chris@0 164 * Responds to entity DELETE requests.
Chris@0 165 *
Chris@0 166 * @param int $id
Chris@0 167 * The ID of the record.
Chris@0 168 *
Chris@0 169 * @return \Drupal\rest\ModifiedResourceResponse
Chris@0 170 * The HTTP response object.
Chris@0 171 *
Chris@0 172 * @throws \Symfony\Component\HttpKernel\Exception\HttpException
Chris@0 173 */
Chris@0 174 public function delete($id) {
Chris@0 175
Chris@0 176 // Make sure the record still exists.
Chris@0 177 $this->loadRecord($id);
Chris@0 178
Chris@0 179 $this->dbConnection->delete('{{ plugin_id }}')
Chris@0 180 ->condition('id', $id)
Chris@0 181 ->execute();
Chris@0 182
Chris@0 183 $this->logger->notice('{{ plugin_label }} record @id has been deleted.', ['@id' => $id]);
Chris@0 184
Chris@0 185 // Deleted responses have an empty body.
Chris@0 186 return new ModifiedResourceResponse(NULL, 204);
Chris@0 187 }
Chris@0 188
Chris@0 189 /**
Chris@0 190 * {@inheritdoc}
Chris@0 191 */
Chris@0 192 protected function getBaseRoute($canonical_path, $method) {
Chris@0 193 $route = parent::getBaseRoute($canonical_path, $method);
Chris@0 194
Chris@0 195 // Change ID validation pattern.
Chris@0 196 if ($method != 'POST') {
Chris@0 197 $route->setRequirement('id', '\d+');
Chris@0 198 }
Chris@0 199
Chris@0 200 return $route;
Chris@0 201 }
Chris@0 202
Chris@0 203 /**
Chris@0 204 * {@inheritdoc}
Chris@0 205 */
Chris@0 206 public function calculateDependencies() {
Chris@0 207 return [];
Chris@0 208 }
Chris@0 209
Chris@0 210 /**
Chris@0 211 * {@inheritdoc}
Chris@0 212 */
Chris@0 213 public function routes() {
Chris@0 214 $collection = parent::routes();
Chris@0 215
Chris@0 216 // ResourceBase class does not support PUT method by some reason.
Chris@0 217 $definition = $this->getPluginDefinition();
Chris@0 218 $canonical_path = $definition['uri_paths']['canonical'];
Chris@0 219 $route = $this->getBaseRoute($canonical_path, 'PUT');
Chris@0 220 $route->addRequirements(['_content_type_format' => implode('|', $this->serializerFormats)]);
Chris@0 221 $collection->add('{{ plugin_id }}.PUT', $route);
Chris@0 222
Chris@4 223 // Take out BC routes added in base class.
Chris@4 224 // @see https://www.drupal.org/node/2865645
Chris@4 225 // @todo Remove this in Drupal 9.
Chris@4 226 foreach ($collection as $route_name => $route) {
Chris@4 227 if ($route instanceof BcRoute) {
Chris@4 228 $collection->remove($route_name);
Chris@4 229 }
Chris@4 230 }
Chris@4 231
Chris@0 232 return $collection;
Chris@0 233 }
Chris@0 234
Chris@0 235 /**
Chris@0 236 * Validates incoming record.
Chris@0 237 *
Chris@0 238 * @param mixed $record
Chris@0 239 * Data to validate.
Chris@0 240 *
Chris@0 241 * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
Chris@0 242 */
Chris@0 243 protected function validate($record) {
Chris@0 244 if (!is_array($record) || count($record) == 0) {
Chris@0 245 throw new BadRequestHttpException('No record content received.');
Chris@0 246 }
Chris@0 247
Chris@0 248 $allowed_fields = [
Chris@0 249 'title',
Chris@0 250 'description',
Chris@0 251 'price',
Chris@0 252 ];
Chris@0 253
Chris@0 254 if (count(array_diff(array_keys($record), $allowed_fields)) > 0) {
Chris@0 255 throw new BadRequestHttpException('Record structure is not correct.');
Chris@0 256 }
Chris@0 257
Chris@0 258 if (empty($record['title'])) {
Chris@0 259 throw new BadRequestHttpException('Title is required.');
Chris@0 260 }
Chris@0 261 elseif (isset($record['title']) && strlen($record['title']) > 255) {
Chris@0 262 throw new BadRequestHttpException('Title is too big.');
Chris@0 263 }
Chris@0 264 // @DCG Add more validation rules here.
Chris@0 265 }
Chris@0 266
Chris@0 267 /**
Chris@0 268 * Loads record from database.
Chris@0 269 *
Chris@0 270 * @param int $id
Chris@0 271 * The ID of the record.
Chris@0 272 *
Chris@0 273 * @return array
Chris@0 274 * The database record.
Chris@0 275 *
Chris@0 276 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
Chris@0 277 */
Chris@0 278 protected function loadRecord($id) {
Chris@0 279 $record = $this->dbConnection->query('SELECT * FROM {{ '{' }}{{ plugin_id }}{{ '}' }} WHERE id = :id', [':id' => $id])->fetchAssoc();
Chris@0 280 if (!$record) {
Chris@0 281 throw new NotFoundHttpException('The record was not found.');
Chris@0 282 }
Chris@0 283 return $record;
Chris@0 284 }
Chris@0 285
Chris@0 286 /**
Chris@0 287 * Updates record.
Chris@0 288 *
Chris@0 289 * @param int $id
Chris@0 290 * The ID of the record.
Chris@0 291 * @param array $record
Chris@0 292 * The record to validate.
Chris@0 293 *
Chris@0 294 * @return \Drupal\rest\ModifiedResourceResponse
Chris@0 295 * The HTTP response object.
Chris@0 296 */
Chris@0 297 protected function updateRecord($id, array $record) {
Chris@0 298
Chris@0 299 // Make sure the record already exists.
Chris@0 300 $this->loadRecord($id);
Chris@0 301
Chris@0 302 $this->validate($record);
Chris@0 303
Chris@0 304 $this->dbConnection->update('{{ plugin_id }}')
Chris@0 305 ->fields($record)
Chris@0 306 ->condition('id', $id)
Chris@0 307 ->execute();
Chris@0 308
Chris@0 309 $this->logger->notice('{{ plugin_label }} record @id has been updated.', ['@id' => $id]);
Chris@0 310
Chris@0 311 // Return the updated record in the response body.
Chris@0 312 $updated_record = $this->loadRecord($id);
Chris@0 313 return new ModifiedResourceResponse($updated_record, 200);
Chris@0 314 }
Chris@0 315
Chris@0 316 }