5

I want to dynamically access value of variable, let's say I have this array:

$aData = array(
  'test' => 123
);

Standard approach to print the test key value would be:

print $aData['test'];

However, if I have to work with string representation of variable (for dynamic purposes)

$sItem = '$aData[\'test\']';

how can I achieve to print aData key named test? Neither of examples provided below works

print $$sItem;
print eval($sItem);

What would be the solution?

Shef
  • 44,808
  • 15
  • 79
  • 90
nabizan
  • 3,185
  • 5
  • 26
  • 38

6 Answers6

6

Your eval example is lacking the return value:

print eval("return $sItem;");

should do it:

$aData['test'] = 'foo';

$sItem = '$aData[\'test\']';

print eval("return $sItem;"); # foo

But it's not recommended to use eval normally. You can go into hell's kitchen with it because eval is evil.

Instead just parse the string and return the value:

$aData['test'] = 'foo';

$sItem = '$aData[\'test\']';

$r = sscanf($sItem, '$%[a-zA-Z][\'%[a-zA-Z]\']', $vName, $vKey);
if ($r === 2)
{
    $result = ${$vName}[$vKey];
}
else
{
    $result = NULL;
}

print $result; # foo

This can be done with some other form of regular expression as well.

As your syntax is very close to PHP an actually a subset of it, there is some alternative you can do if you want to validate the input before using eval. The method is to check against PHP tokens and only allow a subset. This does not validate the string (e.g. syntax and if a variable is actually set) but makes it more strict:

function validate_tokens($str, array $valid)
{
    $vchk = array_flip($valid);
    $tokens = token_get_all(sprintf('<?php %s', $str));
    array_shift($tokens);
    foreach($tokens as $token)
        if (!isset($vchk[$token])) return false;
    return true;
}

You just give an array of valid tokens to that function. Those are the PHP tokens, in your case those are:

T_LNUMBER (305) (probably)
T_VARIABLE (309)
T_CONSTANT_ENCAPSED_STRING (315)

You then just can use it and it works with more complicated keys as well:

$aData['test'] = 'foo';
$aData['te\\\'[]st']['more'] = 'bar';

$sItem = '$aData[\'test\']';
$vValue = NULL;
if (validate_tokens($sItem, array(309, 315, '[', ']')))
{
    $vValue = eval("return $sItem;");
}

I used this in another answer of the question reliably convert string containing PHP array info to array.

Community
  • 1
  • 1
hakre
  • 193,403
  • 52
  • 435
  • 836
  • @all: thanks all of you guys, from what im planning to do (in big picture) it is best solution for me to use eval function ... i just forget to use return – nabizan Dec 02 '11 at 13:06
5

No eval necessary if you have (or can get) the array name and key into separate variables:

$aData = array(
  'test' => 123
);

$arrayname = 'aData';
$keyname = 'test';

print ${$arrayname}[$keyname]; // 123
Boann
  • 48,794
  • 16
  • 117
  • 146
3

You can just use it like an ordinary array:

$key = "test";

print $aData[$key];

Likewise $aData could itself be an entry in a larger array store.


As alternative, extracting the potential array keys using a regex and traversing an anonymous array (should have mentioned that in your question, if) with references would be possible. See Set multi-dimensional array by key path from array values? and similar topics.


Personally I'm using a construct like this to utilize dynamic variable paths like varname[keyname] instead (similar to how PHP interprets GET parameters). It's just an eval in sheeps clothing (do not agree with the eval scaremongering though):

$val = preg_replace("/^(\w)+(\[(\w+)])$/e", '$\1["\3"]', "aData[test]");
Community
  • 1
  • 1
