有没有更好的PHP方法从数组(字典)中按键获取默认值?

71

Python中,可以这样做:

foo = {}
assert foo.get('bar', 'baz') == 'baz'

PHP 中,可以使用三元运算符:

$foo = array();
assert( (isset($foo['bar'])) ? $foo['bar'] : 'baz' == 'baz' );

我正在寻找一种高尔夫版本。我能否在PHP中更短、更好地实现它?

更新[2020年3月]:

assert($foo['bar'] ?? 'baz' == 'baz');

看起来今天值得尝试的是空值合并运算符 ??,更多信息请参考这里

在下面的评论中发现(+1)


我正在为一个黑客答案进行==$_=&的资格认证。虽然真正的答案是否定的 - 没有捷径可走。看起来像是一个不错的功能请求,类似于array_get($foo, 'bar', 'baz')函数。事实上,有一个待处理的请求和PHP 6的补丁 https://bugs.php.net/bug.php?id=40792 - Yauhen Yakimovich
3
我不明白为什么这个功能不被内置到PHP中。它非常简单实用,早就应该有了。他们需要发布多少版本的PHP才能解决这个问题?PHP的创造者真的在使用它吗?为什么要重复编写一个表达式呢? - Jens
1
FYI Yauhen:一个更好(也更简短)的Python习惯用法是直接使用默认的无匹配值,即None,并直接断言我们没有得到None:assert foo.get('bar') is not None,这等同于assert foo.get('bar', None) is not None - smci
另外,您可能希望添加标签[tag:language-design]。 - smci
2
更新后的答案是在PHP 7中使用空值合并运算符"??":https://dev59.com/p2w15IYBdhLWcg3wT5_2#41246606 - Brian Peterson
8个回答

75

时间流逝,PHP正在不断发展。现在的PHP 7支持空合并运算符??

// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

// Coalescing can be chained: this will return the first
// defined value out of $_GET['user'], $_POST['user'], and
// 'nobody'.
$username = $_GET['user'] ?? $_POST['user'] ?? 'nobody';

1
我认为这个答案是误导性的,因为它没有检查数组是否存在。如果键和数组变量不存在,它会宽容地允许它。我认为答案https://dev59.com/p2w15IYBdhLWcg3wT5_2#22389397更准确。 - msemelman
1
@msemelman 这个问题是关于从数组中获取默认键,与检查数组是否存在无关。 - Ivan Yarych
@IvanYarych 在这个问题中,isset($foo['bar']) 是一个长期存在的习惯用法,用于检查数组键是否存在。虽然不是最好的方法,但这在过去是非常常见的用法。 - Izkata

53

我刚刚想出了这个小助手函数:

function get(&$var, $default=null) {
    return isset($var) ? $var : $default;
}

这不仅适用于字典,而且适用于各种类型的变量:

$test = array('foo'=>'bar');
get($test['foo'],'nope'); // bar
get($test['baz'],'nope'); // nope
get($test['spam']['eggs'],'nope'); // nope
get($undefined,'nope'); // nope

将以前未定义的变量按引用传递不会导致NOTICE错误。相反,通过引用传递$var将定义它并将其设置为null如果传递的变量为null,则还将返回默认值。还要注意在spam/eggs示例中隐式生成的数组:

json_encode($test); // {"foo":"bar","baz":null,"spam":{"eggs":null}}
$undefined===null; // true (got defined by passing it to get)
isset($undefined) // false
get($undefined,'nope'); // nope

请注意,即使通过引用传递了$varget($var)的结果将是$var的副本,而不是一个引用。希望这能有所帮助!


3
有没有办法删除/取消由get($ar[0][1][1]), 'def');创建的$ar[0][1][1] - CoR
1
我写了一篇博客文章来进一步研究这个问题并解释我的解决方案。 - stepmuel
2
最好使用!empty代替isset,因为它会验证变量是否实际上有一个值。 - EliuX
让我想起了Python :) - Richard de Wit
1
当您以以下方式使用它时:get($test['foo']['bar']);,它会抛出一个警告。 - Slavik Meltser

24

有趣。除了在这么小的一部分代码中出现语法错误(可能并不那么严重)之外,还会发生什么问题? - Yauhen Yakimovich
1
我刚刚注意到我们代码库中的这种模式。它感觉非常不合适,但却能正常工作。 - André Laszlo
5
请注意,这仅在评估$foo['bar']时暂时设置error_reporting(0)。它仍将调用错误处理程序,并且错误处理程序有责任在error_reporting() === 0时忽略错误。 - Jesse
回到顶部! - Mr. Lance E Sloan
2
对于像 0 这样的值无效。$foo = ['bar' => 0]; $bar = @$foo['bar'] ?: 'defaultvalue'; 返回 "defaultvalue"。 - Adam Barnes
强调@AdamBarnes的评论:这个答案是一个不好的习惯,因为它对任何"等于false的值"(0,空字符串,..)返回默认值。相反,使用任何仅对null返回默认值的答案(例如使用isset或null-coelescing)。或者如果应该允许null,则使用一个执行array_key_exists的答案。 - ToolmakerSteve

