Problem/Motivation

In the code for UrlHelper::buildQuery() is the comment:

   * @todo Remove this function once PHP 5.4 is required as we can use just
   *   http_build_query() directly.

Since we now require PHP 5.4, we should proceed with this.

Proposed resolution

For now replace the guts of the method with the built-in functional call.

Remaining tasks

make patch, see if any tests fail

User interface changes

N/A

API changes

N/A

CommentFileSizeAuthor
#47 224857-47.interdiff.txt5.61 KBneclimdul
#47 224857-47.patch21.25 KBneclimdul
#44 224857.patch18.13 KBneclimdul
#41 optimize-2248257-41.patch21.97 KBidimopoulos
#37 optimize-2248257-37.patch15.54 KBidimopoulos
#37 interdiff.txt1.85 KBidimopoulos
#36 optimize-2248257-36.patch15.99 KBidimopoulos
#27 optimize-2248257-27.patch14.74 KBneclimdul
#27 optimize-2248257-27.interdiff.txt1.06 KBneclimdul
#27 http_build_query_benchmarks.txt1.44 KBneclimdul
#27 http_build_query_benchmarks.png19.57 KBneclimdul
#27 http_build_query_benchmark.php_.txt2.63 KBneclimdul
#26 optimize-2248257-26.patch14.79 KBneclimdul
#26 optimize-2248257-26.interdiff.txt8.78 KBneclimdul
#24 optimize-2248257-24.patch6 KBneclimdul
#24 optimize-2248257-24.interdiff.txt3.13 KBneclimdul
#22 2248257-22.interdiff.txt3.01 KBneclimdul
#22 optimize-2248257-22-test-only.patch2.28 KBneclimdul
#22 optimize-2248257-22.patch4.38 KBneclimdul
#19 2248257-19.patch1.81 KBdagmar
#13 2248257-13.patch5.33 KBpwolanin
#13 increment.txt3.57 KBpwolanin
#1 2248257-1.patch1.76 KBpwolanin
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

pwolanin’s picture

Issue summary: View changes
Status: Active » Needs review
FileSize
1.76 KB

This outputs the brackets urlencoded too, which is a little annoying.

Status: Needs review » Needs work

The last submitted patch, 1: 2248257-1.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review

1: 2248257-1.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 1: 2248257-1.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review

1: 2248257-1.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 1: 2248257-1.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review

1: 2248257-1.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 1: 2248257-1.patch, failed testing.

The last submitted patch, 1: 2248257-1.patch, failed testing.

The last submitted patch, 1: 2248257-1.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review

1: 2248257-1.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 1: 2248257-1.patch, failed testing.

pwolanin’s picture

Status: Needs work » Needs review
FileSize
3.57 KB
5.33 KB

http://tools.ietf.org/html/rfc3986 and related discussed suggest the PHP built-in escaping is correct in terms of the spec.

Patch with test fixes. neclimdul suggested changing it so we just test against the PHP function in case someone changes the wrapper.

tim.plunkett’s picture

Status: Needs review » Needs work
  1. +++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
    @@ -54,8 +55,8 @@ public function providerTestBuildQuery() {
    +    $this->assertEquals(http_build_query($query, '', '&', PHP_QUERY_RFC3986), UrlHelper::buildQuery($query), $message);
    

    How is this asserting *anything*? It's just the implementation compared to the method using that implementation. The $expected should not be removed, IMO

  2. +++ b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
    @@ -297,16 +297,16 @@ public function testPathBasedURLGeneration() {
    -        $url = $base . 'node/123?foo';
    ...
    +        $url = $base . 'node/123?foo=';
    ...
    -        $url = $base . 'node/123?foo#bar';
    ...
    +        $url = $base . 'node/123?foo=#bar';
    

    Isn't this the entire reason why we have this implementation? Those are ugly.

neclimdul’s picture

1) Yeah, so I mentioned we should remove it since we don't want to be in the business of testing php's code. That's the job of php's test suite. However if we're concerned about coverage then we could do that. It basically just confirms we're calling it correctly I guess... I'm not really sure how to test this to be honest.

2) Yeah... those are ugly. Cleaning them up seems likely to be a beast though since we get back a string.

