-
Notifications
You must be signed in to change notification settings - Fork 0
JavaScript and PHP handle arrays differently
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 hey 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.