2

I can easily write to and read from a sub-array in the session array.

$_SESSION['a']['b']['c']['value']=123;
$val=$_SESSION['a']['b']['c']['value'];

Instead of hard coding the "location" where the value is written, I would like it to be definable via a string or some other way. The following will obviously not work, but hopefully will better explain the intent.

$prefix="['a']['b']['c']";  //defined in config page, etc
$_SESSION.$prefix.['value']=123;
$val=$_SESSION.$prefix.['value'];

How can this be accomplished?

Tomasz Kowalczyk
  • 10,472
  • 6
  • 52
  • 68
user1032531
  • 24,767
  • 68
  • 217
  • 387

2 Answers2

3

PropertyAccess

There is an excellent Symfony component for such tasks, named PropertyAccess. You can use it as follows:

$persons = array('a' => array('b' => 5.7));
$accessor = PropertyAccess::createPropertyAccessor();
echo $accessor->getValue($persons, '[a][b]'); // 5.7

You can install it using Composer as described in docs or fetch directly from GitHub.

Custom solution

This is a complete solution, I'm really impressed that it works... but it works! Check the code below, assert()'s demonstrate the usage:

<?php
function arrayPropertyPathGet(array $arr, $path) {
    $parts = explode('.', $path);
    $ret = $arr;
    foreach($parts as $part) {
        $ret = $ret[$part];
        }
    return $ret;
    }

function arrayPropertyPathSet(array &$arr, $path, $value) {
    $parts = explode('.', $path);
    $tmp = &$arr;
    foreach($parts as $part) {
        if(!isset($tmp[$part])) { return false; }
        $tmp = &$tmp[$part];
        }
    $tmp = $value;
    return true;
    }

$test = array('a' => array('b' => 'value'));

assert('value' === arrayPropertyPathGet($test, 'a.b'));
assert(true === arrayPropertyPathSet($test, 'a.b', 'other'));
assert('other' === arrayPropertyPathGet($test, 'a.b'));

Side note

As a theoretical side note (do not use this for anything other than learning purposes) you can experiment with eval(), such as:

eval("$value = $persons['a']['b']");
Tomasz Kowalczyk
  • 10,472
  • 6
  • 52
  • 68
  • Thanks Tomasz. While I don't want to use a new framework right now, I am sure I can look through the Symfony code and see how they do it. – user1032531 Aug 08 '14 at 10:59
  • See my updated answer, I provided solution without 3rd party code. But really look into that component, you don't need to use framework to have only it in your project. – Tomasz Kowalczyk Aug 08 '14 at 11:01
  • 1
    Quick note, you don't need the whole framework, symfony is well known for being decoupled, you can use only the PropertyAcces component without the rest of the framework code as stated in the answer – mTorres Aug 08 '14 at 11:03
  • These Symfony2 components were specifically designed to be used anywhere, they're not bound to any framework. – Tomasz Kowalczyk Aug 08 '14 at 11:03
  • @user1032531 Did my updated answer solve your problem? – Tomasz Kowalczyk Aug 08 '14 at 11:11
  • Thanks Tomasz. Yes, it did help. Just going through it right now. Yes, I know eval is evil, but why? – user1032531 Aug 08 '14 at 11:41
  • Eval is evil whenever you put anything dynamically inside it (user input, configurable data). It *may* not be evil when used with static input but still, there are better solutions for that use case. Eval is circumventing the whole programming process with uncontrollable code sequence (syntax errors, context errors and so on - you don't know you're in trouble until you execute it). There are many reasons and I'm yet to see a valid reason to use `eval()`. As for dynamic code execution there is also an excellent ExpressionLanguage Symfony Component. No eval() required. :) – Tomasz Kowalczyk Aug 08 '14 at 11:47
  • Thanks Tomasz. Your getPropertyPath() function didn't originally work for me, and I made a small change. Please reject it if you feel it shouldn't have been changed. Also, howe would you recommend setting a value similar to how a value is returned? – user1032531 Aug 08 '14 at 12:00
  • You only added a check for non-existent elements, that's fine and it definitely needs to be there - I wrote code for an optimistic case where everything is where it should be. :) As for setting value in my opinion you're out of luck, the only thing that could work is getting subsequent array elements by reference and then trying to modify them in place, but I need to check if it's possible. If my answer helped you, please upvote and accept. :) – Tomasz Kowalczyk Aug 08 '14 at 12:47
  • Check my latest update, everything perfectly solved and working. Now I think I earned my points. :D – Tomasz Kowalczyk Aug 08 '14 at 13:00
  • Thank you Tomasz. Yes, I was thinking reference would be needed to set the value. I will go over it in detail. – user1032531 Aug 08 '14 at 13:04
0

I faced the same problem a few times ago, and as I didn't find any solution, I made one by myself, if that can help you in anyway (only the interesting part) :

class ArrayAccessor {

    private $prefix;

    function setPrefix() {
        $this->prefix = array();
        for ($i = 0; $i < func_num_args(); ++$i) {
            $this->prefix[] =  func_get_arg($i);
        }
    }

    function getFromPrefix(array $array) {
        $tmp_array = $array;
        foreach ($this->prefix as $pre) {
            if (isset ($tmp_array[$pre])) {
                $tmp_array = $tmp_array[$pre];
            } else {
                return null;
            }
        } 
        return $tmp_array;
    }

}



$Access = new ArrayAccessor();
$Access->setPrefix('Hi', 'Toto');

$MyTestArray['Hi']['Toto'] = 'Works';
var_dump ($Access->getFromPrefix($MyTestArray));

$Access->setPrefix('Hi');
var_dump ($Access->getFromPrefix($MyTestArray));

$Access->setPrefix('No');
var_dump ($Access->getFromPrefix($MyTestArray));

Result :

string(5) "Works"

array(1) {
  ["Toto"]=>
  string(5) "Works"
}

NULL
Clément Malet
  • 5,062
  • 3
  • 29
  • 48
  • Thanks Clement, Going through your script now. Did you have a method that actually sets the value. – user1032531 Aug 08 '14 at 11:46
  • @user1032531 Sets what value ? `$Access->getFromPrefix($MyTestArray) = 'My new value';` ? – Clément Malet Aug 08 '14 at 11:49
  • Setting value would be difficult if not possible in this case - you could try by getting references to array elements to modify them in place, but that won't be nice code to write or maintain. – Tomasz Kowalczyk Aug 08 '14 at 11:50
  • @user1032531 Ah right my bad, I see what you mean, yes I've a setFromPrefix method too, but can't access the code anymore now, I will edit my answer tonight if you still need it. (And yes, it's a little bit harder that the getter one). Anyway, I think using the Symfony solution sounds much more robust – Clément Malet Aug 08 '14 at 12:04
  • The last `var_dump()` should return `array('Toto' => 'Works')`, don't you think? Anyway, check my answer, I solved both problems in a much simpler manner. – Tomasz Kowalczyk Aug 08 '14 at 13:04
  • @TomaszKowalczyk No, it's just an example of what this method will return if the prefix cannot be found in the array : 'No' is nowhere in the array, so it returns null. It's just the way I designed it – Clément Malet Aug 08 '14 at 13:07
  • Sorry, I didn't see that scroll, didn't see last case. My bad. – Tomasz Kowalczyk Aug 08 '14 at 13:09