mario
  • 144,265
  • 20
  • 237
  • 291
  • Apart from the fact that you probably mean print $aData[$key], and not $aData[$test], this is the best solution by far. +1 – Berry Langerak Dec 02 '11 at 12:48
  • 1
    If you ignore what's being asked then it's the best solution, yes! :) – Jan Hančič Dec 02 '11 at 12:49
  • +1. I think this is the only reasonable, and working solution. No matter how the others have suggested to use `eval`, none of them will work to return the _key_ `test`. Probably some regex string parsing would achieve to extract the key, but eval would never manage to do that based on the way the OP has the string. – Shef Dec 02 '11 at 12:50
  • @JanHančič What is being asked is the _key_ not the result. Neither of the `eval` examples work to retrieve the _key_. The string parsing solution from hakre does work, but it's a roundabout trip. Why would you be the one assigning the key to a substring of a string variable, then having to go over that entire string to fetch the key, when you could have the key ready all the times. – Shef Dec 02 '11 at 13:00
  • @JanHančič Maybe, you might be right. If the question is to access the value, then you are right. However, we would have to wait for OP's response to see if I understand it or not. :) – Shef Dec 02 '11 at 13:03
  • if i have to work with strings like '[status][msg][simple]' so whit this kind of strings i would have to parse it and count the result to use this solution so it is much simple to just use eval function ... those dynamically passing variables is using only on internal purpose, no passing variables from users, so using eval is safe – nabizan Dec 02 '11 at 13:11
1

The only solution in your case is to use Eval().

But please be very very very careful when doing this! Eval will evaluate (and execute) any argument you pass to it as PHP. So if you will feed it something that comes from users, then anyone could execute any PHP code on your server, which goes without saying is a security hole the size of Grand canyon!.

edit: you will have to put a "print" or "echo" inside your $sItem variable somehow. It will either have to be in $sItem ($sItem = 'echo $aData[\'test\']';) or you will have to write your Eval() like this: Eval ( 'echo ' . $sData ).

Jan Hančič
  • 53,269
  • 16
  • 95
  • 99
1
$sItem = '$aData[\'test\']';
eval('$someVar = '.$sItem.';');
echo $someVar;

Use eval() with high caution as others aldready explained.

Maxime Pacary
  • 22,336
  • 11
  • 85
  • 113
1

You could use this method

function getRecursive($path, array $data) {
        // transform "foo['bar']" and 'foo["bar"]' to "foo[bar]"
        $path = preg_replace('@\[(?:"|\')(.+)(?:"|\')\]@Uis', '[\1]', $path);

        // get root
        $i = strpos($path, '[');
        $rootKey = substr($path, 0, $i);
        if (!isset($data[$rootKey])) {
            return null;
        }
        $value = $data[$rootKey];

        $length = strlen($path);
        $currentKey = null;
        for (; $i < $length; ++$i) {
            $char = $path[$i];

            switch ($char) {
                case '[':
                    if ($currentKey !== null) {
                        throw new InvalidArgumentException(sprintf('Malformed path, unexpected "[" at position %u', $i));
                    }
                    $currentKey = '';
                    break;
                case ']':
                    if ($currentKey === null) {
                        throw new InvalidArgumentException(sprintf('Malformed path, unexpected "]" at position %u', $i));
                    }

                    if (!isset($value[$currentKey])) {
                        return null;
                    }

                    $value = $value[$currentKey];
                    if (!is_array($value)) {
                        return $value;
                    }

                    $currentKey = null;
                    break;
                default:
                    if ($currentKey === null) {
                        throw new InvalidArgumentException(sprintf('Malformed path, unexpected "%s" at position %u', $char, $i));
                    }
                    $currentKey .= $char;
                    break;
            }
        }

        if ($currentKey !== null) {
            throw new InvalidArgumentException('Malformed path, must be and with "]"');
        }

        return $value;
    }
azat
  • 3,545
  • 1
  • 28
  • 30
  • That's a good approach. A single `preg_match_all` would however suffice to get the array keys at once, avoding the `[` parsing `]`. – mario Dec 02 '11 at 13:18
  • @mario thanks. Give me such please, with given that such key can not exist in array – azat Dec 02 '11 at 13:19
  • Missed two `+`. You'd still need to probe if the keys exist. It would be just be a more convenient way to break out the potential keys. Something like `preg_match_all('/((?<=^|\$)\w+|(?<=\[[\'"])[^\'"]+(?=[\'"]\]))/')` for example breaks up `foo["bar"]` into `foo` and `bar` – mario Dec 02 '11 at 13:49
  • @mario, do you test it? It returns `foo` and `foo`, and `w+` I fix myself :) – azat Dec 02 '11 at 13:54
  • Yes, tested it. You forgot the second `+` then. – mario Dec 02 '11 at 13:55