Looking at the changes it seems we're also introducing a "regression" in not supporting array('foo' => NULL) and requiring array('foo' => ''). I'm actually OK with this unless there are some deeper implications I can't think of.

pwolanin’s picture

We can cut the pointless tests for sure - my only thought to leave it was as a regression test if someone tries to change the implementation?

So, in terms of omitting NULL values, PHP itself parses a query key with no = as having an empty string value. So ?x and ?x= are both found in $_GET['x'] as ''

While this is slightly ugly, are any en users going to care about the rare query string with a trailing = that didn't have it before?

tim.plunkett’s picture

  1. +++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
    @@ -34,11 +34,12 @@ public static function getInfo() {
    -      array(array('a' => ' &#//+%20@۞'), 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.'),
    -      array(array(' &#//+%20@۞' => 'a'), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.'),
    -      array(array('a' => '1', 'b' => '2', 'c' => '3'), 'a=1&b=2&c=3', 'Multiple values were properly concatenated.'),
    -      array(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo'), 'a[b]=2&a[c]=3&d=foo', 'Nested array was properly encoded.'),
    -      array(array('foo' => NULL), 'foo', 'Simple parameters are properly added.'),
    +      array(array('a' => ' &#//+%20@۞'), 'Value was properly encoded.'),
    +      array(array(' &#//+%20@۞' => 'a'), 'Key was properly encoded.'),
    +      array(array('a' => '1', 'b' => '2', 'c' => '3'), 'Multiple values were properly concatenated.'),
    +      array(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo'), 'Nested array was properly encoded.'),
    +      array(array('foo' => NULL), 'NULL parameters are omitted.'),
    +      array(array('foo' => ''), 'Simple parameters are properly added.'),
    
    @@ -54,8 +55,8 @@ public function providerTestBuildQuery() {
    +    $this->assertEquals(http_build_query($query, '', '&', PHP_QUERY_RFC3986), UrlHelper::buildQuery($query), $message);
    

    This change is not ideal. We're now essentially testing that calling a method two different times will give the same results. Please revert!

  2. +++ b/core/tests/Drupal/Tests/Core/Routing/UrlGeneratorTest.php
    @@ -297,16 +297,16 @@ public function testPathBasedURLGeneration() {
    -        $url = $base . 'node/123?foo';
    -        $result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => NULL), 'absolute' => $absolute));
    +        $url = $base . 'node/123?foo=';
    +        $result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => ''), 'absolute' => $absolute));
    ...
    -        $url = $base . 'node/123?foo#bar';
    -        $result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute));
    +        $url = $base . 'node/123?foo=#bar';
    +        $result = $this->generator->generateFromPath('node/123', array('query' => array('foo' => ''), 'fragment' => 'bar', 'absolute' => $absolute));
    

    We shouldn't have to change our tests :(

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

dagmar’s picture

Version: 8.1.x-dev » 8.2.x-dev
Status: Needs work » Needs review
FileSize
1.81 KB

Re-rolled, doesn't applied anymore. Let's start again from #1 I can't see the results from the logger two years after this patch was tested.

Status: Needs review » Needs work

The last submitted patch, 19: 2248257-19.patch, failed testing.

neclimdul’s picture

