I'm able to create get/post/delete methods but I don't know how to create a put/patch method.

There are no examples on this without using entities (my case). I have searched on Google and on Drupal source code without any results.

My problem is that I have this annotation:

/**
* Provides a resource for Device registration.
*
* @RestResource(
* id = "MY_RESOURCE_ID",
* label = @Translation("My resource"),
* serialization_class = "Drupal\my_module\Data\MyClass",
* uri_paths = {
* "canonical" = "/api/test/{id}",
* "https://www.drupal.org/link-relations/create" = "/api/test"
* }
* )
*/

public function put(MyClass $data) {
}

And when I use it like this for a PUT request:

{api_endpoint}/test/4?_format=json

I get this error:

TypeError: Argument 1 passed to Drupal\MY_RESOURCE::patch() must be an instance of Drupal\MyClass, string given in

If I change it to:

public function put($id) {
}

It works but that's not what I want because I need the data that it's passed on the request to update a row data on a custom table.

Comments

bdominguez created an issue. See original summary.

wim leers’s picture

Assigned: Unassigned » wim leers
Priority: Major » Normal
Status: Active » Postponed (maintainer needs more info)

It's very strange that your PUT request is being routed to ::patch(). That's definitely wrong. Is that indeed what is happening? Because the issue title says "PUT or PATCH", but then in the steps to reproduce, you say that "PUT" is mapped to the PHP method for "PATCH".

wim leers’s picture

Status: Postponed (maintainer needs more info) » Fixed

Oh wait, I see the problem. You have this as the route URL:

/api/test/{id}

This means that id is what the controller is expected to receive. This is why function patch($id) works, but public function patch(MyClass $data) { does not. The parameter must match the route slug ({id} here).

So you'd want to change it to:

/api/test/{my_class}

But then you'll also need to provide a paramconverter service for my_class
see \Drupal\Core\ParamConverter\ParamConverterInterface.

Once you do that, Drupal can connect the dots: it knows how to parse the ID from the URL and convert that to a parameter to pass to the controller.

wim leers’s picture

Note that Drupal core does not support PUT for entities for the reasons given in https://groups.drupal.org/node/284948 — it prefers PATCH instead. That post is linked from the official REST docs at https://www.drupal.org/docs/8/core/modules/rest/1-getting-started-rest-c....

wim leers’s picture

Assigned: wim leers » Unassigned
bdominguez’s picture

I'm able to use it with GET/POST/PATCH requests (the ParamConverterInterface code it's being executed) but not for PUT requests. It seems that the converter doesn't get processed on this request methods.

If I log in convert method the "array $defaults" parameter I see that Route object has this:

[methods:Symfony\Component\Routing\Route:private] => Array
(
[0] => GET
[1] => POST
)

[requirements:Symfony\Component\Routing\Route:private] => Array
(
[_method] => GET|POST
)

I have added in routing.yml for my custom route:

requirements:
_method: GET|POST|PUT|PATCH|DELETE

And now I see it like this:

[methods:Symfony\Component\Routing\Route:private] => Array
(
[0] => GET
[1] => POST
[2] => PUT
[3] => PATCH
[4] => DELETE
)

[requirements:Symfony\Component\Routing\Route:private] => Array
(
[_method] => GET|POST|PUT|PATCH|DELETE
)

But still doesn't get processed for PUT requests. Only for the other four.

Also, how can I read request data (body parameters) for PUT/PATCH requests in order to build my custom object class?

Thanks.

wim leers’s picture

I have added in routing.yml for my custom route:

You're not supposed to define a route. You're supposed to define a @RestResource plugin. You can then enable this REST resource (and automatically create a route) via https://www.drupal.org/project/restui

Also, how can I read request data (body parameters) for PUT/PATCH requests in order to build my custom object class?

An array $data parameter would work.

bdominguez’s picture

What is "array $data"??? In ParamConverterInterface there is an "array $defaults" only.

But you have said that I need to create a ParamConverterInterface. If I do this reading documentation I see that I need to create an entry on "*.routing.yml" defining it like this:

