12

There are many questions similar to this, however this is slightly different since it's about deep object property access, not just one level of depth.

Let's say I have a variable containing the string foo.bar.

$user = new User();
$user->foo = new Foo();
$user->foo->bar = "Hello World";

$variable = "foo.bar"

I would like to echo $user->foo->bar by making use of $variable:

echo $user->foo->bar

This is what I have tried so far but with no success (it says NULL):

$value = str_replace(".", "->", $value);
echo $user->{$value};
Pablo
  • 4,821
  • 12
  • 52
  • 82
GiamPy
  • 3,543
  • 3
  • 30
  • 51
  • FYI, `$user->{$value}` tries to access the property called "foo->bar" (no, not the property "bar" on the property "foo", but the property called "foo->bar") on `$user`… – deceze Jan 11 '17 at 15:23
  • So `$user->{$value}` means `$user->{"foo->bar"}`? – GiamPy Jan 11 '17 at 15:35
  • Yup. It doesn't matter what the variable contains, `$o->$p` (same thing with or without `{}`) means you're accessing *one* direct property on `$o`, not a nested chain. – deceze Jan 11 '17 at 15:48

5 Answers5

23

It is very easy to reduce the object path using variable property notation ($o->$p):

$path = 'foo.bar';
echo array_reduce(explode('.', $path), function ($o, $p) { return $o->$p; }, $user);

This could easily be turned into a small helper function.

deceze
  • 510,633
  • 85
  • 743
  • 889
11

A little improvement added to @deceze post.

This allow handling cases where you need to go through arrays also.

$path = 'foo.bar.songs.0.title';
echo array_reduce(explode('.', $path), function ($o, $p) { return is_numeric($p) ? $o[$p] : $o->$p; }, $user);

Edit:

And if you have PHP 7+, then the following will safely return null if a property's name is mistyped or if it doesn't exist.

$path = 'foo.bar.songs.0FOOBAR.title';
echo array_reduce(explode('.', $path), function ($o, $p) { return is_numeric($p) ? ($o[$p] ?? null) : ($o->$p ?? null); }, $user);
asiby
  • 3,229
  • 29
  • 32
0

There is no easy way to do it.

Fortunately though, lots of people want to do this, so there's libraries that support it, like Symfony's PropertyAccessor:

http://symfony.com/doc/current/components/property_access.html

Erik
  • 3,598
  • 14
  • 29
  • Why is this downvoted? It's a clean solution for the problem. – muffe Jan 11 '17 at 16:41
  • @muffe because solution is easy and doesn't involve using whole framework – Vir Jan 15 '17 at 23:13
  • 2
    @Vir it's not a framework, just a library. And the library solves a ton of bugs and issues that you'll have when using the simple solutions. – Erik Jan 16 '17 at 06:30
  • 2
    Exactly this. Almost every component of Symfony works standalone and is bound to work better than almost anything you'll find here. – muffe Jan 16 '17 at 15:13
  • @Erik still in php solution is one liner here – Vir Jan 16 '17 at 18:07
  • @Vir: a one liner that will cause a ton of bugs, probably. Most one-liners above will crash if any level of the structure doesn't exist, for example. – Erik Jan 16 '17 at 20:13
  • 2
    @Erik protection against those crashes is still simple enough. What was posted here are just bare solutions to be refined for target use. Also notice that projects could use different frameworks and other stuff - mixing it with Symfony just for simple piece of code isn't IMHO good way to go. That problem isn't that complicated to justify employing whole 3rd party code. And well if "any level of structure doesn't exist" than this code SHOULD crash and that exception (probably need to add throw there) should be then catched and serviced. – Vir Jan 17 '17 at 10:18
0

I am posting this as a compliment to an answer (How to write getter/setter to access multi-level array by key names?) that does the same for arrays.

Create the $path array via explode() (or add to the function), then use references.

$path = explode('.', $variable);

Getter

function get($path, $object) {
    $temp = &$object;

    foreach($path as $var) {
        $temp =& $temp->$var;
    }
    return $temp;
}

$value = get($path, $user);

And of course the evil eval(), not recommended:

$value = str_replace('.', '->', $variable);
eval("echo \$user->$value;");
Community
  • 1
  • 1
AbraCadaver
  • 78,200
  • 7
  • 66
  • 87
  • FWIW, you could easily adapt my `array_reduce` to work with arrays too (`return $a[$k]`). You could even dynamically figure out whether you're dealing with an array or object and use `$o->$p` or `$a[$k]` dynamically. – deceze Jan 11 '17 at 16:53
0

I have written a recursive algorithm for finding all the values ​​of properties.

    public function findAttributeFromJson($json,$attributeFind,$assignAttribute)
{
    if(!is_array($json)) return $assignAttribute;

    $properties = array_keys($json);

    foreach ($properties as $value) {
        if($value === $attributeFind)
        {
            $assignAttribute[count($assignAttribute)] = $json[$value];
        }
        $assignAttribute = $this->findAttributeFromJson($json[$value],$attributeFind,$assignAttribute);
    }
    return $assignAttribute;
}

And use it

        $arrResult = array();
        $arrResult = $this->findAttributeFromJson($arrFind,$properties,$arrResult );