How to override a rest resource from Drupal core
Sometimes there are projects that tend to make us find new ways to do things. Those are the projects are really dear to us and to our developers. We had this project where a REST override was necessary, and as we found little to no resource on the matter, we’ve decided to document our trajectory towards a working solution.
Before proceeding, make yourself a cup of coffee and have a small bite… This may take a while. Ready, steady, go!
THE PREMISES OF A DRUPAL 8 OVERRIDE
First of all, you should understand that Drupal 8 handles all configuration in a unified way. More features are stored in the Drupal 8’s core than in any other Drupal version. This means that a wrong override of the core may result in irreparable damages and in a lot of frustration for any Drupal backend developer.
Drupal 8 allows us to use global $config overrides
, but the configuration will keep this overrides via a Drupal\Core\Config\ConfigFactory::get()
implementation. This happens in the case of global overrides and will return conditional override values for configuration.
Still, no word of REST resources or of core overrides. This is where the fun begins! Drupal 8 comes with a generic and rather basic REST resource for the system’s entities. This means that in order to do more complex actions with REST resources, a class extension should be firstly undergone.
HOW TO OVERRIDE A REST RESOURCE FROM DRUPAL CORE
To create a resource that extends a core functionality for a POST/PATCH, we can:
1. CREATE A NEW CLASS THAT EXTENDS THE “ENTITYRESOURCE” CLASS
This means that you will be able to use the REST functionalities provided by Drupal 8, while overriding when necessary, with no additional harm or information loss.
2. USE THE FOLLOWING ANNOTATIONS FOR THE NEWLY-CREATED CLASS:
If the resources will be used for Nodes, the annotation can be:
/**
* Provides a resource to get and patch asset type terms.
*
* @RestResource(
* id = "my_custom_resource",
* label = @Translation("My Custom Resource"),
* entity_type = "node",
* serialization_class = "Drupal\node\Entity\Node",
* uri_paths = {
* "canonical" = "/api/node/{node}",
* "https://www.drupal.org/link-relations/create" = "/api/node"
* }
* )
*/
Explanations:
"entity_type"
: represents the entity type to be used. In this case, the type will be “node”"serialization_class"
: the class that declares the Node entity"uri_paths"
: represents the endpoints for the new REST service"canonical"
: used for PATCH or DELETE- It is very important that the last element from the endpoint to correspond with the entity type, in our case with the "node"
"https://www.drupal.org/link-relations/create"
: URL to be used for the POST
If the resource is used for Taxonomy terms, the annotation can be:
/**
* Provides a resource to get and patch asset type terms.
*
* @RestResource(
* id = "my_custom_resource",
* label = @Translation("My Custom Resource"),
* entity_type = "taxonomy_term",
* serialization_class = "Drupal\taxonomy\Entity\Term",
* uri_paths = {
* "canonical" = "/api/taxonomy/{taxonomy_term}",
* "https://www.drupal.org/link-relations/create" = "/api/taxonomy"
* }
* )
*/
3. USE THE POST METHOD
This method allows the creation of new entities in the platform.
/**
* @inheritdoc
*/
public function post(EntityInterface $entity = NULL) {
parent::post($entity);
return new ResourceResponse($entity, 200);
}
This method is inherited from an extended class and then uses the functionality by calling parent::post($entity);
It enables us to do a set of modifications in the answer, as well as a set of entity validations or operations before or even after the entity has been saved.
To see the changes brought to the resources, open Postman and call a path in the form of: siteUrl/api/node?_format=json
4. USE THE PATCH METHOD
This method allows us to edit new entities that are already existing on the website. While it is somehow similar to the POST method, the PATCH has some differences you may want to pay attention to.
/**
* @inheritdoc
*/
public function patch(EntityInterface $original_entity, EntityInterface $entity = NULL)
{
parent::patch($original_entity, $entity);
return new ResourceResponse($bundle, 200);
}
5. EXAMPLE TIME!
To better understand all the explanation above, here’s an example for a NODE entity. After reading or using this example, you may want to try to create your own example for a Taxonomy entity, just to see if you have understood the ideas we’ve put forward.
namespace Drupal\project_name\Plugin\rest\resource;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\rest\Plugin\rest\resource\EntityResource;
use Drupal\rest\ResourceResponse;
use Psr\Log\LoggerInterface;
/**
* Provides a resource to get and patch asset type terms
*
* @RestResource(
* id = "my_custom_resource",
* label = @Translation("My Custom Resource"),
* entity_type = "node",
* serialization_class = "Drupal\node\Entity\Node",
* uri_paths = {
* "canonical" = "/api/node/{node}",
* "https://www.drupal.org/link-relations/create" = "/api/node"
* }
* )
*/
class Custom extends EntityResource {
/**
* Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param array $serializer_formats
* The available serialization formats.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory.
* @param \Drupal\Component\Plugin\PluginManagerInterface $link_relation_type_manager
* The link relation type manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, array $serializer_formats, LoggerInterface $logger, ConfigFactoryInterface $config_factory, PluginManagerInterface $link_relation_type_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $serializer_formats, $logger, $config_factory, $link_relation_type_manager);
}
/**
* Responds to POST requests.
*
* @param \Drupal\Core\Entity\EntityInterface|null $entity
* The entity.
*/
public function post(EntityInterface $entity = NULL) {
parent::post($entity);
return new ResourceResponse($entity);
}
/**
* Responds to PATCH requests.
*
* @param \Drupal\Core\Entity\EntityInterface $original_entity
* The original entity.
* @param \Drupal\Core\Entity\EntityInterface|null $entity
* The current entity.
*/
public function patch(EntityInterface $original_entity, EntityInterface $entity = NULL) {
parent::patch($original_entity, $entity);
return new ResourceResponse($entity, 200);
}
}
Hope this works for you. Either way, let us know your thoughts on the matter, in the comment section below.