Skip to content

Commit dd41289

Browse files
committed
Clean-up REST resource generator
1 parent a49f29b commit dd41289

File tree

3 files changed

+190
-481
lines changed

3 files changed

+190
-481
lines changed

templates/plugin/rest-resource/rest-resource.twig

Lines changed: 80 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,12 @@
22
33
namespace Drupal\{{ machine_name }}\Plugin\rest\resource;
44
5-
use Drupal\Component\Plugin\DependentPluginInterface;
6-
use Drupal\Core\Database\Connection;
7-
use Drupal\Core\Routing\BcRoute;
5+
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
86
use Drupal\rest\ModifiedResourceResponse;
97
use Drupal\rest\Plugin\ResourceBase;
108
use Drupal\rest\ResourceResponse;
119
use Psr\Log\LoggerInterface;
1210
use Symfony\Component\DependencyInjection\ContainerInterface;
13-
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
1411
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
1512
1613
/**
@@ -26,43 +23,49 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
2623
* )
2724
*
2825
* @DCG
29-
* This plugin exposes database records as REST resources. In order to enable it
30-
* import the resource configuration into active configuration storage. You may
31-
* find an example of such configuration in the following file:
26+
* The plugin exposes key-value records as REST resources. In order to enable it
27+
* import the resource configuration into active configuration storage. An
28+
* example of such configuration can be located in the following file:
3229
* core/modules/rest/config/optional/rest.resource.entity.node.yml.
33-
* Alternatively you can make use of REST UI module.
30+
* Alternatively you can enable it through admin interface provider by REST UI
31+
* module.
3432
* @see https://www.drupal.org/project/restui
35-
* For accessing Drupal entities through REST interface use
36-
* \Drupal\rest\Plugin\rest\resource\EntityResource plugin.
33+
*
34+
* @DCG
35+
* Notice that this plugin does not provide any validation for the data.
36+
* Consider creating custom normalizer to validate and normalize the incoming
37+
* data. It can be enabled in the plugin definition as follows.
38+
* @code
39+
* serialization_class = "Drupal\foo\MyDataStructure",
40+
* @endcode
41+
*
42+
* @DCG
43+
* For entities, it is recommended to use REST resource plugin provided by
44+
* Drupal core.
45+
* @see \Drupal\rest\Plugin\rest\resource\EntityResource
3746
*/
38-
class {{ class }} extends ResourceBase implements DependentPluginInterface {
47+
class {{ class }} extends ResourceBase {
3948
4049
/**
41-
* The database connection.
50+
* The key-value storage.
4251
*
43-
* @var \Drupal\Core\Database\Connection
52+
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
4453
*/
45-
protected $dbConnection;
54+
protected $storage;
4655
4756
/**
48-
* Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object.
49-
*
50-
* @param array $configuration
51-
* A configuration array containing information about the plugin instance.
52-
* @param string $plugin_id
53-
* The plugin_id for the plugin instance.
54-
* @param mixed $plugin_definition
55-
* The plugin implementation definition.
56-
* @param array $serializer_formats
57-
* The available serialization formats.
58-
* @param \Psr\Log\LoggerInterface $logger
59-
* A logger instance.
60-
* @param \Drupal\Core\Database\Connection $db_connection
61-
* The database connection.
57+
* {@inheritdoc}
6258
*/
63-
public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, Connection $db_connection) {
64-
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
65-
$this->dbConnection = $db_connection;
59+
public function __construct(
60+
array $configuration,
61+
$plugin_id,
62+
$plugin_definition,
63+
array $serializer_formats,
64+
LoggerInterface $logger,
65+
KeyValueFactoryInterface $keyValueFactory,
66+
) {
67+
parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger, $keyValueFactory);
68+
$this->storage = $keyValueFactory->get('{{ plugin_id }}');
6669
}
6770
6871
/**
@@ -75,88 +78,81 @@ class {{ class }} extends ResourceBase implements DependentPluginInterface {
7578
$plugin_definition,
7679
$container->getParameter('serializer.formats'),
7780
$container->get('logger.factory')->get('rest'),
78-
$container->get('database')
81+
$container->get('keyvalue')
7982
);
8083
}
8184
8285
/**
83-
* Responds to GET requests.
84-
*
85-
* @param int $id
86-
* The ID of the record.
86+
* Responds to POST requests and saves the new record.
8787
*
88-
* @return \Drupal\rest\ResourceResponse
89-
* The response containing the record.
88+
* @param array $data
89+
* Data to write into the database.
9090
*
91-
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
91+
* @return \Drupal\rest\ModifiedResourceResponse
92+
* The HTTP response object.
9293
*/
93-
public function get($id) {
94-
return new ResourceResponse($this->loadRecord($id));
94+
public function post(array $data) {
95+
$data['id'] = $this->getNextId();
96+
$this->storage->set($data['id'], $data);
97+
$this->logger->notice('Create new {{ plugin_label|lower }} record @id.');
98+
// Return the newly created record in the response body.
99+
return new ModifiedResourceResponse($data, 201);
95100
}
96101
97102
/**
98-
* Responds to POST requests and saves the new record.
103+
* Responds to GET requests.
99104
*
100-
* @param mixed $data
101-
* Data to write into the database.
105+
* @param int $id
106+
* The ID of the record.
102107
*
103-
* @return \Drupal\rest\ModifiedResourceResponse
104-
* The HTTP response object.
108+
* @return \Drupal\rest\ResourceResponse
109+
* The response containing the record.
105110
*/
106-
public function post($data) {
107-
108-
$this->validate($data);
109-
110-
$id = $this->dbConnection->insert('{{ plugin_id }}')
111-
->fields($data)
112-
->execute();
113-
114-
$this->logger->notice('New {{ plugin_label|lower }} record has been created.');
115-
116-
$created_record = $this->loadRecord($id);
117-
118-
// Return the newly created record in the response body.
119-
return new ModifiedResourceResponse($created_record, 201);
111+
public function get($id) {
112+
if (!$this->storage->has($id)) {
113+
throw new NotFoundHttpException();
114+
}
115+
$resource = $this->storage->get($id);
116+
return new ResourceResponse($resource);
120117
}
121118
122119
/**
123-
* Responds to entity PATCH requests.
120+
* Responds to PATCH requests.
124121
*
125122
* @param int $id
126123
* The ID of the record.
127-
* @param mixed $data
128-
* Data to write into the database.
124+
* @param array $data
125+
* Data to write into the storage.
129126
*
130127
* @return \Drupal\rest\ModifiedResourceResponse
131128
* The HTTP response object.
132129
*/
133-
public function patch($id, $data) {
134-
$this->validate($data);
135-
return $this->updateRecord($id, $data);
130+
public function patch($id, array $data) {
131+
if (!$this->storage->has($id)) {
132+
throw new NotFoundHttpException();
133+
}
134+
$stored_data = $this->storage->get($id);
135+
$data += $stored_data;
136+
$this->storage->set($id, $data);
137+
$this->logger->notice('The {{ plugin_label|lower }} record @id has been updated.');
138+
return new ModifiedResourceResponse($data, 200);
136139
}
137140
138141
/**
139-
* Responds to entity DELETE requests.
142+
* Responds to DELETE requests.
140143
*
141144
* @param int $id
142145
* The ID of the record.
143146
*
144147
* @return \Drupal\rest\ModifiedResourceResponse
145148
* The HTTP response object.
146-
*
147-
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
148149
*/
149150
public function delete($id) {
150-
151-
// Make sure the record still exists.
152-
$this->loadRecord($id);
153-
154-
$this->dbConnection->delete('{{ plugin_id }}')
155-
->condition('id', $id)
156-
->execute();
157-
158-
$this->logger->notice('{{ plugin_label }} record @id has been deleted.', ['@id' => $id]);
159-
151+
if (!$this->storage->has($id)) {
152+
throw new NotFoundHttpException();
153+
}
154+
$this->storage->delete($id);
155+
$this->logger->notice('The {{ plugin_label|lower }} record @id has been deleted.', ['@id' => $id]);
160156
// Deleted responses have an empty body.
161157
return new ModifiedResourceResponse(NULL, 204);
162158
}
@@ -166,119 +162,19 @@ class {{ class }} extends ResourceBase implements DependentPluginInterface {
166162
*/
167163
protected function getBaseRoute($canonical_path, $method) {
168164
$route = parent::getBaseRoute($canonical_path, $method);
169-
170-
// Change ID validation pattern.
165+
// Set ID validation pattern.
171166
if ($method != 'POST') {
172167
$route->setRequirement('id', '\d+');
173168
}
174-
175169
return $route;
176170
}
177171
178172
/**
179-
* {@inheritdoc}
180-
*/
181-
public function calculateDependencies() {
182-
return [];
183-
}
184-
185-
/**
186-
* {@inheritdoc}
187-
*/
188-
public function routes() {
189-
$collection = parent::routes();
190-
191-
// Take out BC routes added in base class.
192-
// @see https://www.drupal.org/node/2865645
193-
// @todo Remove this in Drupal 9.
194-
foreach ($collection as $route_name => $route) {
195-
if ($route instanceof BcRoute) {
196-
$collection->remove($route_name);
197-
}
198-
}
199-
200-
return $collection;
201-
}
202-
203-
/**
204-
* Validates incoming record.
205-
*
206-
* @param mixed $record
207-
* Data to validate.
208-
*
209-
* @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
210-
*/
211-
protected function validate($record) {
212-
if (!is_array($record) || count($record) == 0) {
213-
throw new BadRequestHttpException('No record content received.');
214-
}
215-
216-
$allowed_fields = [
217-
'title',
218-
'description',
219-
'price',
220-
];
221-
222-
if (count(array_diff(array_keys($record), $allowed_fields)) > 0) {
223-
throw new BadRequestHttpException('Record structure is not correct.');
224-
}
225-
226-
if (empty($record['title'])) {
227-
throw new BadRequestHttpException('Title is required.');
228-
}
229-
elseif (isset($record['title']) && strlen($record['title']) > 255) {
230-
throw new BadRequestHttpException('Title is too big.');
231-
}
232-
// @DCG Add more validation rules here.
233-
}
234-
235-
/**
236-
* Loads record from database.
237-
*
238-
* @param int $id
239-
* The ID of the record.
240-
*
241-
* @return array
242-
* The database record.
243-
*
244-
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
173+
* Returns next available ID.
245174
*/
246-
protected function loadRecord($id) {
247-
$record = $this->dbConnection->query('SELECT * FROM {{ '{' }}{{ plugin_id }}{{ '}' }} WHERE id = :id', [':id' => $id])->fetchAssoc();
248-
if (!$record) {
249-
throw new NotFoundHttpException('The record was not found.');
250-
}
251-
return $record;
252-
}
253-
254-
/**
255-
* Updates record.
256-
*
257-
* @param int $id
258-
* The ID of the record.
259-
* @param array $record
260-
* The record to validate.
261-
*
262-
* @return \Drupal\rest\ModifiedResourceResponse
263-
* The HTTP response object.
264-
*/
265-
protected function updateRecord($id, array $record) {
266-
267-
// Make sure the record already exists.
268-
$this->loadRecord($id);
269-
270-
$this->validate($record);
271-
272-
$this->dbConnection->update('{{ plugin_id }}')
273-
->fields($record)
274-
->condition('id', $id)
275-
->execute();
276-
277-
$this->logger->notice('{{ plugin_label }} record @id has been updated.', ['@id' => $id]);
278-
279-
// Return the updated record in the response body.
280-
$updated_record = $this->loadRecord($id);
281-
return new ModifiedResourceResponse($updated_record, 200);
175+
private function getNextId() {
176+
$ids = \array_keys($this->storage->getAll());
177+
return count($ids) > 0 ? max($ids) + 1 : 1;
282178
}
283179
284180
}

0 commit comments

Comments
 (0)