I was happy to get that thing working in PHP 7.4, but now it’s broken again in PHP 8.1.14.
The task is to sort an array of pages by their title, but in a simplified way. Leading non-letters shall be ignored when sorting.
This is a sample code:
$pages = [
'a' => ['title' => 'A'],
'c' => ['title' => 'C'],
'b' => ['title' => 'B'],
'n' => ['title' => '.N'],
];
array_multisort(
$pages,
SORT_ASC,
SORT_STRING,
array_map(fn($page) =>
preg_replace('_^[^0-9a-z]*_', '', strtolower($page['title'])
), $pages)
);
echo implode(', ', array_map(fn($page) => $page['title'], $pages));
It sorts to this output in PHP 7.4, nothing in the error log:
A, B, C, .N
In PHP 8.1, I get this error instead:
ErrorException: Array to string conversion in ...index.php:30
Stack trace:
#0 [internal function]: {closure}()
#1 ...index.php(30): array_multisort()
I don’t understand what the problem is. What has been changed here for PHP 8 and what can I do to fix the code again?
Update/clarification:
In PHP 7.4, there is a "Notice" with the text "Array to string conversion". In PHP 8.1 this has been changed to a "Warning". I treat warnings as errors so it fails in my case.
That doesn’t change that PHP thinks my code is wrong and I don’t have a clue why.
4
Answers
You seem to have the arrays backwards. The first array is what is actually sorted, and the second array has the same transformation applied to it.
The problem is not in the PHP code but in the way you use
array_multisort()
.The third argument (
array_sort_flags
) tellsarray_multisort()
how to compare the items of the first argument (the array to sort). The valueSORT_STRING
means "compare items as strings" but the items of$pages
are not strings but arrays.When an array is used in string context PHP throws the warning that you saw. It used to be a notice before PHP 8, it has been (correctly) promoted to warning in PHP 8. It could even be changed to error in a future PHP version.
Do not hide the notices, many time they reveal logical errors. PHP used to be very permissive and reported such errors as notices; since PHP 8 many of them were promoted as warnings. Repair the code to not produce errors, warnings or even notices.
There are multiple ways to repair this code but the right one depends on your expected outcome. Reversing the order of the arrays solves the problem if the values of property
title
are unique after the transformation produced byarray_map()
:It still compares items from the
$pages
array but only when it encounters duplicate values in the first array.Another solution that is easier to read and understand but slightly worse performance wise is to use
uasort()
to sort$pages
using the callback that is currently passed toarray_map()
to compare the elementsCheck it online.
If
$pages
is large then callingpreg_replace()
andstrtolower()
for each comparison wastes time. A better solution is to compute the sort keys only once and keep them in a separate array and let the comparison function get them from this array:Check it online.
As an alternative, you could use
uasort
.You can spare your code from so much fluffing around if you isolate the title column data before sorting.
I recommend only making replacements if the matched string has length. Also, you can sort case-insensitively with the
SORT_FLAG_CASE
so long as you also includeSORT_STRING
.Code: (Demo)
Bear in mind,
array_multisort()
will actually do what the name implies. The sorting rules described in the first set of parameters will be the first round of sorting. If there are any ties after that first round, the subsequent sets of arguments (or in this case the unaltered array of values) will be used to try to break ties. Demo