-
Notifications
You must be signed in to change notification settings - Fork 0
JavaScript and PHP handle arrays differently
Problem rating: Simple ---------------*----- Deeeeep
To understand this family of problems, you must first understand how PHP and JavaScript handle arrays:
In PHP, arrays can have numeric or string keys. In JS, arrays can only have numeric keys, and instead, you would use an object for string keys.
Let's take a look at what happens if we convert different PHP arrays to JSON and parse them into JavaScript using a PHP script like:
let jsArray = JSON.parse('<?php echo json_encode($phpArray); ?>');
PHP | JS |
---|---|
[
0 => 'foo',
1 => 'bar',
2 => 'baz',
] |
[
'foo',
'bar',
'baz',
] |
Standard numeric/ordered arrays come to JavaScript as numeric/ordered arrays. Good so far.
PHP | JS |
---|---|
[
'foo' => 'bob',
'bar' => 'lob',
'baz' => 'law',
] |
{
'foo': 'bob',
'bar': 'lob',
'baz': 'law',
} |
Standard PHP associative arrays come to JavaScript as standard key-value objects. Still expected.
PHP | JS |
---|---|
[
0 => 'foo',
2 => 'baz',
1 => 'bar',
] |
{
'0': 'foo',
'1': 'bar',
'2': 'baz',
} |
Notice that if the array comes to JavaScript out of order, it will re-order the numeric keys and create an object instead of an array.
PHP | JS |
---|---|
[
'foo' => 'bob',
3 => 'lob',
'2' => 'law',
] |
{
'2': 'lob',
'3': 'bar',
'foo': 'baz',
} |
There are a few things going on here:
- JavaScript treats integer keys and numeric string keys the same. Both as numbers
- JavaScript re-orders numeric keys and places them before string keys
Now that we understand how the PHP/JS machinery behaves, we can understand the problems it poses to Livewire.
Consider a Livewire component like this:
class Example extends Component
{
public $foo = [
1 => 'bar',
0 => 'baz',
];
}
If you were to load the component in a browser and trigger a round-trip (using something like wire:click="$refresh"
), you would receive an error telling you the checksum integrity was violated. This is what happens to cause this:
- The un-ordered array is used in PHP to generate a unique checksum hash
- The array goes to JS, and get's parsed. When it is parsed, JS re-orders it
- The data is sent back to PHP when the refresh is triggered
- PHP re-hashes the data and compares it with the previously generated checksum
- The two don't match because the data is in different forms
- Error is thrown
Currently, Livewire V2 takes the following approach:
Whatever damaging transformations JS will apply to the data, we can instead apply in PHP before generating the checksum.
For example, if an array in PHP...
[
0 => 'foo',
2 => 'baz',
1 => 'bar',
]
...were about to be sent to JavaScript, we would re-order it first so it would become this:
[
0 => 'foo',
1 => 'bar',
2 => 'baz',
];
This prevents the checksum issue but causes odd behavior. For example, because PHP re-ordered the array before sending to JS, the first Livewire render will show this array in a specific order, then after a refresh, it will show the changed version (ordered).
We can mitigate this problem by applying the re-order BEFORE rendering the component's blade view. But we are just moving the problem farther upstream. The behavior will still be confusing if a dev tries to re-order an array and render it, but it never shows the re-ordered version.
If re-ordering is too heavy-handed, we can instead preserve the order, but re-index the array in PHP.
This would mean a component like:
class Example extends Component
{
public $items = [
0 => 'foo',
2 => 'baz',
1 => 'bar',
];
}
Would refresh and the order would be preserved, but the indexes would now be:
[
0 => 'foo',
1 => 'baz',
2 => 'bar',
];
Most of the time, it's ok to re-index an unordered array, because generally, the developer applied some sort of sorting to arrays and didn't bother to re-index them. Typically they wouldn't notice a difference.
However, in some cases, especially cases of mixed numeric and stringed keys, the associated keys MUST be preserved and re-indexing the numeric ones would cause odd or even damaging behavior.
No matter what, we NEED to parse the JSON from PHP into JavaScript so that we can access the data in JavaScript.
However, if we could not parse, but send a portion of the original JSON directly back to PHP as it came in, we wouldn't break the checksum AND would preserve array orders AND indexes.
The only odd behavior now would be re-ordered arrays in the JavaScript representation of the component data. This seems fine to me and would certainly be the lesser of all the evils.