PHP中的解构赋值用于对象/关联数组

80

在 CoffeeScript、Clojure、ES6 和许多其他语言中,我们有对象/映射等的解构,类似于这样:

obj = {keyA: 'Hello from A', keyB: 'Hello from B'}
{keyA, keyB} = obj

我在php中找到了list函数,它可以让你像这样解构数组:

$info = array('coffee', 'brown', 'caffeine');
list($drink, $color, $power) = $info;

在 PHP 中是否有一种方法可以解构对象或关联数组?如果核心库中没有,也许有人编写了一些智能帮助函数吗?


对称数组解构的边缘考虑:在解构数组时,是否可以多次访问相同的元素值? - mickmackusa
5个回答

83

对于 PHP 7.0 及以下版本,这超出了 list 函数的功能。文档说明如下:

list 仅适用于数字数组,并假定数字索引从 0 开始。

可以满足您需求的其中一件事是extract()函数,它将变量从数组导入到当前符号表中。虽然使用 list 可以显式地定义变量名,但是 extract() 不会给您这种自由。

提取关联数组

使用 extract,您可以像这样操作:

<?php

$info = [ 'drink' => 'coffee', 'color' => 'brown', 'power' => 'caffeine' ];
extract($info);

var_dump($drink); // string(6) "coffee"
var_dump($color); // string(5) "brown"
var_dump($power); // string(8) "caffeine"

提取一个对象

提取对象的方法与提取数组几乎相同。由于extract只接受数组作为参数,因此我们需要将对象属性获取为数组。get_object_vars可以做到这一点。它返回一个关联数组,其中所有公共属性作为键,它们的值作为值。

<?php

class User {

    public $name = 'Thomas';

}

$user = new User();
extract( get_object_vars($user) );

var_dump($name); // string(6) "Thomas"

注意事项

extract()不同于list,因为它不允许您显式定义要导出到符号表的变量名。默认情况下,变量名对应于数组键。

  • list是语言结构而extract()是一个函数
  • 可能会无意中覆盖之前定义的变量
  • 您的数组键可能无效作为变量名称

使用可以作为第二个参数传递给extract()$flags参数,您可以影响在发生冲突或无效变量时的行为。但是重要的是要知道extract()如何工作,并小心地使用它。

编辑:自PHP 7.1起,这是可能的:

http://php.net/manual/en/migration71.new-features.php#migration71.new-features.support-for-keys-in-list

现在可以在list()或其新的简写[]语法中指定键。这使得可以解构具有非整数或非连续键的数组。

https://php.net/manual/en/migration71.new-features.php#migration71.new-features.symmetric-array-destructuring

简写数组语法([])现在可用于解构赋值中的数组(包括在foreach内部),作为现有list()语法的替代方案,仍然得到支持。

例如这个:

$test_arr = ['a' => 1, 'b' => 2];
list('a' => $a, 'b' => $b) = $test_arr;
var_dump($a);
var_dump($b);

从7.1.0版本开始将输出以下内容。

int(1) 
int(2)

3
自 PHP 7.1 起,支持两种解构格式。list() 现在支持显式索引,可以是字符串 http://php.net/manual/zh/migration71.new-features.php#migration71.new-features.support-for-keys-in-list,并且有一个使用方括号的 list 的简写 http://php.net/manual/zh/migration71.new-features.php#migration71.new-features.symmetric-array-destructuring。 - Jeff Horton
1
我们能像这样销毁某些东西吗?[$a=>$b] = ['a'=>'b']; - Vidy Videni
@VidyVideni,是的。刚测试过了,有效(顺便说一下,是在7.2上测试的)。 - Anwar
@VidyVideni - 你是如何使用那种语法为$test_arr提供值的? - temuri
6
从 php >= 7.2 版本开始,你可以这样做:['a' => $a, 'b' => $b] = ['a' => 1, 'b' => 2] - Jeffery ThaGintoki
显示剩余2条评论

81

我注意到被接受的答案漏掉了使用简写符号、使用extract存在的安全问题以及IDE问题的示例。

数值类型数组解构(PHP 7.1)

从PHP 7.1开始支持数值类型数组解构(对称数组解构),如下所示:

<?php
$data = [55, 'John', 'UK'];
[$id, $name] = $data; // short-hand (recommended)
list($id, $name) = $data; // long-hand

注意,如果不想要它们,可以省略部分项目。

关联数组解构(PHP 7.1)

您也可以像这样解构关联数组(在列表中支持键):

<?php
$data = ['id' => 55, 'firstName' => 'John', 'country' => 'UK']
['id' => $id, 'firstName' => $name] = $data; // short-hand (recommended)
list('id' => $id, 'firstName' => $name) = $data; // long-hand

请注意,如果您不需要它们,可以跳过项目。另外,变量名可以与属性名不同。

对象解构(PHP 7.1)

不幸的是,没有对象解构。但是,您可以使用get_object_vars将对象转换为关联数组,然后使用关联数组解构。

<?php
class User {
    public $id;
    public $name;
    public $country;
}

$user = new User();
$user->id = 55;
$user->name = 'John';
$user->country = 'UK';

['id' => $id, 'name' => $firstName] = get_object_vars($user)