Oh goodness we have that much code still doing string matches :(

neclimdul’s picture

This will still fail but it fixes UrlHelperTest. Similar to pwolanin's approach but slightly different. It looks like it changed more but it actually changed less I think. Every case is covered as it was before. By comparing that the result of the method decodes to the same value we're testing the intent of the method without testing the exact string output. I attached the test change alone to confirm its testing and passing the same without the optimization.

The gotcha here is parse_str(), which decodes query strings, drops leading spaces from keys for some crazy reason. As best I can tell we have to string compare on this one but luckily the output of the 2 implementation is identical for this case.

I also hacked the ['foo' => NULL]; logic in as you'll see from the interdiff so that test case is also unchanged. I'd prefer we stick with http_build_query() directly since we documented that as the goal of the method but we should also keep with our API promise and be consistent across minor releases. Especially if we have a test that caught the change. We can change that in 9 (or remove entirely).

Undoubtedly the other string matches like in bigpipe are going to still fail though. :(

The last submitted patch, 22: optimize-2248257-22.patch, failed testing.

neclimdul’s picture

+++ b/core/lib/Drupal/Component/Utility/UrlHelper.php
@@ -42,27 +36,16 @@ class UrlHelper {
-        // For better readability of paths in query strings, we decode slashes.
-        $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));

Neat fact, this was casting objects. http_build_query() doesn't do that. Turns out by accident we had coverage for this "functionality" because of t() and safestring. Another back ported functionality added. Added explicit unit test since that's probably an important usability improvement.

Status: Needs review » Needs work

The last submitted patch, 24: optimize-2248257-24.patch, failed testing.

neclimdul’s picture

Status: Needs work » Needs review
FileSize
8.78 KB
14.79 KB

The bigpipe method that builds the ids is protected so we can't call it in the test without modifying the class :(

This uses our helpers though instead of hardcoding the strings though so its a bit better off.

neclimdul’s picture

Doing some benchmarks I realized the if statements where unnecessary and its a bit faster to just cast everything. string casting a string is significantly cheaper then applying the 2 if statements.

The benchmarks are a bit of a mixed bag though. We see huge improvements in 5.x php but in 7 we are just a smidge slower in most cases. The exception being nested data which is always a lot faster. Seems the array walk is a bit slower then the loop for some reason that might be worth investigating.

dagmar’s picture

The bigpipe method that builds the ids is protected so we can't call it in the test without modifying the class :(

@neclimdul I assume you already know this but, have you considered use reflection to test private methods?

neclimdul’s picture

I'd avoid using reflection for that since its pretty heavy. Much easier to create a stub and expose the method through it. Definitely possible though. I went with the hacky solution since I planned on making a follow up to discuss making it public.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.0-beta1 was released on August 3, 2016, which means new developments and disruptive changes should now be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.3.x-dev » 8.4.x-dev

Drupal 8.3.0-alpha1 will be released the week of January 30, 2017, which means new developments and disruptive changes should now be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.4.x-dev » 8.5.x-dev

Drupal 8.4.0-alpha1 will be released the week of July 31, 2017, which means new developments and disruptive changes should now be targeted against the 8.5.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.5.x-dev » 8.6.x-dev

Drupal 8.5.0-alpha1 will be released the week of January 17, 2018, which means new developments and disruptive changes should now be targeted against the 8.6.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.6.x-dev » 8.7.x-dev

Drupal 8.6.0-alpha1 will be released the week of July 16, 2018, which means new developments and disruptive changes should now be targeted against the 8.7.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

idimopoulos’s picture

  1. +++ b/core/lib/Drupal/Component/Utility/UrlHelper.php
    @@ -42,27 +36,16 @@ class UrlHelper {
    +    // Do some conversions to match previous implementations.
    +    array_walk_recursive($query, function (&$value, $key) {
    +      // Previous versions of Drupal created query arguments with NULL values
    +      // but http_build_query throws them away. It also throws away objects.
    +      // As a convenience and backward compatibility we convert all values to a
    +      // string.
    +      $value = (string) $value;
    +    });
    

    I am not sure if this is correct.
    I have tested the http_build_query with booleans and it converts them to numbers. 1 for true and 0 for false.
    This will convert the false to an empty string.
    This will keep the ::buildQuery being different that the http_build_query itself.

  2. +++ b/core/modules/system/src/Tests/Database/SelectTableSortDefaultTest.php
    @@ -32,8 +32,8 @@ function testTableSortQuery() {
    +      $this->assertEqual($first->task, $sort['first'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort'])));
    +      $this->assertEqual($last->task, $sort['last'], format_string('Items appear in the correct order sorting by @field @sort.', array('@field' => $sort['field'], '@sort' => $sort['sort'])));
    

    format_string is now deprecated. This should be updated to use the FormattableMarkup class.

  3. +++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
    @@ -18,13 +19,12 @@ class UrlHelperTest extends UnitTestCase {
    +    return [
    +      [['a' => ' &#//+%20@۞'], 'Value was properly encoded.'],
    +      [['a' => '1', 'b' => '2', 'c' => '3'], 'Multiple values were properly concatenated.'],
    +      [['a' => ['b' => '2', 'c' => '3'], 'd' => 'foo'], 'Nested array was properly encoded.'],
    +      [['foo' => NULL], 'Simple parameters are properly added.'],
    +    ];
    

    We need to also test the boolean values here.
    I will add one more case.

  4. +++ b/core/tests/Drupal/Tests/Component/Utility/UrlHelperTest.php
    @@ -35,13 +35,25 @@ public function providerTestBuildQuery() {
    +    parse_str(UrlHelper::buildQuery($query), $result);
    +    $this->assertEquals($query, $result, $message);
    ...
    +   */
    

    I have an issue with the boolean values here.
    While the other cases work fine, using ['autoplay' => FALSE, 'fullscreen' => TRUE].

    The UrlHelper::buildQuery($query) returns autoplay=&fullscreen=1.
    The http_build_query returns autoplay=0&fullscreen=1.

    The second problem is the parse_str. It will also convert the query into the following array:

    Array
    (
        [autoplay] => 
        [fullscreen] => 1
    )
    

    Even if this is not the same as the initial array, the $this->assertEquals is considering them equals.
    In addition, if I try to work around it and end up having autoplay = 0, the assertion will fail because string 0 is not equals to false.

    IMO we should not test the query encoding by parsing the string. The query built is not obliged to be decoded since multiple values might be converted to the same result (e.g. both 0 and false are converted to "0" by http_build_query).

I am re-rolling the patch fixing also the issues detected here. I will follow the previous philosophy of keeping the array_walk before doing the http_build_query but I think we should not workaround in order to provide results different that what php would do.

idimopoulos’s picture

I would like to, however, see what failures are there if we consider a proper solutions that query parameters with NULL are removed (just like http_build_query does).

Status: Needs review » Needs work

The last submitted patch, 37: optimize-2248257-37.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

claudiu.cristea’s picture

@neclimdul, @idimopoulos instead of adapting this function that will be dropped anyway, why not replacing all occurrences from core with http_build_query()? Of course, on each usage we'll have to account, if case, the different output. Then we deprecate UrlHelper::buildQuery().

claudiu.cristea’s picture

Status: Needs work » Needs review
Issue tags: -Performance, -PHP 5.4

(deleted)

idimopoulos’s picture

@claudiu tbh, I also like that idea. I am attaching a small patch to check how tests will behave. I replaced the usage of the ::buildQuery and cleaned up the test. I also deprecated the method.

Status: Needs review » Needs work

The last submitted patch, 41: optimize-2248257-41.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

claudiu.cristea’s picture

It seems that was a decision or, at least, a discussion to keep this method as it is because of the different output. See #2322059-50: Keep the UrlHelper::buildQuery method and remove @todo. It's likely that both issues will be "won't fix". Let's put on hold the work here till something is decided.

neclimdul’s picture

Status: Needs work » Needs review
FileSize
18.13 KB

It sounds like its being overlooked still that buildQuery does object casting which http_build_query fails to do. This means if you have a stringable object in your query like a markup string or as the new test I added demonstrates an Attribute it fails to produce the correct output.

Granted this is super easy to miss and undocumented but this was a breaking change to core I documented in my patch and in the early comments. Getting rid of this method is likely to be a strangely breaking change I'm not sure makes sense.

Here's a re-roll of the earlier functioning patch. Also adding back my credit... not sure why that was removed.

Status: Needs review » Needs work

The last submitted patch, 44: 224857.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

neclimdul’s picture

Status: Needs review » Needs work

The last submitted patch, 47: 224857-47.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

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

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

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.

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.

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

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

joshuautley’s picture

With the recent release of Drupal 10 are there any other maintainers of core considering this for an upcoming minor version release?

Reference https://www.drupal.org/project/commerce_usps/issues/3326069#comment-1483...

With the holidays in full swing, this has become a hot topic.

Thank you everyone for your time.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.