78

In CoffeeScript, Clojure, ES6 and many other languages we have destructuring of objects/maps/etc somewhat like this:

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

I've found the list function in php which lets you destructure arrays like so:

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

Is there a way to destructure objects or associative arrays in PHP? If not in the core libs maybe someone wrote some smart helper function?

Ben Creasy
  • 3,825
  • 4
  • 40
  • 50
Cotten
  • 8,787
  • 17
  • 61
  • 98
  • Fringe consideration for symmetric array destructuring: [While destructuring an array, can the same element value be accessed more than once?](https://stackoverflow.com/q/71807960/2943403) – mickmackusa May 03 '23 at 22:37

4 Answers4

83

For PHP 7.0 and below that is beyond the functionality of list. The docs state:

list only works on numerical arrays and assumes the numerical indices start at 0.

One of the things that could suit your purpose would be the extract() function which imports variables from an array into the current symbol table. While with list you are able to define variable names explicitly, extract() does not give you this freedom.

Extracting an associative array

With extract you could do something like that:

<?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"

Extracting an Object

Extracting an object works almost the same. Since extract only takes an array as an argument we need to get the objects properties as an array. get_object_vars does that for you. It returns an associative array with all public properties as key and their values as value.

<?php

class User {

    public $name = 'Thomas';

}

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

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

Pitfalls

extract() is not the same as list since it does not allow you to explicitly define the variable names that get exported to the symbol table. The variable names correspond the array keys by default.

  • list is a language construct while extract() is a function
  • It might happen that you overwrite variables that you have defined beforehand unintentionally
  • Your array keys might be invalid as variable names

With the $flags parameter that you can pass as second argument to extract() you can influence the behavior in case of colliding or invalid variables. But still it's important to know how extract() works and to use it with cauton.

Edit: As of PHP 7.1 this is possible:

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

You can now specify keys in list(), or its new shorthand [] syntax. This enables destructuring of arrays with non-integer or non-sequential keys.

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

The shorthand array syntax ([]) may now be used to destructure arrays for assignments (including within foreach), as an alternative to the existing list() syntax, which is still supported.

For example this:

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

Will output the following as of 7.1.0

int(1) 
int(2)
Georg Schölly
  • 124,188
  • 49
  • 220
  • 267
thpl
  • 5,810
  • 3
  • 29
  • 43
  • 3
    As of PHP 7.1 there is destructuring using one of two formats. list() now supports explicit indexes, which can be strings http://php.net/manual/en/migration71.new-features.php#migration71.new-features.support-for-keys-in-list and there is a shorthand for list using square brackets http://php.net/manual/en/migration71.new-features.php#migration71.new-features.symmetric-array-destructuring – Jeff Horton Jan 17 '17 at 18:32
  • 1
    can we destruct something like this? [$a=>$b] = ['a'=>'b']; – Vidy Videni Aug 21 '18 at 02:54
  • @VidyVideni, yep. Just tested, it works (tested on 7.2 btw). – Anwar Nov 03 '18 at 21:34
  • @VidyVideni - how do you provide $test_arr with that syntax? – temuri Jul 09 '19 at 19:05
  • What should I do if some keys might not exist? For getting single keys I can use `??`. – quant2016 Jul 23 '19 at 10:02
  • `extract()` is exactly what I want. Thank you for the help. – Shahrukh Anwar Aug 02 '19 at 12:56
  • 6
    As of php >= 7.2 you can do this, `['a' => $a, 'b' => $b] = ['a' => 1, 'b' => 2]`. – Jeffery ThaGintoki Sep 12 '19 at 18:02
76

I noticed the accepted answer missed out examples that use the short-hand notation, security issues with using extract, and IDE issues.

Numerical Array Destructuring (PHP 7.1)

As of PHP 7.1 numerical array destructuring (Symetric array destructuring) is supported like so:

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

Notice that you can miss items out if you don't want them.

Associative Array Destructuring (PHP 7.1)

You can also destructure associative arrays (Support for keys in list) like so:

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

Notice that you can miss items out if you don't want them. Also the variable name can be different to the property name.

Object Destructuring (PHP 7.1)

Unfortunately there is no object destructuring. However you can convert an object to an associative array using get_object_vars, and then use associative array destructuring.

<?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)

