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
Comment #2
wim leersIt's very strange that your
PUTrequest 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".Comment #3
wim leersOh wait, I see the problem. You have this as the route URL:
This means that
idis what the controller is expected to receive. This is whyfunction patch($id)works, butpublic function patch(MyClass $data) {does not. The parameter must match the route slug ({id}here).So you'd want to change it to:
But then you'll also need to provide a
paramconverterservice formy_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.
Comment #4
wim leersNote that Drupal core does not support
PUTfor entities for the reasons given in https://groups.drupal.org/node/284948 — it prefersPATCHinstead. That post is linked from the official REST docs at https://www.drupal.org/docs/8/core/modules/rest/1-getting-started-rest-c....Comment #5
wim leersComment #6
bdominguez commentedI'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.
Comment #7
wim leersYou're not supposed to define a route. You're supposed to define a
@RestResourceplugin. You can then enable this REST resource (and automatically create a route) via https://www.drupal.org/project/restuiAn
array $dataparameter would work.Comment #8
bdominguez commentedWhat 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:
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).
Comment #9
wim leersSorry, this is what I meant:
This would work:
function post(array $data) {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!
Comment #10
bdominguez commentedBut 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;
}
Comment #11
wim leersIf you want to specify route options to indicate which paramconverter to use, you can override
\Drupal\rest\Plugin\ResourceBase::getBaseRoute()to specify 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!
Comment #12
bdominguez commentedThanks! I'm looking forward to that example.
Comment #13
macherifYou can not use the
PUTmethod because it's not allowed as the Documentation said:You can checkout the right way at the official documentation.
Comment #14
macherif