my_id:
path: '/api/device/{device_id}'
requirements:
_method: PATCH
options:
parameters:
device_id:
type: device

Create a Converter:

class DeviceConverter implements ParamConverterInterface {

  public function convert($value, $definition, $name, array $defaults) {
    return $data; // here I return my data class
  }

  public function applies($definition, $name, Route $route) {
    return (!empty($definition['type']) && $definition['type'] == 'device');
  }

}

Reference it in "*.services.yml":

device_converter:
class: Drupal\my_namespace\DeviceConverter
tags:
- { name: paramconverter }
lazy: true

I only want to convert that "device/{device_id}" to an object reading PATCH request body data to a custom object. I see that using paramconverts it's the way but if I don't see a complete example (It has been impossible to find one and I have searched Drupal source code).

wim leers’s picture

Sorry, this is what I meant:

Also, how can I read request data (body parameters) for PUT/PATCH requests in order to build my custom object class?

This would work:
function post(array $data) {

But you have said that I need to create a ParamConverterInterface. If I do this reading documentation I see that I need to create an entry on "*.routing.yml" defining it like this:


path: '/api/device/{device_id}'

This would need to be: path: '/api/device/{device}'. Just like e.g. the Node route is /node/{node}, not /node/{node_id}.

Also, you should NOT create a route, like I explained in #7. The ParamConverter docs you got that from are talking about the default/general routing case, but routes are generated automatically for REST resources.

Sorry that this is so confusing :( I can't help it either, I didn't design this system. I merely started maintaining it. Please help make the docs better!

bdominguez’s picture

But If I don't need to create a route how it's going to use my ParamConverter??? I need to tell somewhere that it needs to use it.

Also I don't understand this:

function post(array $data) {

I don't want that. You said that creating a ParamConverter I can receive as parameter my converter object (using a ParamConverter) directly, like this:

function post(Device $data) {

Also take in mind that I need it to PUT/PATCH not POST.

/colgate/api/device/{device_id}

I'm requesting it like this:

PATCH http://{host}/api/device/a921ebdd9d10f21602d949fc6edd6154280c590e?_format=json HTTP/1.1
Content-Type: application/json
Accept: application/json
X-CSRF-Token: c3o_rUqq9GHAdK9nXLb0g7YCpv8VY-SCt2QDtJPg95o

{"deviceId":"a921ebdd9d10f21602d949fc6edd6154280c590e","registrationId":"1234","platform":"android"}

And I want that JSON data to be serialized to a Device object and it's why I'm using a ParamConverter like you said to have a result like this:

function patch(Device $data) {
echo $data->deviceId;
echo $data->registrationId;
echo $data->platform;
}

wim leers’s picture

Status: Fixed » Active

But If I don't need to create a route how it's going to use my ParamConverter??? I need to tell somewhere that it needs to use it

If you want to specify route options to indicate which paramconverter to use, you can override \Drupal\rest\Plugin\ResourceBase::getBaseRoute() to specify that.


Also I don't understand this:

function post(array $data) {

I don't want that.

Sorry. I keep trying to help you, but you keep posting contradicting and incomplete information, which makes it very hard to help you.


What you want to achieve is totally reasonable. I can also see how it's simply too hard to do today. I'll work on adding test coverage that A) proves it's possible, and B) serves as an example. And in doing so, I should run into all the problems you're running into — it's quite likely there's several bugs hiding there!

bdominguez’s picture

What you want to achieve is totally reasonable. I can also see how it's simply too hard to do today. I'll work on adding test coverage that A) proves it's possible, and B) serves as an example. And in doing so, I should run into all the problems you're running into — it's quite likely there's several bugs hiding there!

Thanks! I'm looking forward to that example.

macherif’s picture

You can not use the PUT method because it's not allowed as the Documentation said:

Note: PUT is not supported for good reasons

You can checkout the right way at the official documentation.

macherif’s picture

Status: Active » Closed (works as designed)