However, this can break some IDE features. These are some issues I noticed when using PHPStorm 2019.1:

  • IDE's may no longer understand the type for the variables, so you would need to add some @var Type PHPDocs to maintain auto-complete functionality
  • Does not work well with refactoring tools. For example, if you rename one of the properties, the array destructuring portion will not also automatically rename.

So I recommend just doing it the normal way:

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

Do NOT use extract

With extract, all variables are always set. There it is a really bad idea to use it because:

  • It can lead to security issues. Even if your careful, it can lead to non-obvious security holes in the future. If you do use it, don't use it with user input (e.g. $_GET, $_POST), unless you want to make a malicious hacker's day.
  • Can lead to hard to detect bugs
  • If the class or array changes in the future, by introducing new properties, it can break your code if it coincides with an already used variable, unless you use the EXTR_SKIP flag or similar
Yahya Uddin
  • 26,997
  • 35
  • 140
  • 231
  • 2
    As a follow up, [What's so wrong with extract](https://stackoverflow.com/questions/829407/what-is-so-wrong-with-extract): it can put unsanitized, unknown variables in the global (or local function) namespace, or overwrite your own variables. It can also lead other programmers to wonder where a variable introduced this way came into play. Basically, use wisely only when sure of the data coming in is safe, and you are aware of all variables that could be introduced. Use with caution on user data. – SherylHohman Mar 28 '20 at 18:42
  • 1
    Something to note: doesn't look like there's a "rest/spread" on the destructing like JS has. Which could be useful for calling a method that returns an associate array, maybe there's a flag that determines what fields may or may not be on the returned associate array -- you would then want to further destructure after determining the shape of the response. You could destructure the keys that may or may not be there, but then you'll get PHP Notices in your log which is not favorable. You ultimately need to end up writing logic around it which reduces the simplicity that could have been. – CTS_AE Dec 13 '20 at 14:43
  • 1
    Regarding the PHPStorm typehinting, another user has since created a plugin to help with that. See https://stackoverflow.com/a/51032174/1797579 and/or https://plugins.jetbrains.com/plugin/9927-deep-assoc-completion – Willem Renzema Sep 05 '22 at 13:50
1

Variable variables are one way to achieve this:

$args = ['a' => 1, 'b' => 2, 'c' => 3];
foreach (['a', 'c'] as $v) $$v = $args[$v];
// $a is 1, $b is undefined, $c is 3

It's really not pretty, and thankfully this has been addressed in 7.1 by https://wiki.php.net/rfc/short_list_syntax . This would let you say ['a' => $a, 'c' => $c] = $args; in the above example.

Since 7.1 includes a way to use a different name for your var than the assoc array key. This is pretty straight-forward using variable variables here too:

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

Some developers, and some coding styles, define $$var as an anti-pattern similar to using eval, extract, and the GPR magic variables directly. This is because using variable variables makes code harder to understand, which leads directly to bugs and prevents static code analysis tools from functioning.

If you do adopt $$var, it can be helpful to use the ${$var} form instead, which makes it obvious that the author didn't simply type one too many $'s, and may spare the author immediate negative feedback when their code is audited.

Wil
  • 757
  • 9
  • 12
Josh from Qaribou
  • 6,776
  • 2
  • 23
  • 21
  • yikes! `$$` is almost as bad as the `=>` array referencing. `$$var_name` is weird, in my eyes. But `=>` (and `&` for references) is so off putting, I used to have an aversion to PHP. Took some convincing that "ugly" syntax does not make it difficult to understand or use. I had no problems with regex syntax, but PHP, that took some convincing. The "difficulty" was just in my head. Oddly, I had less issue with C syntax. lol. – SherylHohman Apr 06 '20 at 20:36
0

One simple solution is to read the Object as an Array. So assuming you use @Yahya Uddin's User object, you can do like so:

['id' => $id, 'firstName' => $name] = (array)$user
// $id = 55, name = 'john'

This will tell PHP to read this objective as an associative Array.

Nadav
  • 1,730
  • 16
  • 20