Problem/Motivation

\Drupal\Component\Utility\Bytes::toInt() passes a string value to round(). In PHP 8 this will trigger an error.

Proposed resolution

Cast the $size value to an integer or a float as appropriate.

Remaining tasks

User interface changes

None

API changes

None

Data model changes

None

Release notes snippet

N/a

Comments

alexpott created an issue. See original summary.

alexpott’s picture

Status: Active » Needs review
StatusFileSize
new728 bytes
krzysztof domański’s picture

Status: Needs review » Needs work

1. The round() returns float (or false).

 * @return float|false The rounded value
 */
function round ($val, $precision = 0, $mode = PHP_ROUND_HALF_UP) {}

2. It doesn't matter if we pass an integer (20) or a float (20.0) to round().

-    // Ensure size is a proper number type.
-    $size = strpos($size, '.') !== FALSE ? (float) $size : (int) $size;
+    // A number should be passed to the round() since PHP 8.
+    $size = (float) $size;
krzysztof domański’s picture

or much easier

     else {
-      return round($size);
+      // A number should be passed to the round() since PHP 8.
+      return round((float) $size);
     }
   }
andypost’s picture

alexpott’s picture

Status: Needs work » Needs review
StatusFileSize
new538 bytes
new1.03 KB

@andypost i disagree that those changes mean we need to change the fix here. Firstly the numeric literal separator will be stripped by this code:

    // Remove the non-unit characters from the size.
    $unit = preg_replace('/[^bkmgtpezy]/i', '', $size);

And there's no need to change that behaviour here. And then

    // Remove the non-numeric characters from the size.
    $size = preg_replace('/[^0-9\.]/', '', $size);

means that we don;t need to do anything for string to number changes.

Here's the change that @Krzysztof Domański suggests and yep it is a bit neater. Doing less work in most cases. I've added a couple of test cases to prove nothing has changed. Therefore the test only patch will not fail. And yep we should fix #3142934: Replace \Drupal\Component\Utility\Bytes::toInt() with \Drupal\Component\Utility\Bytes::toNumber() due to return type sometime soon.

andypost’s picture

+++ b/core/tests/Drupal/Tests/Component/Utility/BytesTest.php
@@ -56,6 +56,8 @@ public function providerTestToInt() {
+      ['1.5', 2],
+      ['2.4', 2],

test-only passes

alexpott’s picture

@andypost it’s meant to - it proves that the changed code path has not changed functionally.

krzysztof domański’s picture

Status: Needs review » Reviewed & tested by the community
andypost’s picture

Status: Reviewed & tested by the community » Needs review

I disagree with change - PHP properly threads floats as strings (after preg_replace)

The code shows that PHP 8 throws notice in case of wrongly formatted numeric-string

/srv # php -v
PHP 8.0.0alpha3 (cli) (built: Jul 23 2020 19:54:36) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.0-dev, Copyright (c) Zend Technologies
    with Zend OPcache v8.0.0alpha3, Copyright (c), by Zend Technologies
/srv # php8 -r "var_dump(round('12.2'));"
float(12)
/srv # php8 -r "var_dump(round('12,2'));"
PHP Notice:  A non well formed numeric value encountered in Command line code on line 1

Notice: A non well formed numeric value encountered in Command line code on line 1
float(12)

but the later does not notice in PHP 7.4

/srv # php -v
PHP 7.4.8 (cli) (built: Jul 15 2020 15:47:18) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.8, Copyright (c), by Zend Technologies
/srv # php -r "var_dump(round('12,2'));"
float(12)

It means this cast just hides error, the same happening in #3156878-3: \Drupal\Component\Datetime\DateTimePlus should pass correct parameter types to checkdate()

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

@andypost it's not an error. We are maintaining behaviour that's the best we can do. I'm not sure that people passing 12,2 MB into this method need us to work around PHPs funky number handling.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work

Also more importantly keeps 12,2 bytes behaving the same as 12,2. However running that - https://3v4l.org/XcqDT show we does have a problem - and that #2 was the correct fix.

alexpott’s picture

Status: Needs work » Reviewed & tested by the community

Actually ignore #13. dreditor--

    // Remove the non-unit characters from the size.
    $unit = preg_replace('/[^bkmgtpezy]/i', '', $size);
    // Remove the non-numeric characters from the size.
    $size = preg_replace('/[^0-9\.]/', '', $size);

There will NEVER be a comma in $size #11 is incorrect.

#7 is still a good fix. See https://3v4l.org/fQvXm

alexpott’s picture

@andypost asked why do we need the cast - PHP is doing this for us. Here's why - https://3v4l.org/YpYAQ

The test on DrupalCI that is failing is due to this is Drupal\Tests\editor\Functional\EditorDialogAccessTest::testEditorImageDialogAccess - see https://dispatcher.drupalci.org/job/drupal_patches/51890/testReport/juni...

I still think the fix in #7 is fine.

Compare:

Therefore #7 preserves existing behaviour and warnings and looks to be the correct way forward.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review
StatusFileSize
new453 bytes
new1.05 KB

Here's #15 as a test so we have test coverage for the empty string behaviour as the Editor module makes extensive use of it. Search for 'max_size' => '' and you'll see it plenty.

krzysztof domański’s picture

Status: Needs review » Reviewed & tested by the community

I discovered an additional problem looking at https://3v4l.org/OUJaH bytes_to_int('12,2'); returnsfloat(122).

I created follow-up. See #3161729: Bytes::toNumber($size) will throw warning is $size failed to parse and #3161730: [META] Refactor \Drupal\Component\Utility\Bytes::toInt().

  • larowlan committed ff72265 on 9.1.x
    Issue #3156879 by alexpott, Krzysztof Domański: \Drupal\Component\...

  • larowlan committed 14e5691 on 9.0.x
    Issue #3156879 by alexpott, Krzysztof Domański: \Drupal\Component\...
larowlan’s picture

Title: \Drupal\Component\Utility\Bytes::toInt() - ensure $size is a number type » [backport] \Drupal\Component\Utility\Bytes::toInt() - ensure $size is a number type
Version: 9.1.x-dev » 8.9.x-dev

Committed ff72265 and pushed to 9.1.x. Thanks!

c/p to 9.0.x

Leaving open for discussion about backporting

larowlan’s picture

Issue tags: +Bug Smash Initiative
alexpott’s picture

We are definitely going to need this to PHP 8 compatibility in Drupal 8 so +1 for backporting.

alexpott’s picture

  • larowlan committed 46ee6c8 on 8.9.x
    Issue #3156879 by alexpott, Krzysztof Domański: \Drupal\Component\...
larowlan’s picture

Title: [backport] \Drupal\Component\Utility\Bytes::toInt() - ensure $size is a number type » \Drupal\Component\Utility\Bytes::toInt() - ensure $size is a number type
Status: Reviewed & tested by the community » Fixed

C/p to 8.9.x - thanks

Status: Fixed » Closed (fixed)

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

rondog469’s picture

This patch should have probably included a fix for the line above the else:

return round($size * pow(self::KILOBYTE, stripos('bkmgtpezy', $unit[0]))); is returning the error: TypeError: Unsupported operand types: string * int in Drupal\Component\Utility\Bytes::toNumber()

rondog469’s picture

apologies, this issue was for Bytes::toInt which is now deprecated. My issue is with the Bytes::toNumber function albeit it still applies.

mondrake’s picture

@rondog469 mind opening a new issue with your report? Unlikely anything will happen on an issue that has been closed for 3 years.

rondog469’s picture

@mondrake Yup I just did and I linked back to this one for reference purposes https://www.drupal.org/project/drupal/issues/3352728