然而,这可能会破坏一些IDE功能。以下是我在使用PHPStorm 2019.1时注意到的一些问题:

  • IDE可能不再理解变量的类型,因此您需要添加一些@var Type PHPDocs以保持自动完成功能。
  • 与重构工具不兼容。例如,如果您重命名其中一个属性,则数组解构部分也不会自动重命名。

因此,我建议按照正常方式进行操作:

$id = $user->id
$name = $user->firstName

不要使用extract

使用extract,所有变量都会被设置。因此,使用它是一个非常糟糕的主意,因为:

  • 它可能会导致安全问题。即使你小心谨慎,它也可能会在未来导致不明显的安全漏洞。如果你确实使用它,请勿与用户输入(例如$_GET$_POST)一起使用,除非你想让一个恶意黑客开心。
  • 可能会导致难以检测的错误
  • 如果类或数组在未来发生更改,引入新属性,如果与已使用的变量相符,则可能会破坏您的代码,除非您使用EXTR_SKIP标志或类似标志

2
作为后续,提取函数有什么问题:它可能会将未经过净化的、未知的变量放入全局(或本地函数)命名空间中,或覆盖您自己的变量。它还可能导致其他程序员想知道这种引入方式引入的变量是从哪里来的。基本上,只有在确定输入数据是安全的,并且您知道可能引入的所有变量时才明智地使用。在用户数据上谨慎使用。 - SherylHohman
1
需要注意的是:看起来在解构中没有像JS那样的“rest/spread”。这可能对调用返回关联数组的方法很有用,也许有一个标志确定返回的关联数组上可能或不可能有哪些字段--然后在确定响应的形状后,您将希望进一步解构。您可以解构可能存在或不存在的键,但是然后您将在日志中收到PHP通知,这是不可取的。最终,您需要编写围绕它的逻辑,这会减少本来可以实现的简单性。 - CTS_AE
1
关于PHPStorm的类型提示,另一个用户已经创建了一个插件来帮助解决这个问题。请参见https://dev59.com/l2Up5IYBdhLWcg3wPFtK#51032174和/或https://plugins.jetbrains.com/plugin/9927-deep-assoc-completion。 - Willem Renzema

1
变量变量是实现这一目标的一种方式:
$args = ['a' => 1, 'b' => 2, 'c' => 3];
foreach (['a', 'c'] as $v) $$v = $args[$v];
// $a is 1, $b is undefined, $c is 3

非常不美观,值得庆幸的是,在7.1中已经通过https://wiki.php.net/rfc/short_list_syntax解决了这个问题。这将使您能够在上面的示例中说['a' => $a, 'c' => $c] = $args;

由于7.1包括一种方法来使用与关联数组键不同的变量名称。在此处使用变量变量也很简单:

foreach (['a' => 'eh', 'b' => 'bee'] as $k => $v) $$v = $args[$k];
// $eh is 1, $bee is 2

一些开发人员和一些编码风格将$$var定义为类似于直接使用evalextractGPR魔术变量的反模式。这是因为使用变量变量会使代码更难理解,从而直接导致错误并防止静态代码分析工具正常运行。
如果您确实采用了$$var,使用${$var}形式可能会有所帮助,因为这使得作者没有简单地输入了过多的$符号变得明显,并且在审核其代码时可以避免立即获得负面反馈。

天啊!$$几乎和=>数组引用一样糟糕。在我看来,$$var_name很奇怪。但是=>(以及引用的&)让人感到很不舒服,我曾经对PHP有一些反感。花点时间说服自己“丑陋”的语法并不会使它难以理解或使用。我对正则表达式语法没有问题,但PHP需要消耗一些精力才能掌握。这种“困难”只存在于我的头脑中。奇怪的是,我对C语言语法的问题比较少。哈哈。 - SherylHohman

0
一种简单的解决方案是将对象作为数组读取。假设您使用@Yahya UddinUser对象,可以这样操作:
['id' => $id, 'firstName' => $name] = (array)$user
// $id = 55, name = 'john'

这将告诉PHP将此对象读取为关联数组。


0
以下函数实现了PHP 7.1的列表构造,并允许提取带有重命名支持的对象属性。
/**
 * Extracts specified properties from array or object into variables. Similar to PHP
 * 7.1 list construction: ['a' => $varA, 'b' => $varB] = ['a' => 10, 'b' => 20];
 * Missing properties will have null value.
 *
 * @param  array|object $box          Container to extract properties from.
 * @param  mixed[]      $boxToVarsMap Map of key from box => reference to variable, where value should be stored.
 *
 * @example toVars($_SERVER, ['REMOTE_ADDR' => &$remoteAddr, 'REQUEST_URI' => &$requestUri]);
 * @example toVars($object, ['user_name' => &$userName, 'pass' => &$password]);
 */
public static function toVars (array|object $box, array $boxToVarsMap): void {
  if (is_array($box)) {
    foreach ($boxToVarsMap as $key => &$var) {
      $var = $box[$key] ?? null;
    }
  } else {
    foreach ($boxToVarsMap as $key => &$var) {
      $var = $box->{$key} ?? null;
    }
  }
}

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