6

I am trying to build simple search autocomplete POST endpoint, that accepts JSON and outputs JSON, but I am having problems getting Drupal 8 to just accept plain JSON.

Currently I am getting a 404 error for that endpoint, even though it is enabled in REST UI and I have permission. If I add serialization_class with value Drupal\node\Entity\Node to the class comment, I get 415 error, because it expects a node. Okay, great. How do I actually make it accept JSON? Do I really need to make an Entity type just for my autocomplete search?

Here is the code I have in mymodule/src/Plugin/rest/resource/SearchAutocompleteResource.php.

namespace Drupal\MyModule\Plugin\rest\resource;

use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\node\Entity\Node;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Provides a resource for search autocomplete.
 *
 * @RestResource(
 *   id = "mymodule:searchautocomplete",
 *   label = @Translation("MyModule: Search Autocomplete"),
 *   uri_paths = {
 *     "canonical" = "/api/v1/search-autocomplete",
 *     "https://www.drupal.org/link-relations/create" = "/api/v1/search-autocomplete"
 *   }
 * )
 */
class SearchAutocompleteResource extends ResourceBase {

  public function post($search = null) {

    return new ResourceResponse(array(
      'example' => 'return value'
    ));

  }

}
7

Up until Make serialization_class optional (#2419825) issue has been fixed for 8.2.x branch you couldn't get away without creating a custom serialization class which merely passes the provided data to satisfy the requirement for mandatory serialization.

If you need to make it work on Drupal <= 8.1, try the following:

  1. Create a custom normalizer.
  2. Expose it to the system as a service.
  3. Use it as serialization_class for your Rest resource.

modules/mymodule/src/normalizer/JsonDenormalizer.php:

<?php

namespace Drupal\mymodule\normalizer;

use Drupal\serialization\Normalizer\NormalizerBase;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

/**
 * Class JsonDenormalizer
 *
 * @todo Remove this class once https://www.drupal.org/node/2419825 is fixed.
 */
class JsonDenormalizer extends NormalizerBase implements DenormalizerInterface {

  /**
   * The interface or class that this Normalizer supports.
   *
   * @var array
   */
  protected $supportedInterfaceOrClass = array(__CLASS__);

  /**
   * {@inheritdoc}
   */
  public function normalize($object, $format = null, array $context = array()) {
    return parent::normalize($object, $format, $context);
  }

  /**
   * {@inheritdoc}
   */
  public function denormalize($data, $class, $format = NULL, array $context  = array()) {
    return $data;
  }
}

modules/mymodule/mymodule.services.yml:

services:
  # can be removed once https://www.drupal.org/node/2419825 is fixed.
  serializer.normalizer.mymodule.json:
    class: Drupal\mymodule\normalizer\JsonDenormalizer
    tags:
      - { name: normalizer }

mymodule/src/Plugin/rest/resource/SearchAutocompleteResource.php:

/**
 * Provides a resource for search autocomplete.
 *
 * @RestResource(
 *   id = "mymodule:searchautocomplete",
 *   label = @Translation("MyModule: Search Autocomplete"),
 *   serialization_class = "Drupal\mymodule\normalizer\JsonDenormalizer",
 *   uri_paths = {
 *     "canonical" = "/api/v1/search-autocomplete",
 *     "https://www.drupal.org/link-relations/create" = "/api/v1/search-autocomplete"
 *   }
 * )
 */
  • Another option for you is to just create a custom controller and return autocomplete values from there. Rest resource plugin is just one (and fancy) way of doing it. – maijs Jul 5 '16 at 13:21
  • Hmm, could there be a typo somewhere in your code, I get this error in the response: <br /> <b>Fatal error</b>: Class 'Drupal\mymodule\normalizer\JsonDenormalizer' not found in <b>/srv/www/drupal8/core/lib/Drupal/Component/DependencyInjection/Container.php</b> on line <b>264</b><br /> The file is mymodule/src/normalizer/JsonDenormalizer.php, should be correct – Risse Jul 5 '16 at 13:39
  • I added <?php for JsonDenormalizer class but other than that I believe the code is correct. I created a module mymodule with the provided code and REST client returned the correct JSON from your SearchAutocompleteResource class. – maijs Jul 5 '16 at 14:17
  • My bad, I renamed mymodule to my module name with uppercase letters and looks like that is not allowed. Now it works perfectly, thanks a lot! – Risse Jul 5 '16 at 14:30
3

The answer supplied by @maijs is correct for Drupal 8.1.x and lower, but for 8.2.x it's so much easier, because you don't even have to think about the normalizer. Here's an example of my_module/src/Plugin/rest/resource/MyRestResource.php

<?php

namespace Drupal\my_module\Plugin\rest\resource;

use Drupal\Core\Session\AccountProxyInterface;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Psr\Log\LoggerInterface;

/**
 * Provides a resource to get view modes by entity and bundle.
 *
 * @RestResource(
 *   id = "my_rest_resource",
 *   label = @Translation("My rest resource"),
 *   serialization_class = "",
 *   uri_paths = {
 *     "canonical" = "/my_rest_resource"
 *   }
 * )
 */
class MyRestResource extends ResourceBase {

  /**
   * A current user instance.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * Constructs a Drupal\rest\Plugin\ResourceBase 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 array $serializer_formats
   *   The available serialization formats.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   A current user instance.
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    array $serializer_formats,
    LoggerInterface $logger,
    AccountProxyInterface $current_user) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);

    $this->currentUser = $current_user;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->getParameter('serializer.formats'),
      $container->get('logger.factory')->get('my_module'),
      $container->get('current_user')
    );
  }

  /**
   * Responds to GET requests.
   *
   * Returns a list of bundles for specified entity.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   */
  public function get() {
    $response = 'hello world';
    return new ResourceResponse($response);
  }

  public function post(array $data = []) {
    $response = array(
      "hello_world" => $data,
    );
    return new ResourceResponse($response);
  }

  public function patch($arg) {
    return new ResourceResponse('hello patch');
  }

}

The key here is post(array $data = []) near the bottom of the file. In 8.1.x that was impossible, but in 8.2.x that's all it takes to convert post data to an array. Here's how it looks when you request that URL with postman:

enter image description here

1

This is what I did to receive a JSON object from a post.

use Symfony\Component\HttpFoundation\Request;

class AjaxController extends ControllerBase {
    public function save(Request $request) {
        $object = json_decode($request->getContent());
  • Could you provide an extended example, including routing settings and (possibly) example code? – maijs Jul 19 '16 at 11:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.