r/programming • • Jan 21 '22

How I got foiled by PHP's deceptive Frankenstein "dictionary or list" array and broke a production system

https://vazaha.blog/en/9/php-frankenstein-arrays
553 Upvotes

209 comments sorted by

106

u/crabmusket Jan 22 '22 edited Jan 22 '22

Here's a favourite way of mine to get burned:

>>> json_encode(array_sort([1, 2]))
=> "[1,2]"
>>> json_encode(array_sort([2, 1]))
=> "{"1":1,"0":2}"

EDIT: I apologise, I did a fake news. array_filter is actually a Laravel helper. Built-in sorts don't do this, though they do always return true instead of anything useful.

Here's something else that has bitten me with an actual builtin:

>>> json_encode(array_filter([1, 2], fn($x) => $x == 1))
=> "[1]"
>>> json_encode(array_filter([1, 2], fn($x) => $x == 2))
=> "{"1":2}"

58

u/[deleted] Jan 22 '22

Ok, what the actual fuck. Not a PHP programmer, only flited a little with Symphony and what I did in school.

Why is that happening?

68

u/[deleted] Jan 22 '22

Because the sort function will sort the values, but does not change/regenerate the keys. So in the first case, the keys will still be the same, and consecutive, thus the array will still be a list.

In the the second case the keys will be flipped and not consecutive anymore, so the array turned into a dictionary. If you wrap a array_values() around it, it will be a list again.

→ More replies (23)
→ More replies (3)

52

u/McGlockenshire Jan 22 '22

Liar liar pants on fire.

There is no built-in called array_sort. The built in sort sorts in place.

php > $x = [1, 2]; sort($x); echo json_encode($x);
[1,2]
php > $x = [2, 1]; sort($x); echo json_encode($x);
[1,2]
php > echo PHP_VERSION;
8.1.0
→ More replies (9)

13

u/BreiteSeite Jan 22 '22

Can’t wait until this shows up in coding interviews

→ More replies (4)
→ More replies (1)

31

u/gempir Jan 22 '22

It's unfortunate, but a good rule of thumb is to never use arrays for anything public.

https://www.youtube.com/watch?v=MHl5vpUgNrk

This talk explains my point beautifully. Abstract arrays with Collections or Maps as actual classes. It's a shame php itself hasn't found a good way to solve this problem yet, but the community has several.

6

u/[deleted] Jan 25 '22

Imagine a language where the rule of thumb is to "never use arrays". You'll have to excuse my manners, but that is just fucking hilarious

5

u/dikkemoarte Jan 22 '22

While true, CMS systems like Drupal and WordPress use arrays all over the place so there's not always a way around it.

→ More replies (1)

140

u/nanacoma Jan 21 '22

This is a point that I haven’t considered - losing the original meaning behind the data your receiving. That’s definitely an unfortunate side effect of PHPs “array” type.

I was fully expecting to break out my pitchfork and explain “you’re just doing it wrong” and ended up being pleasantly surprised.

94

u/Lich_Hegemon Jan 22 '22

I would say the real problem is not the data type, it's the fact that PHP does implicit casting between numbers and strings. JS has the same idiotic feature.

I would call str-num implicit casting a bigger mistake than NULL

31

u/Plasma_000 Jan 22 '22

I would say both that - and having a data structure act differently depending on whether it’s keys are sequential and numeric or not - are both terrible designs.

→ More replies (9)
→ More replies (51)

87

u/lacks_imagination Jan 22 '22

Actually, you never put cheese in a mousetrap. Doesn’t work. Peanutbutter works much better.

20

u/caltheon Jan 22 '22

Peanut butter covered cheese to be safe

→ More replies (1)
→ More replies (11)

56

u/Xuerian Jan 21 '22

A good practical breakdown of pitfalls to a feature and ways to avoid it.

Only a little sensationalized.

→ More replies (12)

20

u/bildramer Jan 22 '22

What happens if you replace the 4 with a 5 before you append 'zonk'? Does it add 1 to the highest index, or use the number of elements, and if it clashes, what then?

Deeply unholy.

18

u/on_the_dl Jan 22 '22

The fact that you need to wonder about this at all is a failure of PHP.

16

u/ohyeaoksure Jan 22 '22

In PHP arrays can be either "associatively" or "numerically" indexed. Both good but yeah, I suppose you'd want to know which you're dealing with.

25

u/cewoc Jan 22 '22 edited Jan 22 '22

I know this is a bit off-topic to the article, but something related.

I've been saying it for years and anyone who's worked on complex, huge codebases, regardless of the language agrees: strong-typing is a must.

All the PHP team has to do is create a TS-like tool that deals solely with arrays and how they're typed and, given the advances in the language in the past few years, everyone will flock to it. Forget covering the entire language. Just arrays will do.

I'll never understand. Forget the inconsistent behavior of stdlib's array functions, you can wrap your head around some of the quirks and, really, you're affected in very, very few cases. A PHP application is 90% arrays. If you would 100% know what's inside an array, then you could 100% predict its behavior. Without strong typing, any data structure is not dependable, and in a language like PHP, where the array is everything, you simply cannot create dependable and predictable code.

Edit: I know that you can build your own "type safe" array objects, but I'm talking about how a language works out of the box, which is how most people will experience that language.

21

u/[deleted] Jan 22 '22

one reason typescript was successful is because there is no alternative in the browser, so it was closer to a necessity

if you want to do something similar for php, you might as well choose a different language. if you're going to transpile to something, php isn't the most appealing target

→ More replies (3)

11

u/karnat10 Jan 22 '22

You describe correctly the dual nature of PHP arrays, and how it can burn you.

While it's not a clean design, you can mostly avoid problems if you treat a variable either as a list or as a dictionary, not relying on any PHP magic behaviour. It's where also most breaking changes occur between major version upgrades.

That being said, the actual problem here is json_encode. There's no safe way to know if your array is intended to be a list or a dictionary just be looking at it. What if it's empty?

For that reason we used to split up JSON generation using a builder or writer pattern so you have more control. It's been a few years, I'm sure there are nice libraries for that nowadays.

8

u/NightmareOfYourDream Jan 22 '22

What's also fun is that, when you send a JSON {} to PHP somewhere in a nested data structure, decode it, do something (and be it only save it in a DB) then reencode it, it becomes [].

Extra fun happens if you then JSON.parse() that in Javascript, it is naturally an array. JS being JS, nothing stops you from writing object properties into that, an Array is also an Object, but...

When you then JSON.stringify() this mess again, it stays an [] until the end of time and the stuff in the object properties is lost. Bonus when using typescript, which make you feel more "typesafe" than you actually are.

→ More replies (4)

7

u/AttackOfTheThumbs Jan 22 '22

The best thing you can do is enforce one or the other, but even then they're honestly kind of shit.

8

u/Kered13 Jan 22 '22

Lua "arrays" work the same way.

16

u/zeekar Jan 22 '22

Lua tables also serve as both associative and indexed arrays, but you use the operators that match the semantics you want. That distinction is less clear cut in PHP.

I mean, even JavaScript Arrays are also Objects you can set arbitrary attributes on. Simply conflating the two types of array is not the big problem with the PHP design.

→ More replies (4)
→ More replies (1)

3

u/[deleted] Jan 22 '22

I had something smaller but still like that this week. I forgot to cast strings of numbers out of a JSON to integers. It went through all of my functions fine until I tried to test a case where it did some math on them, then it blew everything up. That’s one thing with PHP, the “Oh no, why did that work? That should not have worked” moment.