安全地获取定义和未定义索引的数组元素值

42

我已经厌倦了写三元表达式来清理数据,比如:

$x = isset($array['idx']) ? $array['idx'] : null;
// and
$x = !empty($array['idx']) ? $array['idx'] : null;

有没有一种 本地方式ZF访问器/过滤器 可以获取某个给定数组的元素值,而不需要:

  • 禁用 error_reporting
  • 使用三元运算符进行 isset/empty 检查
  • 错误控制运算符 @
  • 创建自己的全局函数或应用访问器/过滤器

类似于:

$x = get_if_set($array['idx']);
// or 
$x = Zend_XXX($array, 'idx')

4
为什么不自己创造呢?在PHP社区中,这是一种流行的方法 :) - J0HN
1
诚然,我自己对此并不太熟悉,但如果您扩展ArrayObject类,使得在魔术getter方法中具有三元操作,那么您就可以像正常设置一样进行设置。 - mseancole
你也可以尝试使用 filter_var(),但这可能会变得非常笨重和繁琐。仍然认为最好的方法是设置魔术获取方法并将数组传递给ArrayObject。 - mseancole
3
@J0HN - 我完全同意你的看法,我只是想获取其他开发者的意见,并确保我没有重复造轮子。 @showerhead - 谢谢,我喜欢这种方法...只需要重载 ArrayObject::offsetGet($index) 就可以搞定。 - Alex
@Alex:我必须再次承认,我不知道offsetGet()方法,我只是在谈论神奇的__get()方法。然而,既然它已经具备了那个功能,请继续使用 :) - mseancole
@showerhead __get() 仅适用于请求属性 $obj->prop,而不是索引 $obj['idx']。由于 ArrayObject 实现了 ArrayAccess,我们可以重载数组访问方法 offsetGet,在尝试获取索引之前检查其是否存在。感谢您的建议。 - Alex
4个回答

52

PHP7引入了null合并运算符??。假设你的PHP版本是7或更高,你可以使用:

$x = $array['idx'] ?? null;

1
  1. 访问未定义的键的结果已经是 null,因此不需要使用 ?? null
  2. 请注意,*"将发出一个 E_NOTICE 级别的错误消息"*:https://www.php.net/manual/en/language.types.array.php "尝试访问未定义的数组键与访问任何其他未定义变量相同:将发出一个 E_NOTICE 级别的错误消息,并且结果将为 NULL。"
- ToolmakerSteve
9
@ToolmakerSteve那是不正确的。Elvis运算符抑制了提示(就像isset一样),然后返回右边的值。你可以自己试试看。 - Tamlyn

41

自PHP 7.0起

多亏了Andrea FauldsNikita Popov的贡献,现在事情变得更加容易-使用 Null Coalescing Operator ?? 文档,迁移,RFC

$x = $array['idx'] ?? NULL;

或者使用 $default

$x = $array['idx'] ?? $default ?? NULL;
issetempty 一样,它不会产生任何警告并且表达式向右侧继续执行(如果未设置)。如果被设置并且不为NULL,则会取该值。这也是前面示例中的 $default 总是起作用的方式,即使未定义也是如此。


自PHP 7.4以来

感谢 Midori Kocak - 是 Null Coalescing Assignment Operator ??=Docs, RFC (这是我在使用 ?? 后遗漏的其中之一)允许直接分配默认值:

$array['idx'] ??= null;
$x = $array['idx'];

虽然我不像使用 ?? 一样频繁地使用它,但是了解它很好,尤其是在分离数据处理逻辑并希望尽早设置默认值时。


原始老答案


只要您只需要将 NULL 作为“默认”值,就可以利用错误抑制运算符:

$x = @$array['idx'];

批评:使用错误抑制运算符存在一些缺点。首先它使用了错误抑制运算符,所以如果代码中有问题,就不能容易地恢复。另外,未定义的标准错误情况会影响代码的可读性。你的代码表达不够精确。另一个潜在的问题是使用无效索引值,例如为索引注入对象等。这将不被察觉。

这将防止警告。但是,如果您也想允许其他默认值,可以通过ArrayAccess接口封装对数组偏移的访问:

class PigArray implements ArrayAccess
{
    private $array;
    private $default;

