PHP array_multisort在PHP 8中失败,在PHP 7.4中工作

5t7ly7z5  于 2023-03-28  发布在  PHP
关注(0)|答案(5)|浏览(125)

我很高兴能在PHP 7.4中让它工作,但现在它在PHP 8.1.14中又坏了。
任务是按标题对一个页面数组进行排序,但要以一种简化的方式。排序时,前导非字母将被忽略。
这是一个示例代码:

$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));

它在PHP 7.4中排序为以下输出,错误日志中没有任何内容:
A, B, C, .N
在PHP 8.1中,我得到了这个错误:

ErrorException: Array to string conversion in ...\index.php:30
Stack trace:
#0 [internal function]: {closure}()
#1 ...\index.php(30): array_multisort()

我不明白问题出在哪里。PHP 8在这里做了什么改变,我能做些什么来再次修复代码?

更新/澄清:

在PHP 7.4中,有一个“Notice”,文本为“Array to string conversion”。在PHP 8.1中,这已更改为“Warning”。我将警告视为错误,因此在我的情况下它失败了。
这并没有改变PHP认为我的代码是错误的,我不知道为什么。

brjng4g3

brjng4g31#

你看起来把数组倒过来了。第一个数组是实际排序的,第二个数组应用了相同的转换。

array_multisort(
    array_map(fn($page) => preg_replace('_^[^0-9a-z]*_', '', strtolower($page['title'])), $pages),
    SORT_ASC, SORT_STRING,
    $pages
);
ryoqjall

ryoqjall2#

问题不在于PHP代码,而在于您使用array_multisort()的方式。
第三个参数(array_sort_flags)告诉array_multisort()如何比较第一个参数(要排序的数组)的项。值SORT_STRING表示 “将项作为字符串进行比较”$pages的项不是字符串而是数组。
当数组在字符串上下文中使用时,PHP会抛出你看到的警告。在PHP 8之前,它曾经是一个通知,在PHP 8中它已经(正确地)升级为警告。在未来的PHP版本中,它甚至可以更改为错误。
不要隐藏通知,很多时候它们会显示逻辑错误。PHP过去非常宽容,并将此类错误报告为通知;自从PHP 8以来,其中许多都被提升为警告。修复代码,使其不会产生错误,警告甚至通知。
有多种方法可以修复此代码,但正确的方法取决于您的预期结果。如果array_map()转换后属性title的值是唯一的,则颠倒数组的顺序可以解决这个问题:

array_multisort(
  array_map(fn($page) =>
    preg_replace('_^[^0-9a-z]*_', '', strtolower($page['title'])
  ), $pages),
  SORT_ASC,
  SORT_STRING,
  $pages
);

它仍然比较$pages数组中的项,但仅当它在第一个数组中遇到重复值时。
另一种更易于阅读和理解但性能稍差的解决方案是使用uasort()$pages进行排序,使用当前传递给array_map()的回调来比较元素

$pages = [
    'a' => ['title' => 'A'],
    'c' => ['title' => 'C'],
    'b' => ['title' => 'B'],
    'n' => ['title' => '.N'],
];

uasort($pages, fn($a, $b) => 
    preg_replace('_^[^0-9a-z]*_', '', strtolower($a['title']))
    <=>
    preg_replace('_^[^0-9a-z]*_', '', strtolower($b['title']))
);

echo implode(', ', array_map(fn($page) => $page['title'], $pages));

检查online
如果$pages很大,那么每次比较都调用preg_replace()strtolower()会浪费时间。更好的解决方案是只计算一次排序键,并将它们保存在一个单独的数组中,让比较函数从这个数组中获取它们:

$keys = [];
foreach ($pages as $p) {
    if (! isset($keys[$p['title']])) {
        $keys[$p['title']] = preg_replace('_^[^0-9a-z]*_', '', strtolower($p['title']));
    }
}

uasort($pages, fn($a, $b) => $keys[$a['title']] <=> $keys[$b['title']]);

检查online

rsaldnfx

rsaldnfx3#

作为替代,您可以使用uasort

uasort($pages, function($a, $b) {
    $at = preg_replace('/[^a-z]+/sU', '', strtolower($a['title']));
    $bt = preg_replace('/[^a-z]+/sU', '', strtolower($b['title']));
    return strcmp($at, $bt);
});
qij5mzcb

qij5mzcb4#

如果在排序之前隔离标题列数据,就可以避免代码中出现太多的混乱。
我建议只在匹配的字符串有长度的情况下进行替换。此外,只要还包含SORT_STRING,就可以使用SORT_FLAG_CASE进行不区分大小写的排序。
代码:(Demo

$titles = array_column($pages, 'title');

array_multisort(
    preg_replace('/^[^0-9a-z]+/i', '', $titles),
    SORT_STRING | SORT_FLAG_CASE,
    $titles
);

echo implode(', ', $titles);

请记住,array_multisort()实际上会做名字所暗示的事情。前三个参数中描述的排序规则将是第一轮排序。如果在第一轮之后有任何平局,第四个参数(未更改的值数组)将用于尝试打破平局。Demo

zfciruhq

zfciruhq5#

PHP 7.4和8.1应该没有什么区别,可能只是之前的警告被抑制了(服务器设置,PHP配置),问题是他们错误地使用了函数。

<?php
$pages = [
    'a' => ['title' => 'A'],
    'c' => ['title' => 'C'],
    'b' => ['title' => 'B'],
    'n' => ['title' => '.N'],
];

$ps = array_map(fn($page) => preg_replace('_^[^0-9a-z]*_', '', strtolower($page['title'])), $pages);
array_multisort($ps, SORT_ASC, SORT_STRING);

print_r( implode(', ', array_map(fn($page) => $page['title'], $pages)) );

// output: A, C, B, .N

相关问题