2

Given a variable that holds this string:

$property = 'parent->requestdata->inputs->firstname';

And an object:

$obj->parent->requestdata->inputs->firstname = 'Travis';

How do I access the value 'Travis' using the string? I tried this:

$obj->{$property}

But it looks for a property called 'parent->requestdata->inputs->firstname' not the property located at $obj->parent->requestdtaa->inputs->firstname`

I've tried various types of concatenation, use of var_export(), and others. I can explode it into an array and then loop the array like in this question.

But the variable '$property' can hold a value that goes 16 levels deep. And, the data I'm parsing can have hundreds of properties I need to import, so looping through and returning the value at each iteration until I get to level 16 X 100 items seems really inefficient; especially given that I know the actual location of the property at the start.

How do I get the value 'Travis' given (stdClass)$obj and (string)$property?

miken32
  • 42,008
  • 16
  • 111
  • 154

2 Answers2

3

My initial searches didn't yield many results, however, after thinking up a broader range of search terms I found other questions on SO that addressed similar problems. I've come up with three solutions. All will work, but not all will work for everyone.

Solution 1 - Looping

Using an approach similar to the question referenced in my original question or the loop proposed by @miken32 will work.

Solution 2 - anonymous function

The string can be exploded into an array. The array can then be parsed using array_reduce() to produce the result. In my case, the working code (with a check for incorrect/non-existent property names/spellings) was this (PHP 7+):

//create object - this comes from and external API in my case, but I'll include it here 
//so that others can copy and paste for testing purposes

$obj = (object)[
    'parent' => (object)[
        'requestdata' => (object)[
            'inputs' => (object)[
                'firstname' => 'Travis'
             ]
         ]
    ]
];

//string representing the property we want to get on the object

$property = 'parent->requestdata->inputs->firstname';

$name = array_reduce(explode('->', $property), function ($previous, $current) {
    return is_numeric($current) ? ($previous[$current] ?? null) : ($previous->$current ?? null); }, $obj);

var_dump($name); //outputs Travis

see this question for potentially relevant information and the code I based my answer on.

Solution 3 - symfony property access component

In my case, it was easy to use composer to require this component. It allows access to properties on arrays and objects using simple strings. You can read about how to use it on the symfony website. The main benefit for me over the other options was the included error checking.

My code ended up looking like this:

//create object - this comes from and external API in my case, but I'll include it here 
//so that others can copy and paste for testing purposes
//don't forget to include the component at the top of your class
//'use Symfony\Component\PropertyAccess\PropertyAccess;'

$obj = (object)[
    'parent' => (object)[
        'requestdata' => (object)[
            'inputs' => (object)[
                'firstname' => 'Travis'
             ]
         ]
    ]
];

//string representing the property we want to get on the object
//NOTE: syfony uses dot notation. I could not get standard '->' object notation to work.

$property = 'parent.requestdata.inputs.firstname';

//create symfony property access factory

$propertyAccessor = PropertyAccess::createPropertyAccessor();

//get the desired value

$name = $propertyAccessor->getValue($obj, $property);

var_dump($name); //outputs 'Travis'

All three options will work. Choose the one that works for you.

  • Of course, all the `array_*` functions that take a callback are also looping, they're just doing it internally. Ditto for the thousands of lines of code in the Symfony component. – miken32 Dec 10 '20 at 22:36
1

You're right that you'll have to do a loop iteration for each nested object, but you don't need to loop through "hundreds of properties" for each of them, you just access the one you're looking for:

$obj = (object)[
    'parent' => (object)[
        'requestdata' => (object)[
            'inputs' => (object)[
                'firstname' => 'Travis'
             ]
         ]
    ]
];
$property = "parent->requestdata->inputs->firstname";
$props = explode("->", $property);
while ($props && $obj !== null) {
    $prop = array_shift($props);
    $obj = $obj->$prop ?? null;
}
var_dump($obj);

Totally untested but seems like it should work and be fairly performant.

miken32
  • 42,008
  • 16
  • 111
  • 154
  • It is not an X-Y problem. I have provided the actual code from my app that applies to the question. I have one file that is provided as a data import map. It contains an array of stings that represent properties on an object. In another file I request the object from an external API. I need to access the list of properties in the array of strings on the returned object and add them to my database. If looping is indeed the only solution, I'm happy to accept the answer. It just felt inefficient given that the string contains the exact final location of the property. – Ryan Robinson Apr 21 '19 at 20:15
  • Ok that info was missing from the question, so couldn't be sure why you were trying to do this. Too bad you can't request the property directly from the API. – miken32 Apr 21 '19 at 20:25