Problem/Motivation
\Drupal\Component\Utility\Bytes::toInt() always returns a float. This is because it uses the round() function which always returns a float. The name is misleading because suggests that it returns an integer.
Per the documentation it should return integer value but in fact it always returns float.
https://github.com/drupal/drupal/blob/9.0.0-rc1/core/lib/Drupal/Componen...
Proposed resolution
Rename method Bytes::toInt() to Bytes::toNumber() that always returns a float.
Deprecate \Drupal\Component\Utility\Bytes::toInt() for removal in D10.
Remaining tasks
User interface changes
API changes
Data model changes
Release notes snippet
| Comment | File | Size | Author |
|---|---|---|---|
| #82 | interdiff-81-82.txt | 667 bytes | jungle |
| #82 | 3142934-82.patch | 14.15 KB | jungle |
Comments
Comment #2
jyotimishra-developer commentedHi @Chi,
Hope you will fine!!
I am not getting what needs to be done here? can you pls elaborate, if you dont mind?
Comment #3
mondrakeIn
add a casting to int before returning a value, i.e.
that's the right fix IMO, we can't do anything else in a method named
toInt...Comment #4
sivaji_ganesh_jojodae commentedPatch attached to update the documentation.
Comment #5
mondrakeI definitely struggle on #4 :)
Comment #6
mondrakeAlso, we need an automated test (or an integration to an existing one?) to ensure we do not incur in regressions.
Comment #7
sivaji_ganesh_jojodae commentedComment #8
chi commentedGiven that Drupal 8+ does not support PHP 5 anymore why not just add
inttype hint to that method? That would make casting automatically.Comment #9
mondrake#8 that won't be enough as it would just fail badly if the returned value is not int, so the casting is necessary anyway (
round()returns afloatalways). Besides that, adding a typehint is also a bigger problem because any class extending from that would fail miserably at syntax checking - a BC issue.Comment #11
mondrakeBut... I am afraid here we're hitting another problem, i.e.
intare not sufficient for representing zettabytes and yottabytes...Comment #12
chi commentedIt would not.
https://3v4l.org/PmrWm
That's correct. Though I cannot think of any good reason to extend such a small class with a single static method.
Comment #13
mondrake#12 try ‘1 YB’
Comment #14
chi commented#13 That's true, but with explicit casting it returns 0 for '1 YB', which is not much better )
Comment #15
mondrake#14 agree. Then we have to make the casting conditional on the result being below PHP_INT_MAX. Below return int, above leave a float. And needs to be documented and tested.
Comment #16
mondrakeon this
Comment #17
mondrakeDone #15.
Comment #18
mondrakeComment #19
krzysztof domańskiIn this case, casting data types should be safe. It replaces the rounded float (e.g. 10.0 -> 10).
Comment #20
longwaveThis is just copying the code under test, which doesn't make it a very good test. Can we add the type to the data provider somehow? Or at least call assertIsFloat/assertIsInt and go back to assertEquals instead of casting here?
Comment #21
xjmOK, this issue title made me laugh.
I think @longwave raises a good point; NW to look into that.
Comment #22
chi commentedConditional type casting will not make
toInt()truly totoInt(). Would it be better just through an exception when rounded value exceeds PHP_INT_MAX?Comment #23
sivaji_ganesh_jojodae commentedMaybe it is better to add another method
toLong()ortoFloat()for the caller to use in the cases like 1 YB.+1 for
toInt()to thrown an exception if it exceeds PHP_INT_MAX.Comment #24
mondrakeOn this.
Throwing an exception here would be a behavior change and hence a BC break... I understand not everybody is uploading zettabyte sized files everyday, but still... :) (that's also a reason IMHO this is a minor issue)
So if we want to do that, we would have to deprecate first, etc. etc.
Comment #25
mondrakeLet's see if this is better, from the test side - force the number literals in the data provider.
Comment #26
sivaji_ganesh_jojodae commentedPatch looks visually fine to me but returning float by a method named
toInt()doesn't seem contextual. Maybe the method needs renaming?Comment #27
mondrake@Sivaji per Drupal policy we can't just rename a method, unless it's a private one, or marked @internal (and even in those cases I see debate). We would need to add a new
toFloat()method instead, deprecatetoInt(), and file a follow-up to remove in D10. But that would be a separate issue IMHO. Again, I think 99.999999% of the cases we never really get to convert ZB or YB in real life.Comment #28
mondrakeSo in #25 we learn that large literal numbers cannot be parsed if above PHP_INT_MAX... interesting. Working out something different.
Comment #29
mondrakeFollowing @longwave suggestion in #20.
Comment #30
sivaji_ganesh_jojodae commented@mondrake, agreed.
Can I create a new issue to add a new method & deprecate the toInt() in D10?
I suggest naming it to
toByte()to allow returning int or float as needed.Comment #31
mondrake@Sivaji sure thing! Everybody can, this is the sense of open source :)
Comment #32
longwaveI meant something more like
Can we assume we are always testing on 64 bit systems (in which case we know for sure which test cases will be float and which will be int), or is that too much of an assumption to make?
Comment #33
mondrake#32 AFAIK
assertSamealso checks the type, so isn't that superflous?Also about 64-bit I think that's too strong an assumption - fair enough on DrupalCI will be like that, but locally not.
Maybe we can use PHP_INT_SIZE in the test data provider and make the list of expected return values dependent on that, instead: https://stackoverflow.com/questions/670662/whats-the-maximum-value-for-a...
OTOH I'm afraid on 32-bit the parsing will fail for 64-bit only number literals, same way as in #25 the number 1180591620717411303424, which is 1 ZB, fails even on 64-bit.
Comment #34
longwaveActually, can we simply use assertSame without casting at all in the test?
Comment #35
longwaveComment #36
longwaveComment #38
mondrakeNice and clean, @longwave!
RTBC +1, but I cannot.
Comment #39
krzysztof domański1. It looks good to me too. Attaching fix patch (#35) as the latest attachment to be tested by the bot (retested daily).
2. In this case, casting data types should be safe. It always replaces the rounded float (e.g. 10.0 -> 10).
Comment #41
krzysztof domańskiRandom test failure.
Comment #42
xjmI haven't read this code in a really long time but am struck again that
bkmgtpezysounds like a made-up creature in childrens' literature à la Lewis Carroll. "All mimsy were the bkmgtpezy, and the mome raths outgrabe."Shouldn't we be testing both types of return now -- int and float? The data provider is all int-y return things. We're solving part of the test coverage problem with the
assertSame()but not all of it I think. It could be just one more case with a SRSLY LARGE NUMBER in the data provider.Looks good other than that; I think we came 'round to a good place. Thanks!
Comment #43
longwaveAs demonstrated in #34, zettabytes and yottabytes don't fit in a 64 bit int and the pow() function automatically returns a float (double) for these. assertSame checks the type which ensures our method also returns int for the smaller sizes and float for the larger ones. Maybe we need a comment to this effect though?
Comment #44
longwaveAdded some explanatory comments.
Comment #45
mondrakeI think this is the maximum we can do with a method called
toIntthat has to handle non-ints. A follow up could be to deprecate this method in favour of a, say,toNumbermethod always returning a float.Comment #46
xjmThat works for me.
Turns out, we need to add the words
zettabyteandzettabytesto core's dictionary for the spellchecking we just added. (Oddly enough, "yottabytes" is already there.)Comment #47
krzysztof domańskiComment #48
jungleAfter running the spellcheck commands on my local, found out we only need to add
zettabytes. 1) Asyottabytesis not a unknown word, I think that it's better to havezettabytesadded to a upstream dictionary. 2) Meanwhile, we are in progress of removing typos/words from the dictionary file, so I tend to ignore it by usingcspell:ignore, not adding it to the dictionary.Attached patch ignored
zettabytes. I did run an extra spellcheck on my local, it works. So setting this to RTBC at the same time, as the patch is less than 1KB.Comment #49
alexpottNeeds a reroll.
Comment #50
mondrakeComment #51
jungleRerolled and replaced
pow()with the**operatorSee https://www.php.net/manual/en/function.pow.php
Comment #52
mondrakeI think we'll have to cast $size to float here, since that was done in #3156879: \Drupal\Component\Utility\Bytes::toInt() - ensure $size is a number type and is probably the reason this needed a reroll.
Comment #53
longwave> replaced pow() with the ** operator
Why? We don't use ** anywhere else in core that I can see, we do use pow(), and (personal opinion) pow() is much easier to read and remember what it does.
Comment #54
jungleComment #55
krzysztof domańskiLooks good, back to RTBC.
Comment #56
mondrakeComment #57
alexpottI don't think this is a bug per se - it's certainly unexpected given the name but it has been this way for years. Given we are subtly changing the return type I think we should have a change record - in the very unlikely case that someone is depending on this returning a float.
Comment #58
krzysztof domańskiThe reason for this problem is the misleading name, not the return type so #4 is safer (backwards compatibility).
Let's change documentation and deprecate the
Bytes::toInt()method.#3143478: Rename method Bytes::toInt() to Bytes::toNumber()
Comment #59
jungleIf so, I would suggest re-scoping here to include #3143478: Rename method Bytes::toInt() to Bytes::toNumber().
Add
toNumber(), keeptoInt()untouched and deprecate it.Set back to NR for suggestions of what to do next.
Comment #60
mondrake+1 on #59. We should close the other one as duplicate then.
Comment #61
krzysztof domańskiRe #59. IMO
toNumberis a suitable name. Dependency onPHP_INT_MAXis not good. Better if it always returns float.Comment #62
jungleThanks @mondrake and @Krzysztof Domański!
toNumber()returns a float as suggested in #61.Comment #63
jungleSelf review:
int|float -> float, expected_int -> expected_number
Comment #64
jungleSo that
@group legacyadded to testToNumber() as well.Comment #65
jungleComment #66
krzysztof domańskiWe will remove the
Bytes::toInt()method in D10 so it should be like this:Comment #67
jungleThanks @Krzysztof Domański for the review.
Adjusted following #66
Comment #68
andypostIf it accepts int|string then why it can't accept floats?
Also numeric strings could be used to pass integers which are bigger then PHP_INT_MAX and converted internally by PHP itself
Please add `9223372036854775807` as string
Comment #69
andypostmixedis bad practice, better usestring|int|floatso when core will require PHP 8 we can use union typesComment #70
jungle@andypost, thanks for the review!
Addressing #68 and #69
Comment #71
krzysztof domańskiBytes::toInt()returnsBytes::toNumber()soproviderTestToInt()is no longer needed.Comment #72
mondrake@jungle something went wrong with your latest patch :(
Comment #73
mondrakeComment #74
jungleRe #72, yes, it's a 0 bytes patch 🤦
Started from #67, addressed #68, #69, #71.
Thanks!
Comment #76
krzysztof domańskiComment #77
krzysztof domańskiReplace calls to
toInt()withtoNumber(). For example:Comment #78
jungleThanks @Krzysztof Domański!
Addressing #77
Comment #79
mondrakeNeed to add @deprecated and @see to the docblock.
: floatreturn typehint can be added nowtestToNumber($size, float $expected_number): void: arrayreturn typehintComment #80
krzysztof domański#79.2 Unfortunately we should not add return typehint toBytes::toNumber().Bytes::toInt()inheritsBytes::toNumber().Ignore.
Comment #81
jungleThanks @mondrake!
Addressing #79
Comment #82
jungleOne CS violation, fixing.
Comment #83
krzysztof domańskiThanks @jungle!
Comment #84
mondrakenit: the comment here comes from previous patches - not sure it is relevant anymore. Maybe just cast to float as the lines above for consistency.
Comment #85
catchIf the new ::toNumber() method does exactly the same as the old one, then why does the test coverage change? Is this just because we were using assertSame() before which is more permissive?
Re-titling since we're essentially renaming with a bc layer, rather than deprecating in favour of an existing alternative.
Comment #86
jungleRe #85
Previously, using assertEquals(), now using assertSame()
So that changed the data provider.
From my understanding, I think we should change the title back. the old one (
toInt()) is deprecated in favor of the new one (toNumber()), it's not renaming.Thanks!
Comment #87
catch#1 makes sense and seems in scope to correct the tests to be inline with the documentation fix here.
Howabout 'replace'? Just trying to summarize the actual change more in the title.
Comment #88
jungleSounds good. Thanks!
Comment #89
alexpottCommitted 7567d54 and pushed to 9.1.x. Thanks!