Problem/Motivation

Since symfony/http-foundation 5.1: Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated, and will throw a "Symfony\Component\HttpFoundation\Exception\BadRequestException" exception in Symfony 6.0, use "Symfony\Component\HttpFoundation\InputBag::all($key)" instead.

 3x in QueryArgsCacheContextTest::testGetContext from Drupal\Tests\Core\Cache\Context

Related changes
- https://github.com/symfony/symfony/pull/34363/files#diff-9d63a61ac1b3720...
- https://github.com/symfony/symfony/pull/34363/files#diff-51a7b6b88afee9f...

Steps to reproduce

Run a test

Proposed resolution

Use approach from SF https://github.com/symfony/symfony/pull/34363/files#diff-0c1327f76881336...

Use request->query->all() instead of request->query->get().

Where the query isn't guaranteed to be set, start using request->query->has().

In MediaLibraryState, re-implement Symfony's deprecation and behaviour, so that any contrib or custom code interacting with the class (unlikely) updates to the new API.

User interface changes

no

API changes

no

Data model changes

no

Release notes snippet

no

Issue fork drupal-3162016

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

catch created an issue. See original summary.

catch’s picture

Status: Active » Needs review
FileSize
754 bytes

Status: Needs review » Needs work

The last submitted patch, 2: 3162016.patch, failed testing. View results

longwave’s picture

Status: Needs work » Needs review
FileSize
756 bytes

We don't know until we retrieve it if it's a string or an array here, so I think we have to use this technique instead, as per https://github.com/symfony/symfony/pull/34363/files#diff-0c1327f76881336...

Hardik_Patel_12’s picture

There are some more to check:

core/lib/Drupal/Core/Entity/EntityForm.php
core/modules/block/src/BlockForm.php
core/modules/field_ui/src/Form/FieldConfigEditForm.php
core/modules/locale/src/Form/TranslateFormBase.php
core/modules/settings_tray/src/Block/BlockEntitySettingTrayForm.php

Replacing get() with all() method in all files as listed above , kindly review.

longwave’s picture

