Any surgery conducted on this malformed querystring may potentially corrupt data.
If you know that the input will not have = in the key name or values, then you can correct the querystring by injecting [] between each key name and the equals sign, then parse it normally and transpose if you wish.
Code: (Demo)
parse_str(str_replace('=', '[]=', $string), $array);
var_export(array_map(fn($name, $id) => get_defined_vars(), $array['name'], $array['id']));
Output:
array (
0 =>
array (
'name' => 'xxx',
'id' => '11',
),
1 =>
array (
'name' => 'yyy',
'id' => '12',
),
2 =>
array (
'name' => 'zzz',
'id' => '13',
),
3 =>
array (
'name' => 'aaa',
'id' => '10',
),
)
If this isn't strict enough, you can tighten the replacement process to explicitly nominate name and id keys.
parse_str(preg_replace('/\b(name|id)\K(?==)/', '[]', $string), $array);
Ideally, if you have control of the form fields which are producing this submission payload, you should most simply add [] the the fields' name attributes (<input type="text" name="name[]"> and <input type="text" name="id[]">) or otherwise correct the process that is creating key collisions in the payload.