php change value of multidimensional array - php

I got a problem.
I make a function to update my config.json file.
The problem is, my config.json is a multdimensional array. To get a value of a key i use this function:
public function read($key)
{
$read = explode('.', $key);
$config = $this->config;
foreach ($read as $key) {
if (array_key_exists($key, $config)) {
$config = $config[$key];
}
}
return $config;
}
I also made a function to update a key. But the problem is if i make update('database.host', 'new value'); it dont updates only that key but it overrides the whole array.
This is my update function
public function update($key, $value)
{
$read = explode('.', $key);
$config = $this->config;
foreach ($read as $key) {
if (array_key_exists($key, $config)) {
if ($key === end($read)) {
$config[$key] = $value;
}
$config = $config[$key];
}
}
print_r( $config );
}
my config.json looks like this:
{
"database": {
"host": "want to update with new value",
"user": "root",
"pass": "1234",
"name": "dev"
},
some more content...
}
I have a working function but thats not really good. I know that the max of the indexes only can be three, so I count the exploded $key and update the value:
public function update($key, $value)
{
$read = explode('.', $key);
$count = count($read);
if ($count === 1) {
$this->config[$read[0]] = $value;
} elseif ($count === 2) {
$this->config[$read[0]][$read[1]] = $value;
} elseif ($count === 3) {
$this->config[$read[0]][$read[1]][$read[3]] = $value;
}
print_r($this->config);
}
Just to know: the variable $this->config is my config.json parsed to an php array, so nothing wrong with this :)

After I had read your question better I now understand what you want, and your read function, though not very clear, works fine.
Your update can be improved though by using assign by reference & to loop over your indexes and assign the new value to the correct element of the array.
What the below code does is assign the complete config object to a temporary variable newconfig using call by reference, this means that whenever we change the newconfig variable we also change the this->config variable.
Using this "trick" multiple times we can in the end assign the new value to the newconfig variable and because of the call by reference assignments the correct element of the this->config object should be updated.
public function update($key, $value)
{
$read = explode('.', $key);
$count = count($read);
$newconfig = &$this->config; //assign a temp config variable to work with
foreach($read as $key){
//update the newconfig variable by reference to a part of the original object till we have the part of the config object we want to change.
$newconfig = &$newconfig[$key];
}
$newconfig = $value;
print_r($this->config);
}

You can try something like this:
public function update($path, $value)
{
array_replace_recursive(
$this->config,
$this->pathToArray("$path.$value")
);
var_dump($this->config);
}
protected function pathToArray($path)
{
$pos = strpos($path, '.');
if ($pos === false) {
return $path;
}
$key = substr($path, 0, $pos);
$path = substr($path, $pos + 1);
return array(
$key => $this->pathToArray($path),
);
}
Please note that you can improve it to accept all data types for value, not only scalar ones

Related

How to access multiple index array dynamically? [duplicate]

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?
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']");
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

Using a delimited string, how do I get an array item relative to the parsed string?

Given a string in the format of "one/two" or "one/two/three", with / as delimiters, I need to get the value (as a reference) in a 2d array. In this specific case, I'm accessing the $_SESSION variable.
In the SessionAccessor::getData($str) function, I don't know what to put in there to make it parse the delimited string and return the array item. The idea is I won't know which array key I'm accessing. The function will be generic.
class SessionAccessor {
static &function getData($str) {
// $str = 'one/two/three'
return $_SESSION['one']['two']['three'];
}
}
/** Below is an example of how it will be expected to work **/
/**********************************************************/
// Get a reference to the array
$value =& SessionAccessor::getData('one/two/three');
// Set the value of $_SESSION['one']['two']['three'] to NULL
$value = NULL;
Here's the solution provided by #RocketHazmat here at StackOverflow:
class SessionAccessor {
static function &getVar($str) {
$arr =& $_SESSION;
foreach(explode('/',$str) as $path){
$arr =& $arr[$path];
}
return $arr;
}
}
From what you've described, it sounds like this is what you're after;
&function getData($str) {
$path = explode('/', $str);
$value = $_SESSION;
foreach ($path as $key) {
if (!isset($value[$key])) {
return null;
}
$value = $value[$key];
}
return $value;
}
EDIT
To also allow for the values to be set, it would be best to use a setData method. This should hopefully do what you hoped for;
class SessionAccessor {
static function getData($str) {
$path = explode('/', $str);
$value = $_SESSION;
foreach ($path as $key) {
if (!isset($value[$key])) {
return null;
}
$value = $value[$key];
}
return $value;
}
static function setData($str, $newValue) {
$path = explode('/', $str);
$last = array_pop($path);
$value = &$_SESSION;
foreach ($path as $key) {
if (!isset($value[$key])) {
$value[$key] = array();
}
$value = &$value[$key];
}
$value[$last] = $newValue;
}
}

PHP Function that can return value from an array key a dynamic number of levels deep

