Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
$response = call_user_func_array(array($resource, $method), array_merge($parameters, array($unserialized, $request)));
This is really error prone, because it demands that each ResourceInterface
plugin accepts parameters in a very particular order:
+/**
+ * Provides a resource for resetting a user's password.
+ *
+ * @RestResource(
+ * id = "user_password_reset",
+ * label = @Translation("Password reset"),
+ * uri_paths = {
+ * "https://www.drupal.org/link-relations/create" = "/user/password/{name}/{langcode}",
+ * },
+ * )
+ */
…
+ public function post($langcode, $name) {
+ $name = trim($name);
Note how the order of parameters of the post()
method MUST be in this particular order.
Another example: #2870649-12: REST should respond with a 409 for a POST request to an existing entity.
Proposed resolution
- Use the argument resolver.
- But if the arguments resolver fails (exception thrown), fall back to the way this was done in Drupal 8 so far
Remaining tasks
None.
User interface changes
None.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#70 | 2720233-70.patch | 7.49 KB | Wim Leers |
#70 | interdiff.txt | 594 bytes | Wim Leers |
#68 | 2720233-68.patch | 7.43 KB | Wim Leers |
#66 | interdiff-2720233.txt | 2.17 KB | dawehner |
#66 | 2720233-66.patch | 7.21 KB | dawehner |
Comments
Comment #2
dawehnerWhat about something like this? Its still tricky though
Comment #4
Wim Leers+1 for the principle.
Comment #5
klausiwhy is it error prone? how can the controller resolver help us? what kind of issue are we fixing here?
why is this configurable? Can't we do it always?
Otherwise great idea, we should get rid of the code in the else{} clause.
Comment #6
dawehnerWell, I wanted to ensure that there is no BC break.
The reason how this issue started is the following required code:
... Note the order of the parameters.
Comment #7
Wim LeersIOW:
RequestHandler
passes arguments in whatever order they came in, not in the order that is expected.Comment #8
dawehnerWell, I don't know why it happens to be honest, but it does happen and its really not nice.
Comment #9
Wim LeersRight. but my interpretation/explanation is correct, right?
Comment #10
klausiAha, that would make sense! Then this is more of a bug report and we should just replace the brittle part in RequestHandler.
Comment #11
dawehnerWell, let's see whether this actually works
Comment #12
klausiWrong patch?
Comment #14
dawehnerThere we go.
Comment #16
Wim Leers\Drupal\Tests\rest\Kernel\RequestHandlerTest
needs to be updated, then tests will pass again. That seems like a great novice task.Comment #17
klausiwhy do we clone the request here? Please add a comment.
And the issue summary update is still missing.
Comment #18
dawehnerWell, the general idea was to not alter the current request in the middle of nowhere but just change it to be able to pass in the data.
Comment #19
dawehnerGiven that this is a clear BC break (as naming of the variable actually matters), I think we should have a BC layer and some flag to opt into this behaviour.
Comment #20
Wim Leers#19: can you point to which variable is being renamed, which is the BC break?
Comment #21
dawehnerWell, before it was:
function ($route_parameters..., $unserialized, $request)
exactly in this order and the naming of the variables don't matter.Afterwards its for example
function ($unserializer/$data, $route_param_1, $request, $route_param_2)
aka. the order of the variables don't matter anymore, but the names of the variables matter. Previously they could have used$random_1221123
for the$unserialized
parameter, which totally breaks with this patch.Comment #22
Wim LeersI understand now. Thanks for explaining.
I have no idea how to provide BC for that.
Bluntly speaking: this is a plain bugfix, and any existing code relying on non-matching-variable names is therefore buggy too.
Comment #23
dawehnerWell, on the other hand this is most probably just a bugfix for a minority of usecases (more than one parameter in the URL), while the unserialized parameter is passed along in all usecases.
The BC layer is outlined in the first patch, by introducing some flag. One alternative could be to see whether the controller resolver finds every parameter. In case it does, use it, otherwise fallback to the previous logic?
Comment #24
Wim LeersSounds great!
Comment #25
dawehnerLet's see whether this actually works.
There is no interdiff, because this patch is written from scratch.
Comment #29
Wim LeersDo we still think this is worth doing?
Also: this is more an improvement than fixing an actual bug, so changing to
. Finally, this seems very much un-novice :)Comment #30
dawehnerI totally think so, but yeah the BC policy makes some things just impossible hard.
Comment #31
Wim LeersHear hear.
Although I think that in this case, there may be a simple small oversight in the test coverage that's causing the failures? :)
Let's see…
Comment #32
Wim LeersFirst: straight rebase.
Comment #33
Wim LeersTurns out there's just a few trivial bugs in #25 :)
But even with these changes, you only the legacy approach works:
Comment #36
dawehnerThis is one of the issues which we should maybe move to 9.x as its easy to solve there? On the other hand then its hard to introduce a deprecation notice of some kind properly.
Comment #37
Wim LeersWell … http://buytaert.net/making-drupal-upgrades-easy-forever basically says we can't do that. At minimum, we need to already implement it now, in parallel.
I'm honestly convinced that this approach works. We just need to figure out what's broken in the current
ArgumentResolver
code path. I think you know that far better than I do — could you do that?Comment #38
dawehnerI'll give it a try over the weekend, but NOT before my presentation :)
Comment #39
dawehnerWell, its not a bug of the argument resolver, its a problem of what we pass into it.
\Drupal\rest\Plugin\rest\resource\EntityResource::get
uses $entity as name ... but the name in the route is called like the entity type.Note: The current fixes still cause some failures ...
Comment #41
Wim LeersWe can change
\Drupal\rest\Plugin\rest\resource\EntityResource::getBaseRoute()
to set$parameters['entity']
instead — this is an internal detail, not an API.Comment #42
dawehnerfair, but we also have to deal with
$original_entity
for example.
Comment #43
Wim Leers:( You're right of course.
But I don't see how the current logic can ever work correctly. For GET+DELETE,
$entity
is the stored entity. For PATCH+POST,$entity
is the entity that is being sent in the request body. If I fix that, and fix the wildcard argumentisInstance()
exception, then this works.Comment #44
Wim LeersI think this is simpler to understand.
Comment #47
dawehnerNice work @Wim Leers!!
Request should be defined always, right?
This needs some documentation.
Comment #48
Wim LeersFrom 127 to 3 fails, yay! Assigning to @dawehner to get him to review my changes and hopefully confirm that it's the right direction. And then perhaps he can drive it home too :)
Comment #49
dawehnerThe tests have been a little bit tainted
Comment #50
dawehnerComment #52
dawehnerHa, I made a last minute change.
Comment #53
pounardFixed a typo, I'm sorry, I'm watching this issue since a long time, and it hurts my eyes everytime :)
Comment #54
Wim Leers@pounard++ :D — I can't believe I didn't see that myself!
@dawehner++ for driving this home! I was so tempted to RTBC this, but I think we need to clean this up just a little bit more.
We want this to have a @todo to only keep the "try" case in D9.
Probably needs rephrasing.
We want to finish the docblock here: add the missing first line, add the missing trialing period, add @return line, and … mark it @deprecated! <3
Comment #55
Wim LeersI finally did the IS update that #5 requested. I also updated the title because this is no longer about using controller resolver, but about using the arguments resolver.
In doing that, I realized that we need to remove the "fall back to legacy approach" at some point, which means we need a CR and we probably want to nudge users in the right direction by …
… logging a warning in the case of the "catch" case here.
Comment #56
DuaelFrI might miss something but is it a reason to double the "$response ="?
Comment #57
Wim Leers#56: I just wanted to post that same remark :)
Comment #58
Wim LeersSee #2870649-12: REST should respond with a 409 for a POST request to an existing entity — this is a blocker for that issue.
Comment #59
dawehnerI'm writing the change record now ...
Comment #60
dawehnerI'm trying to address the latest feedback.
Comment #61
dawehnerI hope I was able to address all the feedback.
Comment #62
dawehnerComment #63
pounardUsing legacy menu arguments when the resolver throws exceptions is a very good move, I was looking for a solution in a custom non Drupal project and you just gave it to me ! Thank you very much for this.
Comment #64
Wim Leers@pounard :)
@dawehner 👍
Comment #65
catch1. Should this break when it finds an entity? If not why not?
2. Should we find the entity before the if/else, then just assign it in the two code branches?
8.4.0. Also trailing whitespace. Both fixable on commit but while I'm here.
Comment #66
dawehnerGood point. IMHO there is no reason to not break here.
This is also a good idea.
Comment #68
Wim Leers#66 looks great. It introduced one whitespace bug though, fixed that.
Comment #69
larowlanLooks good and is some nice clean up - couple of observations
We need to reference the change notice here.
any reason we didn't use array_filter here?
Comment #70
Wim LeersUnless I'm mistaken, after triple-checking that, that's already the case in theOh you mean in the docblock!trigger_error()
call?(If we're moving legacy code into a deprecated helper method that we're going to remove in the future, you have every reason possible to not change it: out of scope change, avoid risk of introducing new edge cases, and so on.)
Comment #71
dawehner+1 for not changing the existing code. This minimizes review scope.
Comment #72
larowlanThanks, agree
Comment #74
larowlanCredited catch for performance improvements.
Committed as 5679964 and pushed to 8.5.x
Published change record.
Would welcome a follow-up to use array_filter as per #69.2
Comment #75
jibranFixed some version details in the change notice. I think it can be improved. @Wim Leers can you please have a look at it? Thanks!
Comment #76
BerdirSomehow this causes test fails in monitoring:
https://www.drupal.org/node/2907681#comment-12253309
What I'm doing is kinda strange (a hack on a rest plugin to support returning a list), but still seems strange that this broke that.
Comment #77
dawehnerI'm curious did you tried changed the parameter name on your function?
Comment #78
dawehnerI quickly had a look at the problem described by @berdir
The route produced has a path looking like
/monitoring-multigraph/{id}
.The method on the plugin looks like this:
public function get($multigraph_name = NULL) {
, and as such the argument resolver isn't able to resolve the parameter.In theory our backward compatibility layer should jump in, but it doesn't as the parameter is optional.
Maybe we could do something like storing the method name of the plugin resource which should be called for a given route. Given that @berdir could provide a collection route, that returns all elements and doesn't need anymore an optional parameter.
Comment #79
BerdirThanks @dawehner for the help.
I fixed this now in monitoring in #2908002: Fix HEAD test fails in REST tests.
The question is whether this is a regression/BC break and whether we care enough about that to revert this? I'm OK if we don't, but I'd suggest we should at least document this case then in the change record.
Being able to specify the method of the resource plugin for a route would be quite nice and a nicer workaround than what I did until we have #2100637: REST views: add special handling for collections (I think that's the issue for official list/collection output in rest resources) but that's a separate issue.
Comment #80
Wim LeersCan you clarify this a bit? Can you not already do this by overriding
\Drupal\rest\Plugin\ResourceBase::getBaseRoute()
?Comment #81
BerdirWhat he means is this line:
$method = strtolower($request->getMethod());
The problem is you can define whatever you want in your routes.. the concept that http method == php methed on the RestResource plugin is hardcoded and basically impossible to change without duplicating the whole method there.
Instead, it would like this:
$method ? $route_match->getRouteObject()->getOption('_rest_method') ?: strtolower($request->getMethod());
Then I could just say _rest_method = 'list' in my routes() method and wouldn't need my current ugly hacks.
Comment #82
Wim LeersAh, I see! That's indeed one of the more questionable design decisions about
Resource(Interface|Base)
+RequestHandler
.I do find
_rest_method = 'list'
confusing too though. The REST module basically has spent no time/thought on how to deal with collections. AFAICT it deferred that entirely to Views. Which is often undesirable or overkill of course. I think the way you implemented it (http://cgit.drupalcode.org/monitoring/tree/src/Plugin/rest/resource/Moni... + http://cgit.drupalcode.org/monitoring/tree/src/Plugin/rest/resource/Moni...), with a singleget()
responsible for both individual and collectionGET
requests with an optional parameter makes total sense. Shall we discuss that further in a follow-up, to officially support that pattern? So that you don't need thoseroutes()
overrides anymore?Then we could at least start having basic collection GET support. If you're +1, either create a follow-up or comment here and I'll create that follow-up.
Comment #83
BerdirOpening an issue certainly makes sense, but I'm not sure that it should be like I did with monitoring. For one thing, configuration is currently tied to methods, what if a resource doesn't support lists or only supports lists, or supports both but you only want to enable one of the two things? But we can discuss that in a dedicated issue. I already mentioned #2100637: REST views: add special handling for collections above, not sure if that *is* that issue but it's certainly related?
Comment #84
Wim LeersNo, that issue is specifically about using REST views for collections, what we're talking about is about specifically not using the
views
module for retrieving collections via REST.I created #2908800: RestResource plugins: use get() method with optional parameter (or no parameter) to signal list support? to discuss this, and posted my concrete proposal in #2908800-2: RestResource plugins: use get() method with optional parameter (or no parameter) to signal list support?. Let's continue the discussion there :)
Comment #85
Wim LeersI also updated #2893804, #2893804-5: Remove rest.module BC layers includes the interdiff to remove the BC layers introduced in this issue/patch.
Comment #87
Wim LeersLooks like this caused a regression: #2954171: Custom REST resource plugin's POST not working after upgrade 8.4.5->8.5.
Comment #88
Wim LeersIt's been confirmed at #2954171: Custom REST resource plugin's POST not working after upgrade 8.4.5->8.5 that this caused a regression for everybody who has
They need to change it to:
Besides needing to look into why exactly this is broken, I am very surprised so many people have the exact same code. Plus, it doesn't even make sense: a default value means you're able to do a
POST
request without sending any data!Comment #89
Wim LeersApparently #49 introduced that and none of us questioned that change. The reason probably being:
I'll update the CR.
Comment #90
Wim LeersCR updated: https://www.drupal.org/node/2894819/revisions/view/10632228/10888480.