Problem/Motivation
Quoting #2737751: Refactor REST routing to address its brittleness (the parent issue), and only the bullets that are closely related and that this issue wants to solve:
It's very hard to understand routing in the REST module, and it's very brittle, because:
ResourceBaseonly sets_formatfor GET route, should also do so for POST/PATCH/DELETE routes.ResourceBasegenerates one route per method for POST/PATCH/DELETE, but one route per method+format for GET. This should be made consistent.ResourceBasegenerates individual GET routes for every available serialization format, not just the ones that are allowed by the configuration, and thenResourceRoutes::alter()removes those that are not acceptable by the configuration.ResourceBasesets_content_type_formatfor PATCH/POST, but it should actually also set_format.…
ResourceRoutes::alter()is doing configuration verification.ResourceRoutes::alter()assumes_formathas only ever one format listed, even though multiple can be listed. This blows up as soon as any custom@RestResourceplugin specifies multiple formats, which it can by not usingResourceBase::routes().
Proposed resolution
- Add
_formatroute requirement toPOSTandPATCHroutes. - Have a single route for
GET, instead of one per format. - Fix the
_formatrequirement validation inResourceRoutesso that it supports multiple formats (also necessary for point 2). Per #9, this sadly requires significant bugfixes forFixed in #2472337: Provide a lazy alternative to service collectors which just detects service IDs and #2883680: Force all route filters and route enhancers to be non-lazy, which were both blocking this issue.\Drupal\Core\Routing\LazyRouteFilterand related code, because it's not respecting route filter service priorities.- Still per #9: the route filter priorities are incorrect, too! Quoting the relevant portion:
because nothing in the entirety of Drupal 8: not the REST module, not anything else in core, not even the routing system test coverage… had both a
_formatand a_content_type_formatroute requirement. And hence… we didn't even notice that even the existingroute_filterservice priorities were incorrect.method_filterhad priority 1, all others (request_format_route_filterandcontent_type_header_matcher) had no priority, i.e. priority 0.
Butcontent_type_header_matchermust run beforerequest_format_route_filter! Because when a route has a_content_type_formatrestriction, that means it accepts/needs a request body. Without the request body, no sensible response. Missing aContent-Typerequest header causes a 415. Missing a?_format(orAcceptrequest header in an ideal world) causes a 406.The tests are failing because a 415 is expected, but a 406 is received. In other words: exactly the problem I just explained.
Remaining tasks
None.
User interface changes
None.
API changes
None.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #146 | 2858482-146.patch | 41.8 KB | wim leers |
| #146 | interdiff-137-146.txt | 4.14 KB | wim leers |
| #146 | interdiff-129-146.txt | 2.69 KB | wim leers |
| #142 | 2858482-142.patch | 41.03 KB | wim leers |
| #142 | interdiff-137-142.txt | 1.45 KB | wim leers |
Comments
Comment #2
wim leersThis is basically all that's needed in this issue.
Comment #3
wim leersLet's be more clear in
ResourceBaseon whyDELETEdoesn't need_formatnor_content_type_formatroute requirements. Explicit documentation helps prevent questions later.Comment #6
dawehnerI''m a bit worried about custom code linking to these routes, as this would throw exceptions now.
Comment #7
wim leersI didn't notice that I also needed to update
DbLogResourceTest. Easy fix. That fixes one of the failures.Comment #9
wim leersAll remaining failures are the same. They're all about
testPost()failing atEntityResourceTestBase.php:633:(And because #2832859: Write EntityResourceTestBase subclasses for: MenuLinkContent just got committed, I think the number of failures will actually be higher in #7 than in #3, despite fixing one failure).
Why is this happening?
_content_type_formatbefore, now it has_format.\Drupal\Core\Routing\RequestFormatRouteFilter::applies()to returnTRUEinstead ofFALSE._route_filters. It used to be['method_filter', 'content_type_header_matcher']before this patch. But point 2 causes it to be['request_format_route_filter', 'method_filter', 'content_type_header_matcher']after this patch.\Drupal\Core\Routing\LazyRouteFilterand effectively made every route filter a lazy one. But… it did so without respecting the priority ofroute_filter-tagged services! Why was this not noticed? Because REST had almost no test coverage. And neither does the routing system for WSCCI functionality, i.e. REST functionality. And so the handful of route filters that we have just ran in a random order (well, somewhat deterministic, but definitely not respecting the service priority).On top of that, because nothing in the entirety of Drupal 8: not the REST module, not anything else in core, not even the routing system test coverage… had both a
_formatand a_content_type_formatroute requirement. And hence… we didn't even notice that even the existingroute_filterservice priorities were incorrect.method_filterhad priority 1, all others (request_format_route_filterandcontent_type_header_matcher) had no priority, i.e. priority 0.But
content_type_header_matchermust run beforerequest_format_route_filter! Because when a route has a_content_type_formatrestriction, that means it accepts/needs a request body. Without the request body, no sensible response. Missing aContent-Typerequest header causes a 415. Missing a?_format(orAcceptrequest header in an ideal world) causes a 406.The tests are failing because a 415 is expected, but a 406 is received. In other words: exactly the problem I just explained.
So this patch then:
LazyRouteFilterto respect the priorities forroute_filter-tagged services. It looks at\Drupal\Core\Routing\Routerfor guidance — it uses the exact same approach.content_type_header_matcher: it must run before therequest_format_route_filterYes, this arguably belongs in a separate issue. We can debate that later. I first want to get this to green. To prove that this solves the problems uncovered here.
Comment #10
wim leers#2368769: All route enhancers are run on every request also didn't make
ContentTypeHeaderMatcheractually lazy. Let's do that.Comment #13
dawehnerI believe #2472337: Provide a lazy alternative to service collectors which just detects service IDs would help us a lot.
On the longrun I agree. We should make every route filter/enhancer lazy or simply initiatlive it always. That for itself gets rid of 2 indirections which aren't needed.
Comment #14
wim leersThis fixes all fails.
In
\Drupal\rest\Tests\ResourceTest::testSerializationClassIsOptional()and\Drupal\user\Tests\RestRegisterUserTest::registerRequest(), the?_format=query string was missing for POST requests.And
\Drupal\rest\Tests\Update\ResourceGranularityUpdateTestfailed because\Drupal\rest\Routing\ResourceRoutes::getRoutesForResourceConfig()now is updating_formatcorrectly for the first time. In doing so, it is omitting thehal_jsonformat… because thehalmodule was not actually enabled in this test! So the fix was simple, change:to:
Comment #15
dawehnerThe problem is, by adding this tag, this makes it not lazy anymore, see the issue linked above.
Comment #16
wim leersAha. I didn't realize that when I first read your comment. Sorry! Thanks for taking the time to point it out so clearly :)
We can implement it without using
service_collectorin this patch, to avoid making it non-lazy. Did that in this reroll.Comment #17
wim leersComment #18
wim leersThis blocks #2737751: Refactor REST routing to address its brittleness.
Comment #20
wim leersI forgot
--binary. This is the patch that the one in #16 should have been.Comment #21
tedbowLooking good! The multiple routes for each format on GET confused me before.
We should document why these priorities need to be set to these values so we remember and don't accidentally mess this up.
Right now
$collection->add("$route_name.$method", $route);is in every case of this switch block. I think it was needed to be there before because 'GET', 'HEAD' case added multiple routes. Since this patches removes that I think would it be in scope to move adding the route to after the switch block. It would be more readable I think because the switch block would only concern modifications needed, if any, before adding the route to the collectionWhy do we check for
empty($allowed_formats)after we call setRequirement() with $allowed_formates? If $allowed_formats is empty we are not going to add it to the collection so why do that check before the call to setRequirements()?It should be noted that after this patch the ids of routes will change. For example you would no longer have "rest.entity.node.GET.hal_json" and "rest.entity.node.GET.json" it would just be "rest.entity.node.GET". I checked https://www.drupal.org/core/d8-bc-policy and route names are note consider API but think something we would want to note in a change record incase any custom code is checking \Drupal\Core\Routing\RouteMatchInterface::getRouteName. If search core for "->getRouteName() == " you will see it a bunch not related to REST though.
Comment #22
wim leersRE: REST GET routes going from one-per-format-per-resource (N) to one-per-resource (1): change record created: https://www.drupal.org/node/2865645.
Comment #23
tedbow@Wim Leers thanks for the update. Changes look good!
Marking as RTBC. I also updatded the change record with a couple sentences of explanation.
Comment #24
dawehnerIt is sad to see that we don't solve the generic solution out there #2472337: Provide a lazy alternative to service collectors which just detects service IDs ... given how near we are to solving that. It feels like a local time minimum, which doesn't sum up to be a minimum in total.
I'm wondering what this means in the context of this code:
... doesn't this mean that this entire
if (empty())is then dead code?Comment #26
wim leers#24:
Comment #27
wim leersUpdated #2853460: Simplify RequestHandler: make it no longer ContainerAware, split up ::handle() to indicate that it's now blocked on a chain of two issues. And updated #2737751: Refactor REST routing to address its brittleness to indicate it's now blocked on a chain of 3 issues.
Comment #28
wim leers#2472337: Provide a lazy alternative to service collectors which just detects service IDs just landed, so this is now unblocked!
Rerolling…
Comment #29
wim leersThis conflicted with #2826407: PATCH + POST allowed format validation happens in RequestHandler::handle(), rather than in the routing system.
Comment #31
wim leersgit is a git with complex files.
Rerolled with
--binary, without any other changes.Comment #32
wim leersAddressed #29.2: I saw ways to further clean this up.
#2826407: PATCH + POST allowed format validation happens in RequestHandler::handle(), rather than in the routing system moved the setting of the
_content_type_formatroute requirement fromResourceBase::routes()toResourceRoutes::getRoutesForResourceConfig. But this patch was adding the setting of the_formatroute requirement toResourceBase::routes(). Ideally both would be set in the same place, right? Because both suffer from the same problem: we can't set them inResourceBase::routes()(orResourceInterface::routes()in general) because REST resource plugins do not have access to the REST resource configuration…So if we accept that premise, and we then choose to remove the setting of
_formatfromResourceBase::routes(), then there's nothing left to do for thePATCH,GET,HEADandDELETEmethods. The only logic that remains, is that forPOST, to specify a different path than the canonical one.Which means we can delete that entire confusing
switchstatement, and make it super simple. Then it becomes very clear thatResourceBase::routes()really actually is returning base routes by default: the_formatand_content_type_formathave to be specified later, when we can read the actual configured formats for this resource from its config entity!This also means that the changes this patch was making to
ResourceRoutes:::getRoutesforResourceConfig()actually canbe simplified to match that of #2826407: PATCH + POST allowed format validation happens in RequestHandler::handle(), rather than in the routing system. And we can even slightly simplify the logic that #2826407 added there, and make both the
_content_type_formatand the_formatcases consistent.Comment #33
wim leersAddressed #24.1, by using the new functionality introduced by #2472337: Provide a lazy alternative to service collectors which just detects service IDs. I followed the best practices/core committer requirements that were established in that issue, which includes not using
ContainerAware. I also tried to minimize changes inLazyRouteFilter, but felt obligated to correct the most misleading pieces of documentation present inLazyRouteFilter.Note: it's probably easier to not interview the interdiff, but review the overall patch, and look specifically at the
LazyRouteFilter-related changes.IS updated, CR (https://www.drupal.org/node/2598944) updated.
Comment #34
wim leers#24.2: you're right. But in order to achieve that, we'd have to ensure that
\Drupal\Core\Routing\LazyRouteFilter::filter()actually respected the the route filters specified for each specific route. And that's not how\Symfony\Cmf\Component\Routing\NestedMatcher\RouteFilterInterface(filter()) was designed: it checks every route in the collection, regardless. Drupal layered\Drupal\Core\Routing\RouteFilterInterface(applies()) on top of that, solely for the route compilation phase.i.e.
applies()determines which filters need to be applied to a collection of initial route matches (based on URL) tofilter()the route collection down to a smaller number (based on method, format, etc).What you're saying means that every route filter implementation must ensure that every
filter()implementation must also callapplies()for every route it iterates over in its collection. Which means requiring pure Symfony API implementations (justfilter()) to be aware of the Drupal API addition (applies()) and conditionally change their logic.So I think it's safer to keep this as it is today. We could open a separate issue for that though.
The change you quoted is still valid: it ensures that we only run the
ContentTypeHeaderMatcherroute filter when there's actually at least one route in the route collection that needs it!Comment #35
wim leersWhew, now blocked on review!
Comment #36
wim leersComment #37
dawehnerI just proposed a change to make route filters and enhancers much better for themselves: #2883680: Force all route filters and route enhancers to be non-lazy I'm wondering what you think about it.
This would be indeed possible, but it feels a bit like an overoptimisation.
Comment #38
wim leersCommented there. +1 :) But I don't think you are saying that issue should block this one, right?
Fine, reverted.
(I'm confused by this though:
ContentTypeHeaderMatcheris only very rarely necessary, yet HEAD (and this revert) causes it to run always. So then what's the point of it all? Of course, if I understand #2883680: Force all route filters and route enhancers to be non-lazy correctly, it'll remove this altogether, so then making this change now indeed is pointless.)Comment #39
wim leersThis was first blocked on #2472337: Provide a lazy alternative to service collectors which just detects service IDs for almost 3 months. The day after that landed, I got this patch going again (#28 through #36).
But now it's being suggested in #37 that this gets blocked on #2883680: Force all route filters and route enhancers to be non-lazy. I think #2883680 is a worthwhile simplification. But despite me having worked on that for half a day today to get it to green, it's still not green. And for it to get to RTBC, performance (profiling) will have to show positive results (i.e. faster or same speed).
I'm fine with delaying this on #2883680: Force all route filters and route enhancers to be non-lazy for a few weeks, but I don't want this to be blocked again for multiple months. That'd mean 3 REST issues are again blocked for months (this issue, plus #2853460: Simplify RequestHandler: make it no longer ContainerAware, split up ::handle() which is blocked on this, plus #2737751: Refactor REST routing to address its brittleness which is blocked on that). If this were introducing nasty work-arounds, then I'd agree with you that we should hard-block this. on #2883680. But this issue is merely fixing the existing infrastructure, #2883680 is completely refactoring the existing infrastructure.
Postponing for now.
Comment #41
wim leers#2883680: Force all route filters and route enhancers to be non-lazy (finally — after almost 4 months…) landed, which means this is now fully unblocked!
All these changes are obsolete now that #2883680: Force all route filters and route enhancers to be non-lazy is in! :)
The changes here are obsolete because #2775553: Convert web tests to browser tests for REST module refactored this test a few months ago.
So many obsolete changes, we can drop all their hunks, which means this patch just became a lot smaller!
Comment #43
wim leersI omitted too many hunks in #41:
These fixes the the current priorities (plus comments to explain/justify them) are still necessary.
Comment #44
wim leersIS updated. IS now also explains why #43 is making the patch pass.
Comment #45
dawehner🔧 It would be nice to have some explanation why this particular order of priorities are chosen. At least I was not able to understand why from these comments :(
❓Do we have the 80 chars rule in comments in yml files as well?
❓I'm wondering whether we need to keep those around for BC reasons (someone might have overridden this base class or link to those routes using route names). This would be a massive problem of course
I think its always easier to read positive conditions so
$method === 'POST' ? $create_path : $canonical_path;Comment #46
wim leersThanks for the review! 👏
=== POSTis the less common case. I always put the common case first. I don't feel have a strong opinion about this though.)Comment #47
dawehnerI agree with you, but what about people using the route names to link to things? We could probably provide some routes which aren't involved in actual routing, simply for BC reasons? Maybe its not worth doing so though.
Comment #48
wim leersThat's fair. If at all possible, we shouldn't break BC. And it's possible in this case. Although doing this does mean
Symfony's routing system has no concept of "route aliases", so we can't do this. There are only two alternatives:
The latter is the simplest approach, and is also the strategy we used last time we moved routes around (in #2753681: Move CSRF header token out of REST module so that user module can use it, as well as any contrib module).
Comment #49
dawehnerWell, this is not entirely true. Just think about
<front>and<current>.Comment #50
wim leers<front>and<current>don't use the (Symfony) routing system. They use Drupal's special sauce on top: path/route processors.\Drupal\Core\PathProcessor\PathProcessorFront+\Drupal\Core\RouteProcessor\RouteProcessorCurrent.Comment #51
dawehner@Wim Leers
Right, well, this is what I'm saying. We could provide a BC way which doesn't introduce having actual routes in the router.
Comment #52
wim leersBut
<front>and<current>also exist in the router. They just have very thin route definitions, because the bulk of the logic lives in path/route processors. Why is that better/preferable to what #48 does?Comment #53
dawehnerI'm simply fearing by adding them into the routing process as well, these routes might be for example used by the router itself.
I just tried to suggest a BC layer with the most minimal surface.
Comment #54
wim leers#53 is an excellent point. I hadn't looked at it that way yet. I'll try to do that instead.
Comment #55
wim leersI'm glad @dawehner made that remark and insisted on it —
\Drupal\Core\RouteProcessor\OutboundRouteProcessorInterfaceis indeed the perfect fit! Thanks, Daniel!It seems to work nicely. It is a bit more code, but indeed helps ensure it's a BC layer with the smallest surface possible.
Comment #57
wim leersTestbot is working again. Retesting, back to NR.
Comment #59
wim leersSigh, forgot
git diff --binary.Comment #60
borisson_While we don't have a coding standard about it (I think), we usually indented subsequent lines with 3 spaces (or just one space), not with enough to make it line up.
We're already really inconsequent with @todo comments, let's not add another style.
Also, this mentions that we should fix this in drupal 9, but there can't be any bc break for drupal 9 - so we should either find a way to do this in a non-bc breaking way or live with it and remove the comment.
At the very least, this should point to a d.o issue as a followup.
I think this is missing words,
to ensure old rest route definitions still workor something similar?/s/array/string[]/Same comment about indentation.
Comment #61
dawehner❓ I'm wondering whether we should have a flag to remove the processor, if you want to. It would be kinda nice to document the various BC things, by having configuration for all of them.
I'm wondering whether we could be more specific like: '_rest_bc_format_specific' or something like that?
Comment #62
wim leers#60:
B. Done. Note that there is no way to fix this without breaking BC, which is why this comment exists. I started opening a new issue, but then I realized that this is pretty much how it was designed to work. I think that this cannot be completely resolved, not without rearchitecting more of the REST module. Such rearchitecting will only make sense after all of #2905563: REST: top priorities for Drupal 8.5.x is done, because that that point we'll have a much clearer perspective/overview of what works well and what doesn't. So, simply removed those @todos.
Comment #63
wim leers#61:
bc_routeas the route option, because it'd allow all BC routes in D8 to do something like that: so you could easily remove all BC routes.This was a lot more code to do than I thought/hoped… but such is the nature of BC layers.
Comment #65
wim leersI forgot to
RestServiceProviderruns beforerest_update_8501()has runUserRegistrationResourceThis should reduce the number of failures. (The patch is now twice as large as #62… 😨)
The remaining failures look like this:
Exception: TypeError: Argument 1 passed to Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBC::__construct() must be of the type array, object given— and I have NFI why that's happening…Comment #67
dawehnerThere is a parameter in
DrupalKernel, which is FALSE in update.php, which stands for "Make the test pass". I set it to TRUE, and it works as expected. Just kidding :)Long story short, in HEAD update.php passes
$allowDumping = FALSEto the Drupal kernel. This results into having a ContainerBuilder around when executing update.php, which seems to not be able to resolve parameters the right way, as long its in container build state. We would rather need an actual container.I know that Symfony doesn't actually support using a container builder on runtime, so let's switch that off.
Comment #69
wim leersThis is identical to @dawehner's patch in #67, just rebased, so it applies again.
Comment #71
wim leersSo apparently @dawehner's interdiff indeed solves the problems we saw in #65 … but it causes failures in
UpdatePathTestBaseTestandUpdatePathTestBaseFilledTest:(Comment #72
dawehnerMhh, I spend some time trying to debug this problem, but I haven't figured out yet where these test failures are coming from.
This somehow no longer gets triggered ...
Comment #73
wim leersThanks for debugging! Tricky, isn't it…
But other than the fact that it's currently not passing tests, do you prefer the approach in #63 (BC routes exist only if config enables them) and later to the one in #62 (BC routes exist always)? You requested it, but I want to make sure you like the outcome, assuming we can get it to green. If you don't like it, then we also don't need to debug it further.
Comment #74
wim leers#2765959: Make 4xx REST responses cacheable by (Dynamic) Page Cache + comprehensive cacheability test coverage conflicts with this in two places, in trivial ways. Rebased.
Also fixed the coding standards violations.
Assigning to @dawehner for feedback on #73, regarding the new direction he requested.
Comment #76
wim leers😭
Rerolled with
--binary.Comment #78
dawehnerYeah I think having these routes is needed, so also my review.
For contrib module reasons we need to enable those routes actually by default, otherwise new installed modules would fail as well.
I would love to have an opinion from @alexpott to ask him whether executing a container rebuild could be potentially horrible.
Comment #79
wim leersLooks like reverting #67's interdiff makes tests pass :)
Comment #81
wim leersForgot
--binary🤦♂️ Rerolled.For contrib module reasons we need to enable those routes actually by default, otherwise new installed modules would fail as well.
If that's the case, then do we even want/need this flag in config? Who is ever going to find out about this BC flag, and is going to manually disable it?
Is your intent to first enable the BC routes always (for both existing and new sites), and then in a future minor to not install the BC routes on new sites, because contrib modules will have had time to migrate?
Comment #82
wim leersUnnecessary/out of scope change: these are just being switched places. Reverted.
This needs a deprecation error to be thrown. Expect lots of failures due to that.
Comment #84
dawehnerThat is a good point. Well I was hoping people can disable it and run with it, if it works.
Everytime we change these priorities I would like to ask the panels people to test these changes ... better be safe than sorry I guess?
I think the patch looks fine right now, but I think it lacks one essential feature: Actually throw a deprecation error when someone uses the deprecated routes. Much like we want to for example add support for deprecated permissions we want to do essentially the same for routes as well. I'm happy to work on that. Overall I'm not 100% sure whether this should block this issue or not, but I think the general feature would be needed.
Comment #85
wim leersUgh … so #79/#81 fixed the 2 failing tests … but causes all update path tests to fail like this:
because the
serializer.formatsis apparently an object instead of an array. Just like I wrote in #65.Sigh. Undoing #79/#81, aka repeating #67.
Comment #86
wim leersSo, you agree we can remove the flag? The deprecation notice is enough to encourage users to update I think.
Sounds good! 👍
Comment #88
dawehnerSounds sensible for me.
Comment #89
wim leersAlright, on it then. That will also remove the need for the container rebuild, which means the question in #78.2 for Alex Pott is no longer necessary.
Comment #90
wim leersThat allowed for a massive simplification: from
18 files changed, 245 insertions(+), 60 deletions(-)to8 files changed, 139 insertions(+), 42 deletions(-).The set of changes now looks much more coherent, and hence much easier to review :)
Comment #92
wim leersIDK why, but
DeprecationListenerdoesn't seem to work for me (not in PHPStorm, not usingphpunitdirectly, and yes, with updatedphpunit.xmland up-to-date vendor). Is this becauseclass EntityResourceTestBase extends class ResourceTestBase extends BrowserTestBase?(I also tried using
@deprecatedException+@group legacy, but that also failed. That'd be a nightmare here though, because the exception message is different for every subclass. Trying that is also how I found #2928645: \Drupal\Tests\migrate\Kernel\Plugin\MigrationDirectoryTest uses @expectedDeprecationMessage.)Comment #93
wim leersComment #96
dawehnerThings surprisingly work well, when you are doing them right.
Comment #97
wim leers😭😇😱
:D
I'm an idiot. So glad you caught that. I could've debugged that for a few more hours before finding that :D
Comment #99
wim leersHm, that also didn't work apparently? :( Did I get something else wrong?
Comment #100
dawehnerOh, weird
Comment #101
benjy commentedApplying this patch with 8.4.x breaks one of my tests. Afterwards I have the XML route enabled even though my config looks like this:
I end up with the error
No route found for the specified format html.in a test when running the following code$url = Url::fromUri('internal:/api/forum/subscriptions/' . $this->question->id()This is because AcceptHeaderMatcher::filter can no longer detect the correct default format since there are multiple routes enabled.
This is the hunk of code that was removed that causes the issue. Brining that back fixes my issue.
Comment #102
wim leersFirst: rebase. Conflicts with #2927758: Update DbLogResourceTest to use the ResourceTestBase base class instead of the deprecated RESTTestBase (made the patch simpler) and #2928249: Introduce a PHPUnit 6+ compatibility layer for Drupal\Tests\Listeners classes. Easy fixes.
Comment #103
wim leers@benjy: Thanks for testing this!
Aha!
The reason is that we generate BC routes for all formats, even formats that aren't enabled. That's easy enough to fix :)
Let's start by adding a failing test that detects the regression reported in #101.
Comment #104
wim leersAnd the fix!
Comment #107
benjy commentedNot sure how this is meant to work, because it's a new route it loses the "methods" and therefore never gets a route in ResourceRoutes.php.
This is what worked for me.
Comment #108
wim leersIt works fine actually, because:
That
|| $route->hasOption('bc_route'is what makes it work. It's intentional that the route definition of this BC route is as empty as possible, because\Drupal\rest\RouteProcessor\RestResourceGetRouteProcessorBCtakes care of automatically filling it with the successor route's definition.Note that #104 is failing not due to the changes I made in those last few patches, it already was failing, due to the BC
@trigger_error()stuff.So: are you sure it's still broken for you?
Comment #109
benjy commentedAhh i see, i'm using this patch - https://www.drupal.org/project/drupal/issues/1927648?page=1#comment-1224... which didn't have the the OR, but is the best I have that works against 8.4.x.
Is it worth adding a comment to
new Route('')because just reading it, it feels wrong?Comment #110
wim leersAdded such a comment.
Comment #111
benjy commentedIt's the "" argument to Route that seems off, I know we mention "empty as possible" but it still isn't immediately clear reading the code. Could we have a new version of the route object to represent deprecated routes? Not sure how they are stored in the database, would work if they were serialized?
Comment #112
wim leersWoah, #110 suddenly passed, without the 413 fails … because something must've changed in the handling of deprecations. Well, YAY, that means we can finally continue here :)
#111: I understand that the first argument to the
Routeconstructor being the empty string looks confusing, but otherwise we still end up putting metadata in those routes, which we specifically don't want to.I don't know what you're asking for when you say — could you explain that a bit more? I also don't understand what you're getting at when you say .
Comment #113
benjy commentedCurrently we setup the route object each time we want to add a BC route with the right metadata, e.g. with
(new Route(''))->setOption('bc_route', TRUE);. My point was, if the route is only serialised in the database to store basic route info, we could just as easily have a new class that encapsulates the BC variation for us.Then, in the patch we simply do,
new BcRoute(). It would give us somewhere to document this behaviour and it's only a CMD+click away when you're reading the route generation code.Comment #114
wim leers#113: AHHHHH! I love that! 😍
Done.
Comment #116
wim leersRandom infra fail.
Comment #117
dawehnerI really like that too, great idea!
Comment #118
wim leersWOOOT!
Comment #119
benjy commentedGreat, patch looks good. Couple of super minor PHPCS things.
Should this be @see ?
Missing a new line in BcRoute.
Comment #120
wim leers@seethough. IDK what's best. The sad thing is that@seemust be on a line of its own, otherwise I'd do it inline here.I'm glad we're down to this level of nitpicking :D
Thanks for all your detailed feedback so far, @benjy, much appreciated! 👌
Comment #121
benjy commented1. Yeah, I don't have any good suggestions there. RTBC +1 though.
Comment #122
wim leersThis is very important for maintainability, and the size of the patch reflects the complexity. Therefore, bumping to major.
Comment #124
wim leers#2916740: Add generic entity actions conflicted with this, in
DeprecationListenerTrait. Rebased.Comment #125
wim leersAs of #1927648-490: Allow creation of file entities from binary data via REST requests, this is now also a hard blocker for that issue!
Comment #127
wim leersAre you kidding me? :(
My bet is that #2934336: Update symfony/phpunit-bridge to the latest released version caused this :(
Comment #128
wim leers🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️🤦♂️
Comment #129
wim leersComment #130
wim leersTo improve this in the future, @alexpott created https://github.com/symfony/symfony/pull/25757
Comment #131
wim leersAnd he even created a Drupal port of it, in case Symfony doesn't merge his PR: #2935755: Add a trait to allow dynamic setting of expected deprecations.
Comment #132
wim leers#2853460: Simplify RequestHandler: make it no longer ContainerAware, split up ::handle() landed earlier today.
Like that issue, this issue is blocking #1927648: Allow creation of file entities from binary data via REST requests. I said in #125 already that this was a hard blocker, but the latest patch (#1927648-501: Allow creation of file entities from binary data via REST requests) is now truly based on this.
Comment #133
larowlanI think we should wait on #2935755: Add a trait to allow dynamic setting of expected deprecations here if that prevents adding all those expected deprecations - thoughts?
Comment #134
wim leersI think we shouldn't because this blocks #1927648: Allow creation of file entities from binary data via REST requests. It'd be trivial to simplify
\Drupal\Tests\Listeners\DeprecationListenerTrait::getSkippedDeprecations()once #2935755: Add a trait to allow dynamic setting of expected deprecations lands. I'd be happy to guarantee that happens.Comment #135
dawehnerI think iteration on existing code should be easy, so I agree with @Wim Leers that we shouldn't wait.
Also wow, I would have never figured out the deprecation message bit.
Comment #136
larowlannit: 'that that' - 'that it'?
@see ? instead of
F.e seeShould we use the third parameter here?
would using
list()here to create named variables make this easier to understand?Would it be worth creating a factory method on BcRoute e.g.
::fromRouteso others can use this, at present it is limited to this processor.In addition, that could make use of scope advantages given its a sub-class.
shouldn't this use
setExpectedExceptionComment #137
wim leersYes! Good call. It does make the code far more verbose though, but for a good reason probably :)We can't, because this works for any REST resource plugin, and the routes generated are of the formrest.PLUGIN_ID.METHOD.FORMAT. Sorest.entity.node.GET.jsonorrest.dblog.GET.json. Note howexplode('.', …)would result in 5 parts for the former, and 4 parts for the latter.BcRouteis that it's an as-empty-as-possible route definition.X::fromY()implies constructing aXfromY, but that implies thatXinherits all metadata/definition data fromY. Which is exactly what we don't want here.It would still work, but it'd encourage the wrong usage. Unless … we also override
serialize(), to guarantee that it'll always be an empty definition that gets stored in the router.Did that, curious to hear what you think.
Comment #138
dawehnerIn the same way we could limit the setters. Actually though, I think we should remove that given that we don't know the usecases people might have when using the class?
Comment #139
larowlanYeah agree, I think we should wait until the dedicated issue for depreciation of routes. For now let's leave the static on the outbound processor. That way we can work out the best way to do it without holding this up.
Comment #140
wim leersI’m at 🏓, can somebody reupload #129 and RTBC? Thanks!
Comment #141
larowlanI think we need #137 minus #136.5, not #129
No rush Wim, I'll keep my eye on this across the weekend.
Added deprecate issue to related.
We also need a follow-up filed to remove all usages of the deprecated GET routes from core, and then remove the messages from the allowed deprecation messages list. I'd love to see that fixed as soon as possible just to keep that list trimmed and terrific. But obviously, let's get through the alpha deadline first.
Sorry for the misdirection on the BcRoute factory, you're too efficient - I posed the question and was expecting discussion first :)
Ooh, we have an 8.6.x branch now.
Comment #142
wim leersRight, I realized that long after posting #140!
No problem at all! I don't mind exploring these things. It took me <15 minutes to write the implementation. Discussing an actual implementation makes the discussion much more concrete, and hence more effective. It was worth exploring!
DO NOT READ THIS INTERDIFF OR PATCH, I messed things up. Don't skip reading comments, just skip the interdiffs/patch on this comment. Redid the interdiffs/patch in #146.
Comment #143
wim leersThere are no usages! Only in tests:
This is the only way the deprecation error is being triggered, and it's the reason we're updating
\Drupal\Tests\Listeners\DeprecationListenerTrait. That is why I worked with @alexpott to get #2935755: Add a trait to allow dynamic setting of expected deprecations going. Once that lands, we can change the test coverage to useexpectDeprecation($message)instead. Without that, we'd have to have a method that every subclass ofEntityResourceTestBaseneeds to override, to be able to set the appropriate@expectedDeprecationdocblock.IOW: as soon as #2935755: Add a trait to allow dynamic setting of expected deprecations lands, we can simplify
DeprecationListenerTraitagain!These are all existing uses, being updated.
Comment #145
wim leersI completely messed up #142.
Comment #146
wim leersSo, #142 was completely wrong. This completely ignores #142. Again with interdiffs from #129 and #137, but this time … correct. 🤞
Comment #147
dawehnerGreat work @Wim Leers!
Comment #148
larowlanAdded review credits
Comment #149
larowlanCommitted as b4a0230 and pushed to 8.6.x
Cherry-picked as b15791c and pushed to 8.5.x
Published change record
Thanks all, see you in #2928750: Allow to deprecate routes
Comment #150
larowlanCan we get the follow up for the offspring of this issue and #2935755: Add a trait to allow dynamic setting of expected deprecations
Comment #151
wim leersThanks @dawehner!
This also unblocked #1927648: #1927648-514: Allow creation of file entities from binary data via REST requests.
Created #2936704: Remove REST route deprecations from DeprecationListenerTrait::getSkippedDeprecations(), use ExpectDeprecationTrait::expectDeprecation() instead for #150.
Comment #155
wim leers