Skip to content

JavaScript and PHP handle arrays differently

Caleb Porzio edited this page Nov 1, 2022 · 9 revisions

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); ?>');

Numeric Ordered Array

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.

Associative Array

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.

Numeric Un-Ordered Array

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.

Mixed Numeric/Associative 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.

Problem A: Checksum integrity violations

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

Solution A: Re-order arrays in PHP before being sent to JS

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.

Solution B: Re-index arrays instead

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.

Solution C: Preserve original PHP

Clone this wiki locally