Status: Needs review » Needs work
  1. +++ b/core/lib/Drupal/Core/Entity/EntityForm.php
    @@ -238,7 +238,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
    -        $query['destination'] = $this->getRequest()->query->get('destination');
    +        $query['destination'] = $this->getRequest()->query->all()['destination'];
    

    We only need to replace it in the case where we don't know if the value is a scalar or an array. For example I don't think that 'destination' can be an array here?

  2. +++ b/core/modules/block/src/BlockForm.php
    @@ -176,7 +176,7 @@ public function form(array $form, FormStateInterface $form_state) {
    -    $weight = $entity->isNew() ? $this->getRequest()->query->get('weight', 0) : $entity->getWeight();
    +    $weight = $entity->isNew() ? $this->getRequest()->query->all()['weight'] ?? 0 : $entity->getWeight();
    

    Similarly here, 'weight' is a scalar and not an array.

TranslateFormBase is the only one I'm not immediately sure about.

Hardik_Patel_12’s picture

@longwave , i got your point and i have checked in core only TranslateFormBase needs replacement in this case IMO.

andypost’s picture

andypost’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
16.16 KB
16.9 KB

There's more places in 9.1.x - 230 get() on request bags (using phpstorm search)
But we should replace only usage in request, query, cookies which been replaced with new InputBag
See https://github.com/symfony/symfony/pull/34363/files#diff-9d63a61ac1b3720...

Updated IS with proposed resolution

2 tricky places
- \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber::generateContext() I use to add local variable to make condition evaluation the same, because has() could fire for empty array passed in
- \Drupal\media_library\MediaLibraryState (getAllowedTypeIds() and getOpenerParameters() as class extends ParameterBag we can skip modify this methods but in fromRequest() method there's $state->replace($query->all()); which could be incompatible with InputBag from query string

The interdiff as against #4

+++ b/core/modules/locale/src/Form/TranslateFormBase.php
@@ -131,7 +131,7 @@ protected function translateFilterValues($reset = FALSE) {
-        $value = $this->getRequest()->query->get($key);
+        $value = $this->getRequest()->query->all()[$key];
         if (!isset($filter['options']) || isset($filter['options'][$value])) {
           $filter_values[$key] = $value;

No reason to change it, the $value used as array key so it must be string

andypost’s picture

+++ b/core/modules/field_ui/src/Form/FieldConfigEditForm.php
@@ -206,7 +206,7 @@ public function save(array $form, FormStateInterface $form_state) {
-    if (($destinations = $request->query->get('destinations')) && $next_destination = FieldUI::getNextDestination($destinations)) {
+    if (($destinations = ($request->query->all()['destinations'] ?? [])) && $next_destination = FieldUI::getNextDestination($destinations)) {

+++ b/core/modules/field_ui/src/Form/FieldStorageConfigEditForm.php
@@ -224,7 +224,7 @@ public function save(array $form, FormStateInterface $form_state) {
-      if (($destinations = $request->query->get('destinations')) && $next_destination = FieldUI::getNextDestination($destinations)) {
+      if (($destinations = ($request->query->all()['destinations'] ?? [])) && $next_destination = FieldUI::getNextDestination($destinations)) {

+++ b/core/modules/jsonapi/src/EventSubscriber/ResourceResponseSubscriber.php
@@ -144,10 +144,10 @@ protected static function generateContext(Request $request) {
-    if ($request->query->get('fields')) {
+    if ($fields = ($request->query->all()['fields'] ?? [])) {

+++ b/core/modules/menu_ui/src/Controller/MenuController.php
@@ -50,7 +50,7 @@ public static function create(ContainerInterface $container) {
-    if ($menus = $request->request->get('menus')) {
+    if ($menus = ($request->request->all()['menus'] ?? [])) {

extra wrapping parentheses () could be skipped but I've added them for readability

Status: Needs review » Needs work

The last submitted patch, 9: 3162016-9.patch, failed testing. View results

longwave’s picture

+++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
@@ -128,7 +128,7 @@ public function processAttachments(AttachmentsInterface $response) {
-    $ajax_page_state = $request->request->get('ajax_page_state');
+    $ajax_page_state = $request->request->all()['ajax_page_state'] ?? [];

As I understand it, here ajax_page_state will always be an array, in which case we should call ->all('ajax_page_state'). This is a security hardening, so an exception will be thrown if you call get() for an array or all() for a string - we should only use ->all()[...] when we really accept either string or array.

However, all() with an argument also only works from SF 5.1, so should we postpone until then? Otherwise we are bypassing this security improvement and we should add a followup to fix it when we don't need BC with Symfony 4.

longwave’s picture

Or could we add a BC layer here where we call ->all('ajax_page_state') if the object is an InputBag and fall back to ->get('ajax_page_state') for SF4?

andypost’s picture

Status: Needs work » Needs review
FileSize
11.46 KB
16.93 KB

@longwave Thanks for review, missed the point that default for get() is NULL so changed back that hardening and now broken tests passed.

As of all() it's since SF 2.x for ParamerterBag() so BC is kept (also works for 8.9 core) now https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/HttpFo...

Let's see what bot will tell

longwave’s picture

What I meant was that in 5.1 InputBag::all() now takes an argument: https://github.com/symfony/http-foundation/blob/5.1/InputBag.php#L48-L51

    $ajax_page_state = $request->request->all()['ajax_page_state'] ?? NULL;

This is not hardened, ie. if ajax_page_state is a string it will return it, even though we expect an array. As per https://github.com/symfony/symfony/pull/34363 it seems we should only use ->all()[...] if we really do not know if it is an array or a string.

    $ajax_page_state = $request->request->all('ajax_page_state');

Because of the return type on all() this will error if ajax_page_state is a string, but we cannot use this until SF 5.1.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

Gábor Hojtsy’s picture

Title: Since symfony/http-foundation 5.1: Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated, and will throw a "Symfony\Component\HttpFoundation\Exception\BadRequestException" exception in Symfony 6.0, use "Symfon » [Symfony 6] Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated
Issue tags: -Symfony 5 +Symfony 6

Cleaner title and fix tag :)

andypost’s picture

catch’s picture

@longwave #12/#15:

However, all() with an argument also only works from SF 5.1, so should we postpone until then? Otherwise we are bypassing this security improvement and we should add a followup to fix it when we don't need BC with Symfony 4.

InputBag only exists in Symfony 5.1 at all - meaning on Symfony 4 to 5.1, $request->request is either a ParameterBag (no argument for ::all()) or if we're on Symfony >= 5.1 we have an InputBag which accepts the argument. If that's correct, can we not check instanceOf(InputBag) and pass the argument or not depending on that?

If that's wrong somehow and we really can only start using the new API from 10.x onwards (or if it's a lot more complicated to use it in 9.x), I think we should open a follow-up for it and proceed here so there's less to do later.

longwave’s picture

Yeah that is what I suggested in #13, it's a bit messy though as there are a lot of calls.

Alternatively can we wrap/alter the object early in the request cycle to our own forward-compatible subclass?

catch’s picture

OK so three options:

1. Current patch. Forward compatible but not-optimal - will need a follow-up to use the argument.

2. Scatter instanceof around the code base, then remove them again.

3. Subclass either Request or InputBag/ParameterBag or both, and swap this out early for forward compatibility. Update all the calls to use the forward compatible version. Remove the forward-compatibility layer in 10.x

#1 seems preferable to #2. And #3 seems like the best option overall, but IMO we should open a follow-up for that and commit #1 here. The follow-up for #1 would also need to be opened, and #3 will just determine whether it can be committed to 9.x or 10.x

catch’s picture

longwave’s picture

So to test with SF5 this is #14 with #3161889: [META] Symfony 6 compatibility applied but with the two deprecations triggered here explicitly removed. There are two known fails on this patch but if there are no additional fails and #14 passes again then this approach is compatible with both SF4 and SF5.

The last submitted patch, 23: 3162016-sf5-test.patch, failed testing. View results

longwave’s picture

Status: Needs review » Reviewed & tested by the community

This passed as expected (the two fails are unrelated and a known issue) so this is ready to go and then we can work on the followups.

alexpott’s picture

I was writing a big review of this and asking why we weren't using all($param) and then discovered that that is SF5 only. Then I went and read the earlier discussion from @catch and @longwave. Part of me feels like there is another option that feels worth discussing. This would be to add the deprecation to the skipped deprecations and so we can be SF4 and SF5 compat and then do this issue once SF5 is our minimum compatibility. If we want to do something different then my preference would be to add the forward compatibility layer first and then do this issue so once SF5 is the minimum compatibility we only need to remove the forward compatibility layer.

alexpott’s picture

Title: [Symfony 6] Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated » [pp-1] [Symfony 6] Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated
Status: Reviewed & tested by the community » Postponed

Discussed with @catch - we agreed to postpone on #3198594: Forward compatibility layer for InputBag

catch’s picture

#3198594: Forward compatibility layer for InputBag is making good progress so I think it's OK to postpone this on the change there.

I'll go ahead and close #3198596: [PP-1]Update usages of InputBag::all() to take a named parameter since it'll be redundant if we do things this way.

andypost’s picture

Title: [pp-1] [Symfony 6] Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated » [Symfony 6] Retrieving a non-string value from "Symfony\Component\HttpFoundation\InputBag::get()" is deprecated
Status: Postponed » Needs review

Dependency commited

longwave’s picture

Status: Needs review » Needs work

This can now use the new argument to all() where appropriate.

longwave’s picture

Status: Needs work » Needs review

Opened a merge request. There are a handful of cases where we handle both a scalar and an array, so we need to use all()['key'] for those cases.

Once this is green we should try a test patch with SF5 as well.

catch’s picture

Status: Needs review » Needs work

Test failures look real, so moving back to needs work.

longwave’s picture

Status: Needs work » Needs review

Fixed many of the failures, let's see what testbot thinks. MediaLibraryState currently extends from ParameterBag, to change it to extend from InputBag (which is needed for the tests to pass) I had to remove the final from InputBag for now.

andypost’s picture

needs to fix cspell and use

longwave’s picture

Fixed the use statements.

longwave’s picture

Status: Needs review » Needs work

NW for the last two failures.

longwave’s picture

Status: Needs work » Needs review

I think this should be green now.

alexpott’s picture

I think rather than changing the final - we should copy the all() code to MediaLibraryState. If we remove the final then removing the class is harder because someone will extend it like MediaLibraryState.

longwave’s picture

Addressed #39, also reworked MediaLibraryState so it can convert ParameterBags by itself the same way the kernel does, which avoids changing the test.

longwave’s picture

Addressed @catch's point about ParameterBag, I think this is the best solution as it could be either type of InputBag and we only want to replace it if it is not.

catch’s picture

That looks right to me now.

Kristen Pol’s picture

Thanks for the updates. @catch says it looks good and tests pass, so I'm not clear if anything else is needed here, but I took a look anyway. Applied patch and:

1) Checked for getCurrentRequest()->query->get() and found none left.

2) Checked for request->query->get() and found a bunch.

3) Checked for getCurrentRequest()->request->get() and found none left.

4) Checked for request->request->get() and found a bunch.

I'll try to check in with @longwave on this :)

andypost’s picture

I find changes to empty() from isset() strange, or default values require it now?

longwave’s picture

@andypost all() always returns an array, isset() on any array (even empty) is true, so we need to use empty() instead.

longwave’s picture

@andypost we could change many of those isset/empty() checks to ->query->has() which would make it cleaner?

andypost’s picture

@longwave great idea! Surely much clearer

longwave’s picture

Status: Needs review » Needs work

NW for #45-47, will try to get to this this week

longwave’s picture

Status: Needs work » Needs review

Changed empty() checks to use ->has() instead.

Arguably this could be the job of the router; I don't think .routing.yml lets you specify request/querystring parameters that must exist, but it seems like it could be a cleaner way of doing each of these cases - worth opening a followup?

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

Random test failure, not sure what happens with retests on merge requests.

I looked through this again and it all looks fine. Was wondering a bit if it was possible to split out the MediaLibraryState changes from the rest of the patch, but not sure that really helps here - we're already in a multiple-patch process to deal with the InputBag changes.

We didn't have a CR for the overall changes, so I've added one, and linked this issue.

Also opened #3213140: Allow route parameters to be specified as required as a follow-up.

I think this is ready, so marking as such.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

The fail is unfortunately not random.

longwave’s picture

Status: Needs work » Needs review

Thanks for reviews, feedback addressed.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

The remarks of @alexpott have been addressed.
I have updated the CR.
For me it is RTBC.

daffie’s picture

Priority: Normal » Critical

All Symfony 6 issues are critical.

alexpott’s picture

Version: 9.3.x-dev » 9.2.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed 8b8fc1eff7 to 9.3.x and 8f0f57f3e5 to 9.2.x. Thanks!

  • alexpott committed 8b8fc1e on 9.3.x
    Issue #3162016 by longwave, andypost, Hardik_Patel_12, catch, alexpott,...

  • alexpott committed 8f0f57f on 9.2.x
    Issue #3162016 by longwave, andypost, Hardik_Patel_12, catch, alexpott,...
chr.fritsch’s picture

This change is breaking the entity_reference_action module. I have a fix for the module #3216944: Saving modals doesn't work anymore, but maybe it should be fixed here.

alexpott’s picture

Status: Fixed » Needs work

#3216944: Saving modals doesn't work anymore shows that we have broken sub-requests... I'm going to revert this so 9.2.x is not affected and perhaps we can work out a solution that works for sub-requests to.

  • alexpott committed 9306f06 on 9.3.x
    Revert "Issue #3162016 by longwave, andypost, Hardik_Patel_12, catch,...

  • alexpott committed f2cee2d on 9.2.x
    Revert "Issue #3162016 by longwave, andypost, Hardik_Patel_12, catch,...
huzooka’s picture

Re #60 Isn't this a great opportunity to test them as well? Or it is out of scope?

alexpott’s picture

@huzooka we definitely need to add test coverage of what broke the contrib module.

longwave’s picture

I think the underlying issue here is #3198594: Forward compatibility layer for InputBag - there we swap InputBag in place of ParameterBag on the main request only, then this issue depends on that one, but the swap doesn't cover the case of subrequests.

longwave’s picture

Status: Needs work » Needs review
FileSize
3.75 KB

So maybe we can do something like this, as long as others use Request::create() and not new Request() I think this will work.

longwave’s picture

Oh, except we already override the request factory in DrupalKernel::setupTrustedHosts().

catch credited Chi.

catch’s picture

Crediting Chi from two much older, but related issues (Drupal not deciding whether it expects an array, string, or both).

Berdir’s picture

Status: Needs review » Needs work

#67 means this is needs work if I understand this correctly. I'd assume that we would need to duplicate that logic within there too?

longwave’s picture

Version: 9.2.x-dev » 9.3.x-dev
Status: Needs work » Needs review
FileSize
3.47 KB
7.6 KB
3.85 KB

Addressed #67. The TrustedHostsRequestFactory logic is only triggered if trusted hosts are set up, so I extended the existing functional test for this case.

The last submitted patch, 71: 3162016-71-test-only.patch, failed testing. View results

daffie’s picture

Status: Needs review » Needs work

The patch looks good. Just one remark:

+++ b/core/modules/system/tests/src/Functional/System/TrustedHostsTest.php
@@ -111,4 +111,26 @@ public function testShortcut() {
+    $host = $this->container->get('request_stack')->getCurrentRequest()->getHost();
+    $settings['settings']['trusted_host_patterns'] = (object) [
+      'value' => ['^' . preg_quote($host) . '$'],
+      'required' => TRUE,
+    ];
+
+    $this->writeSettings($settings);

Why is this added to the test. I think we can remove it.

longwave’s picture

Status: Needs work » Needs review

See #71. TrustedHostsRequestFactory is only initialised at all if a host pattern is set, but by default it is empty. See DrupalKernel::initializeSettings():

    $host_patterns = Settings::get('trusted_host_patterns', []);
    if (PHP_SAPI !== 'cli' && !empty($host_patterns)) {
      if (static::setupTrustedHosts($request, $host_patterns) === FALSE) {

So we need to explicitly set a trusted_host_patterns key in the test setup for this code path to be exercised. We could prove this in the test by throwing an exception from the test controller if trusted host patterns are empty, I guess?

daffie’s picture

@longwave: When I run the test on my local machine the test passes. It also passes when I remove the part from #73. That is why I was asking if it could be removed. If you want to commit this with the part, then I will change the status to RTBC.

longwave’s picture

The test passes, but it is no longer testing one of the code paths where a fix is required in this issue.

If you also remove the change from TrustedHostsRequestFactory then the test will still pass. But then if you put the part from #73 back, and keep the change out of TrustedHostsRequestFactory, then the test should fail.

daffie’s picture

Status: Needs review » Reviewed & tested by the community

@longwave: Thank you for the explanation.

The change for using the The Drupal forward compatibility class for Symfony 5 (Drupal\Core\Http\InputBag) has been moved from the class Drupal\Core\DrupalKernel to file bootstrap.inc. In this way sub-requests also use the forward compatibility class.
There is testing added.
For me it is RTBC.

longwave’s picture

Note to committers: the merge request was the reverted version of this code - the patch in #71 is the updated version with the subrequest fix.

longwave’s picture

Status: Reviewed & tested by the community » Needs work

There is a subtle bug in #71 actually:

+++ b/core/lib/Drupal/Core/Http/TrustedHostsRequestFactory.php
@@ -61,7 +61,17 @@ public function createRequest(array $query = [], array $request = [], array $att
+      if (!($bag instanceof SymfonyInputBag)) {

This class needs a use statement.

longwave’s picture

Status: Needs work » Needs review
FileSize
7.76 KB
461 bytes
daffie’s picture

Status: Needs review » Reviewed & tested by the community

Use-statement added.
Back to RTBC.

catch’s picture

+++ b/core/modules/system/tests/src/Functional/System/TrustedHostsTest.php
@@ -111,4 +111,26 @@ public function testShortcut() {
 
+  /**
+   * Tests that the request bags have the correct classes.
+   *
+   * @see \Drupal\Core\Http\TrustedHostsRequestFactory
+   */
+  public function testRequestBags() {
+    $this->container->get('module_installer')->install(['trusted_hosts_test']);
+
+    $host = $this->container->get('request_stack')->getCurrentRequest()->getHost();
+    $settings['settings']['trusted_host_patterns'] = (object) [
+      'value' => ['^' . preg_quote($host) . '$'],
+      'required' => TRUE,
+    ];
+
+    $this->writeSettings($settings);
+
+    foreach (['request', 'query', 'cookies'] as $bag) {
+      $this->drupalGet('trusted-hosts-test/bag-type/' . $bag);
+      $this->assertSession()->pageTextContains('InputBag');
+    }
+  }
+

Does this need a @todo to remove once we require Symfony >=5 or are we leaving it as a permanent test. It looks temporary but I could be missing something.

+++ b/core/tests/Drupal/Tests/Core/Http/InputBagTest.php
@@ -28,4 +29,12 @@ public function testAll() {
 
+  /**
+   * @coversNothing
+   */
+  public function testRequestFactory() {
+    $request = Request::create('');
+    $this->assertInstanceOf(InputBag::class, $request->query);
+  }
+

Same question I think.

longwave’s picture

FileSize
7.89 KB
1.1 KB

Added @todos.

andypost’s picture

I bet both todos need links to get rid of them

  • catch committed 022284b on 9.3.x
    Issue #3162016 by longwave, andypost, Hardik_Patel_12, catch, alexpott,...
catch’s picture

Status: Reviewed & tested by the community » Fixed

In this case I think it's OK - we'll probably grep the codebase for 'Symfony 4' when we drop support.

Committed 022284b and pushed to 9.3.x. Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.