12

我发现创建一个这样的函数非常有用:

function array_value($array, $key, $default_value = null) {
    return is_array($array) && array_key_exists($key, $array) ? $array[$key] : $default_value;
}

然后像这样使用它:

$params = array('code' => 7777, 'name' => "Cloud Strife"); 

$code    = array_value($params, 'code');
$name    = array_value($params, 'name');
$weapon  = array_value($params, 'weapon', "Buster Sword");
$materia = array_value($params, 'materia');

echo "{ code: $code, name: $name, weapon: $weapon, materia: $materia }";
在这种情况下,null是默认值,但您可以将其设置为您需要的任何值。
希望它有用。

是的,使用array_key_existsisset更好。 - Kerem
我建议交换$array和$key的顺序,因为内置函数是这样的,例如array_key_exists($key, $array)。 - malhal
小错误,如果它不是一个数组,则返回 false 而不是默认值。 - malhal
@malhal。有趣。一定是运算符的优先级问题。为确保按预期执行,请添加括号:return (is_array($array) && array_key_exists($key, $array)) ? ... - ToolmakerSteve

7

PHP 5.3有一个三元运算符的快捷版本:

$x = $foo ?: 'defaultvaluehere';

基本上,这是关于IT技术的内容。
if (isset($foo)) {
   $x = $foo;
else {
   $x = 'defaultvaluehere';
}

否则,没有更短的方法。

1
短三元语法等同于$x = isset($foo) ? isset($foo) : 'defaultvaluehere';,而不是$x = isset($foo) ? $foo : 'defaultvaluehere'; - NikiC
1
@Marc B 但是如果字典中的false或空字符串是有效值,那么这种方法就不起作用了,对吗? - phihag
26
这个不起作用。短三目运算符在变量未定义时仍会生成“NOTICE”错误,而不像“isset”函数。 - Chris B.
2
是的,这样行不通。三元运算符不会隐式调用isset()函数。 - kdazzle
@TeNNoX 这样做是可以的,但错误仍然会显示在 psysh 控制台上。 - CMCDragonkai
显示剩余2条评论

6
一种“有点”hack的方法是:
<?php
    $foo = array();
    var_dump('baz' == $tmp = &$foo['bar']);
    $foo['bar'] = 'baz';
    var_dump('baz' == $tmp = &$foo['bar']);

http://codepad.viper-7.com/flXHCH

显然这不是一个好的做法,但在其他情况下非常方便。例如,我经常像这样声明 GET 和 POST 变量的快捷方式:

<?php
    $name =& $_GET['name'];
    // instead of
    $name = isset($_GET['name']) ? $_GET['name'] : null;

提示:有人可能称此为“内置的==$_=&特殊比较运算符”:

<?php
    var_dump('baz' ==$_=& $foo['bar']);

提示:当然,你可以直接使用

<?php
    var_dump('baz' == @$foo['bar']);

但这比==$_=&运算符更糟糕。你知道吗,人们不太喜欢错误抑制运算符。


为什么=&会抑制未定义索引的提示?我理解&使其成为引用,但我不明白这样做有什么帮助。 - Grumdrig

2
如果你想要按键值枚举一个数组的默认值,可以这样做:
$foo = array('a' => 1, 'b' => 2);
$defaults = array('b' => 55, 'c' => 44);

$foo = array_merge($defaults, $foo);

print_r($foo);

这将导致:
Array
(
    [b] => 2
    [c] => 44
    [a] => 1
)

你枚举默认值的键/值对越多,代码高尔夫就会变得越好。


这对于$_GET来说非常有用,例如。 - user

2

"Marc B提出的解决方案",可以使用三元运算符快捷方式$x = $foo ?: 'defaultvaluehere';,但它仍然会产生提示。可能是打错了,也许他的意思是 ?? 或者这是在 PHP 7 发布之前写的。 根据三元运算符的描述

自 PHP 5.3 以来,可以省略三元运算符的中间部分。表达式 expr1 ?: expr3 如果 expr1 求值为 TRUE,则返回 expr1,否则返回 expr3

但它不使用isset,并且会产生提示。 为了避免提示,最好使用空合并运算符??,其中包含isset。在 PHP 7 中可用。

表达式 (expr1) ?? (expr2) 的计算结果是,如果 expr1 是 NULL,则为 expr2;否则为 expr1。 特别地,如果左侧值不存在,该运算符不会发出提示,就像 isset() 函数一样。这在处理数组键时特别有用。
示例 #5:分配默认值
<?php
// Example usage for: Null Coalesce Operator
$action = $_POST['action'] ?? 'default';

// The above is identical to this if/else statement
if (isset($_POST['action'])) {
    $action = $_POST['action'];
} else {
    $action = 'default';
}

?>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接