    public function __construct(array $array, $default = NULL)
    {
        $this->array   = $array;
        $this->default = $default;
    }

    public function offsetExists($offset)
    {
        return isset($this->array[$offset]);
    }

    public function offsetGet($offset)
    {
        return isset($this->array[$offset]) 
            ? $this->array[$offset] 
            : $this->default
            ;
    }

    public function offsetSet($offset, $value)
    {
        $this->array[$offset] = $value;
    }

    public function offsetUnset($offset)
    {
        unset($this->array[$offset]);
    }
}

使用方法:

$array = array_fill_keys(range('A', 'C'), 'value');
$array = new PigArray($array, 'default');
$a     = $array['A'];   # string(13) "value"
$idx   = $array['IDX']; # NULL "default"

var_dump($a, $idx);

演示:https://eval.in/80896


在offsetGet中,您应该使用$this->default而不是NULL,这更符合您尝试做的事情的想法。此外,在用法部分,第一行应为“$arrayData =”,而不是“$array”。除此之外,这篇文章非常有帮助。 - Jeffrey Nicholson Carré
@bleuscyther:感谢您的反馈!对于默认值,这很可能是一个疏忽,现在已经修复了,包括一些其他格式问题。对于$arrayData建议,我不这么认为(数据在我看来太含糊了,所以它可以保持原样,因为它是一个数组 - 我已经修复了示例)。我还添加了一个演示:https://eval.in/80896 - hakre
注意:使用 @ 进行错误抑制会带来一些性能成本(虽然没有文档记录,但很容易测量,例如在 http://php.net/manual/en/language.operators.errorcontrol.php 的评论中可以看到)。 实际上,这不仅仅是抑制本身的成本,而是由于 PHP 中实际错误(在这种情况下是不存在的元素)引起的搅动所造成的成本,它仍然以某种方式处理,只是最终隐藏了。 - Sz.
@lunakid:继续使用当前版本。PHP不断提高@运算符的性能。这仅仅是早期的“真假神话”。我认为更重要的是教育人们为什么应该谨慎使用它,并解释它在设计/处理方面的昂贵代价。 - hakre
@hakre,我知道你的意思,这就是为什么我补充说它大多数来自于实际错误本身,而不仅仅是@操作符。因此,没有涉及“神话”:处理实际发生的错误(在$php_errormsg中生成和记录它+与ini设置和各种其他修补等打交道)需要一定的工作量*按设计进行,有文档记录,并可以相应地进行测量。然而,这个问题确实被高估了。无论如何,重点仍然是:避免错误,如果可以的话,不要忽略它。您的好的数组类也是这样做的。 - Sz.
显示剩余6条评论

1
我会建议,使用一个辅助函数来与你的数组进行交互。
function getValue($key, $arr, $default=null) {
   $pieces = explode('.', $key);
   $array = $arr;

   foreach($pieces as $array_key) {

      if(!is_null($array) && is_array($array) && array_key_exists($array_key, $array)) { 
          $array = $array[$array_key];
      }
      else {
          $array = null;
          break;
      }
   }
   return is_null($array) ? $default : $array;
}

$testarr = [
    ['foobar' => 'baz'],
    ['active' => false]
];
$output = getValue('0.foobar',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('0',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('1.active',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('i.do.not.exist',$testarr,'NOT FOUND');
var_dump($output);

这样,您可以提供一个默认值而不是null,您不需要将数组重新实例化为另一个对象,您可以请求任何深度嵌套的值,而无需检查“父”数组。

https://ideone.com/11jtzj


0

声明变量并给它们一些初始值。

$x = NULL;
$y = 'something other than NULL';

现在,如果您有一个具有x和y键的数组$myArray,提取函数将覆盖初始值(您也可以配置为不覆盖)

$myArray['x'] = 'newX';
extract($myArray);
//$x is now newX

如果没有键存在,则变量的初始值保持不变。它还将其他数组键放入相应的变量中。


6
尝试不错,但这比三元检查还要混乱,特别是当数组有数十个字段时... 这将消除 PHP 稀疏数组的动态特性,并导致大量硬编码。这种解决方案将在未来产生不必要的维护工作。 - Alex
3
我对 extract 不太感冒,因为它经常带来的是污染而不是帮助,尽管我知道这段代码是可行的。 - Xuan

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