Using PHP, I would like to write a function that accomplishes what is shown by this pseudo code:
function return_value($input_string='array:subArray:arrayKey')
{
$segments = explode(':',$input_string);
$array_depth = count(segments) - 1;
//Now the bit I'm not sure about
//I need to dynamically generate X number of square brackets to get the value
//So that I'm left with the below:
return $array[$subArray][$arrayKey];
}
Is the above possible? I'd really appreciate some pointer on how to acheive it.
You can use a recursive function (or its iterative equivalent since it's tail recursion):
function return_value($array, $input_string) {
$segments = explode(':',$input_string);
// Can we go next step?
if (!array_key_exists($segments[0], $array)) {
return false; // cannot exist
}
// Yes, do so.
$nextlevel = $array[$segments[0]];
if (!is_array($nextlevel)) {
if (1 == count($segments)) {
// Found!
return $nextlevel;
}
// We can return $nextlevel, which is an array. Or an error.
return false;
}
array_shift($segments);
$nextsegments = implode(':', $segments);
// We can also use tail recursion here, enclosing the whole kit and kaboodle
// into a loop until $segments is empty.
return return_value($nextlevel, $nextsegments);
}
Passing one object
Let's say we want this to be an API and pass only a single string (please remember that HTTP has some method limitation in this, and you may need to POST the string instead of GET).
The string would need to contain both the array data and the "key" location. It's best if we send first the key and then the array:
function decodeJSONblob($input) {
// Step 1: extract the key address. We do this is a dirty way,
// exploiting the fact that a serialized array starts with
// a:<NUMBEROFITEMS>:{ and there will be no "{" in the key address.
$n = strpos($input, ':{');
$items = explode(':', substr($input, 0, $n));
// The last two items of $items will be "a" and "NUMBEROFITEMS"
$ni = array_pop($items);
if ("a" != ($a = array_pop($items))) {
die("Something strange at offset $n, expecting 'a', found {$a}");
}
$array = unserialize("a:{$ni}:".substr($input, $n+1));
while (!empty($items)) {
$key = array_shift($items);
if (!array_key_exists($key, $array)) {
// there is not this item in the array.
}
if (!is_array($array[$key])) {
// Error.
}
$array = $array[$key];
}
return $array;
}
$arr = array(
0 => array(
'hello' => array(
'joe','jack',
array('jill')
)));
print decodeJSONblob("0:hello:1:" . serialize($arr));
print decodeJSONblob("0:hello:2:0" . serialize($arr));
returns
jack
jill
while asking for 0:hello:2: would get you an array { 0: 'jill' }.
you could use recursion and array_key_exists to walk down to the level of said key.
function get_array_element($key, $array)
{
if(stripos(($key,':') !== FALSE) {
$currentKey = substr($key,0,stripos($key,':'));
$remainingKeys = substr($key,stripos($key,':')+1);
if(array_key_exists($currentKey,$array)) {
return ($remainingKeys,$array[$currentKey]);
}
else {
// handle error
return null;
}
}
elseif(array_key_exists($key,$array)) {
return $array[$key];
}
else {
//handle error
return null;
}
}
Use a recursive function like the following or a loop using references to array keys
<?php
function lookup($array,$lookup){
if(!is_array($lookup)){
$lookup=explode(":",$lookup);
}
$key = array_shift($lookup);
if(!isset($array[$key])){
//throw exception if key is not found so false values can also be looked up
throw new Exception("Key does not exist");
}else{
$val = $array[$key];
if(count($lookup)){
return lookup($val,$lookup);
}
return $val;
}
}
$config = array(
'db'=>array(
'host'=>'localhost',
'user'=>'user',
'pass'=>'pass'
),
'data'=>array(
'test1'=>'test1',
'test2'=>array(
'nested'=>'foo'
)
)
);
echo "Host: ".lookup($config,'db:host')."\n";
echo "User: ".lookup($config,'db:user')."\n";
echo "More levels: ".lookup($config,'data:test2:nested')."\n";
Output:
Host: localhost
User: user
More levels: foo

URI segment to associative array in code igniter

I have following url:
www.example.com/index.php/search/search_data/Doctor:a/Gender:Male/Language:Urdu/
and I want to convert it to associative array like
$data=array(
'Doctor'=> 'a',
'Gender'=> 'Male',
'Language'=> 'Urdu'
);
I have tried to do this using codeIgniter's URI class function
$this->uri->uri_to_assoc(n)
but as it accepts the data to be separated via '/' but I am having data with ':' as separator.
please help me.
I don't think there's an easier way to do this, rather than to do it manually.
First, retrieve the total segments, loop through, see if it contains ":", then add it into the array.
$segments = $this->uri->segment_array();
$search_array = array();
foreach($segments as $segment) {
if (strpos($segment, ":") !== FALSE) {
$e_array = explode(":", $segment);
$search_array[$e_array[0]] = $e_array[1];
}
}
Running that snippet somewhere will give you desirable results, $search_array will be an associative array with key => value.
You could hack the URI.php file. Change lines 431 - 439 to;
if (strpos($seg, ":") !== FALSE) {
list($parameter, $value) = explode(':', $seg);
if ($i % 2) {
$retval[$parameter] = $value;
} else {
$retval[$parameter] = $value;
$lastval = $seg;
}
} else {
if ($i % 2) {
$retval[$lastval] = $seg;
} else {
$retval[$seg] = FALSE;
$lastval = $seg;
}
}

PHP - Increment array based on index position

I have a script which handles the naming of parent/child elements on another page. The format for the name is like E5-2-3 which represents the third child of the second child of the fifth element.
What I need to do is pass in the parent name to the function and return the name for the next child. This value would be the increment of the last child or 1 if it is the first child.
(I hope this makes some sense to someone)
The index array looks something like this:
1=>null
2=>null
3=>
1=>null
2=>null
3=>
1=>null
4=>null
5=>
1=>null
2=>
1=>null
2=>null
3=>null //the element I was talking about above
6=>
1=>null
7=>null
My Code so far is
$projectNumber = $_GET['project_number'];
#$parentNumber = $_GET['parent_number']; //suppressed as it may not be set
$query = mysql_query("SELECT e_numbers FROM project_management WHERE project_number = '$projectNumber'");
$resultArray = mysql_fetch_assoc($query);
$eNumbers = unserialize($resultArray['e_numbers']);
if (!is_array($eNumbers)&&!isset($parentNumber)){ //first e_number assigned
$eNumbers[1] = null; //cant possibly have children so null for now
$nextENumber = 'E1';
}else{
if (!isset($parentNumber)){
$nextNumber = count($eNumbers)+1;
$eNumbers[$nextNumber] = null; //cant possibly have children so null for now
$nextENumber = 'E'.$nextNumber;
}else{
$parentIndex = explode('-', str_replace('E', '', $parentNumber));
//$nextENumber = //assign $nextENumber the incremented e number
}
}
echo $nextENumber;
//(then goes on to update sql etc etc)
This is all fine but for the line where I need to get/assign deep numbers. I think this should be some kind of recursive function based on the $parentIndex and $eNumbers arrays, however I'm a bit out of my depth when it comes to recursion.
Any pointer in the right direction will be a great help.
PS
If there is a better way to handle incrementing parent/child relationships I'm all ears. The only thing out of my control is the format of the numbers being passed in/out (Has to be EX-Y-Z-...)
UPDATE I was able to develop #ircmaxell 's function to function more better in my context. The function required you to pass in a zero based array(can be empty) and an optional path. It returns the new path and updates the index array to include the new path. An error message is returned if the index is not found.
function getNextPath(&$array, $path) { //thanks to ircmaxell # stackoverflow for the basis of this function
$newPath = '';
$tmp =& $array;
if (is_string($path)) {
$path = explode('-', str_replace('E', '', $path));
$max = count($path);
foreach ($path as $key => $subpath) {
if (is_array($tmp)) {
if (array_key_exists($subpath, $tmp)){
$tmp =& $tmp[$subpath];
$newPath[] = $subpath;
}else{
return "Parent Path Not Found";
}
}
}
}
$tmp[] = null;
$newPath[] = count($tmp)-1;
if (count($newPath)>1){
$newPath = implode('-', $newPath);
}else{
$newPath = $newPath[0];
}
return "E".$newPath;
}
Here's one way:
function incrementPath(&$array, $path) {
if (is_string($path)) {
$path = explode('-', str_replace('E', '', $path);
}
$tmp =& $array;
foreach ($path as $subpath) {
if (is_array($tmp) && isset($tmp[$subpath])) {
$tmp =& $tmp[$subpath];
} else {
return false; // Could not find entire path
}
}
$tmp++;
return true;
}
Now, if you want it to dynamically create paths, just change the return false; to:
$tmp[$subpath] = array();
$tmp =& $tmp[$subpath];
And then add a check after the loop to see if it's not an integer, and explicitly set to 0 if it isn't...
Edit: AHHH, now I understand:
function getNextPath(&$array, $path) {
if (is_string($path)) {
$path = explode('-', str_replace('E', '', $path);
}
$newPath = '';
$tmp =& $array;
$max = count($path) - 1;
foreach ($path as $key => $subpath) {
if (is_array($tmp) && isset($tmp[$subpath])) {
$tmp =& $tmp[$subpath];
if ($key < $max) {
$newPath .= '-'.$subpath;
}
} else {
return 'E' . ltrim($newPath . '-1', '-'); // Could not find entire path
}
}
if (is_array($tmp)) {
return 'E' . ltrim($newPath . '-' . count($tmp), '-');
} else {
//it's a value, so make it an array
$tmp = array();
return 'E' . ltrim($newPath . '-' . 1, '-');
}
}
I think that should do what you want (it returns the next available path under what you're looking for).
}

Categories