Convert string to multidimensional array without eval - php

I saved template-variables in the DB, e.g.: slider.item1.headline1 = "Headline1".
I am using symfony framework. If I just pass "slider.item1.headline1" to the Twig template, that won't be used because the points are interpreted as a multidimensional array (or nested object) (https://symfony.com/doc/current/templates.html#template-variables).
I've now built a routine that converts the string into a multidimensional array.
But I don't like the eval(), PHP-Storm doesn't like it either, marks it as an error.
How could this be solved in a nicer way (without eval)?
Here my method:
protected function convertTemplateVarsFromDatabase($tplvars): array
{
$myvar = [];
foreach ($tplvars as $tv)
{
$handle = preg_replace('/[^a-zA-Z0-9._]/', '_', $tv['handle']);
$tplsplit = explode('.', $handle);
$mem = "";
foreach ($tplsplit as $eitem)
{
$mem .= "['" . $eitem . "']";
}
$content = $tv['htmltext'];
eval('$myvar' . $mem . ' = $content;');
}
return $myvar;
}

You can indeed avoid eval here. Maintain a variable that follows the existing array structure of $myvar according to the path given, and let it create any missing key while doing so. This is made easier using the & syntax, so to have a reference to a particular place in the nested array:
function convertTemplateVarsFromDatabase($tplvars): array
{
$myvar = [];
foreach ($tplvars as $tv)
{
$handle = preg_replace('/[^a-zA-Z0-9._]/', '_', $tv['handle']);
$tplsplit = explode('.', $handle);
$current = &$myvar;
foreach ($tplsplit as $eitem)
{
if (!isset($current[$eitem])) $current[$eitem] = [];
$current = &$current[$eitem];
}
$current = $tv['htmltext'];
}
return $myvar;
}

Related

How can I get in PHP the value out of an object attribute with the path stored in a variable?

For example, I have the following:
$ValuePath = "object->data->user_nicename";
And I need to print not the value of the $ValuePath but the value of the $variable->data->user_nicename that is part of a larger call .. as I have the following situation:
echo $objects->$ValuePath->details;
and is not working .. I'm getting the error Class cannot be converted to string or something when I try it like that.
PHP pseudo-code:
function get_by_path($object, $path)
{
$o = $object;
$parts = explode('->', $path);
// if you want to remove the first dummy object-> reference
array_shift($parts);
$l = count($parts);
while ($l)
{
$p = array_shift($parts); $l--;
if ( isset($o->{$p}) ) $o = $o->{$p};
else {$o = null; break;}
}
return $o;
}
Use like this:
$value = get_by_path($obj, "object->data->user_nicename");
// $value = $obj->data->user_nicename;
Check also PHP ArrayAccess Interface which enables to access objects as arrays dynamicaly
NO you cannot do that.
From your declaration $ValuePath is a variable which holds a plain string ('object->data->user_nicename') this string is not an object.
Where as,
$objects->object->data->user_nicename->details is a object.
so $objects->object->data->user_nicename->details is not same as $objects->$ValuePath->details

How to use one array in two functions in PHP?

I have an array with the following content:
$array_test = array ("User1"=>"Test1",
"User2"=>"Test2");
First column shall be $uservalue and second $testvalue in the functions. It worked, when I use a single column array for the first function loop through the array in the function.
And I would like to use this array in the following two functions.
The first column of the array should be use as $uservalue.
function do_show(array $options) {
global $showresult, $master;
$cn = $uservalue;
$config = $options["config"]->value;
// an empty show tag
$show = new SimpleXMLElement("<show/>");
// add the user tag
$user = $show->addChild("user");
// add the "cn" attribute
$user->addAttribute("cn", $cn);
if ($config)
$user->addAttribute("config", "true");
print "cmd: " . htmlspecialchars($show->asXML()) . "\n";
// do it
$showresult = $masterPBX->Admin($show->asXML());
print "result: " . htmlspecialchars($showresult) . "\n";
}
Second function where I would like to use the second Column of the array as Value for $testvalue:
function do_modify(array $options) {
global $showresult, $master;
$mod = $testvalue;
$modify = new SimpleXMLElement("$showresult");
$user = $modify->user;
$path = explode("/device/hw/", $mod);
$srch = $user;
$nsegments = count($path);
$i = 1;
foreach ($path as $p) {
if ($i == $nsegments) {
// last part, the modification
list($attr, $value) = explode("=", $p);
$srch[$attr] = $value;
} else {
$srch = $srch->$p;
}
$i++;
}
// wrap the modified user tag in to a <modify> tag
$modify = new SimpleXMLElement("<modify>" . $user->asXML() . "</modify>");
print "cmd: " . htmlspecialchars($cmd = $modify->asXML()) . "\n";
$result = $master->Admin($cmd);
print "result: " . htmlspecialchars($result);
}
How can I archieve this? I found these two functions in the wiki of the software I would like to implement this... Therefore I know that using global variables is not a good option.
You can split your array into two arrays using following code and pass appropriate array to relevant function.
$array_test = array ("User1"=>"Test1",
"User2"=>"Test2");
$array_users = array_keys($array_test);
$array_tests = array_values($array_test);
Here is the link for details on both array_values and array_keys functions.
Pass the array as a function parameter to both functions...or make the array global. Pass by reference if you want to change original array (your array changes made inside the function to be visible outside of the function).

Creating a function that takes arguments and passes variables

I am creating a script that will locate a field in a text file and get the value that I need.
First used the file() function to load my txt into an array by line.
Then I use explode() to create an array for the strings on a selected line.
I assign labels to the array's to describe a $Key and a $Value.
$line = file($myFile);
$arg = 3
$c = explode(" ", $line[$arg]);
$key = strtolower($c[0]);
if (strpos($c[2], '~') !== false) {
$val = str_replace('~', '.', $c[2]);
}else{
$val = $c[2];
}
This works fine but that is a lot of code to have to do over and over again for everything I want to get out of the txt file. So I wanted to create a function that I could call with an argument that would return the value of $key and $val. And this is where I am failing:
<?php
/**
* #author Jason Moore
* #copyright 2014
*/
global $line;
$key = '';
$val = '';
$myFile = "player.txt";
$line = file($myFile); //file in to an array
$arg = 3;
$Character_Name = 3
function get_plr_data2($arg){
global $key;
global $val;
$c = explode(" ", $line[$arg]);
$key = strtolower($c[0]);
if (strpos($c[2], '~') !== false) {
$val = str_replace('~', '.', $c[2]);
}else{
$val = $c[2];
}
return;
}
get_plr_data2($Character_Name);
echo "This character's ",$key,' is ',$val;
?>
I thought that I covered the scope with setting the values in the main and then setting them a global within the function. I feel like I am close but I am just missing something.
I feel like there should be something like return $key,$val; but that doesn't work. I could return an Array but then I would end up typing just as much code to the the info out of the array.
I am missing something with the function and the function argument to. I would like to pass and argument example : get_plr_data2($Character_Name); the argument identifies the line that we are getting the data from.
Any help with this would be more than appreciated.
::Updated::
Thanks to the answers I got past passing the Array.
But my problem is depending on the arguments I put in get_plr_data2($arg) the number of values differ.
I figured that I could just set the Max of num values I could get but this doesn't work at all of course because I end up with undefined offsets instead.
$a = $cdata[0];$b = $cdata[1];$c = $cdata[2];
$d = $cdata[3];$e = $cdata[4];$f = $cdata[5];
$g = $cdata[6];$h = $cdata[7];$i = $cdata[8];
$j = $cdata[9];$k = $cdata[10];$l = $cdata[11];
return array($a,$b,$c,$d,$e,$f,$g,$h,$i,$j,$k,$l);
Now I am thinking that I can use the count function myCount = count($c); to either amend or add more values creating the offsets I need. Or a better option is if there was a way I could generate the return array(), so that it would could the number of values given for array and return all the values needed. I think that maybe I am just making this a whole lot more difficult than it is.
Thanks again for all the help and suggestions
function get_plr_data2($arg){
$myFile = "player.txt";
$line = file($myFile); //file in to an array
$c = explode(" ", $line[$arg]);
$key = strtolower($c[0]);
if (strpos($c[2], '~') !== false) {
$val = str_replace('~', '.', $c[2]);
}else{
$val = $c[2];
}
return array($key,$val);
}
Using:
list($key,$val) = get_plr_data2(SOME_ARG);
you can do this in 2 way
you can return both values in an array
function get_plr_data2($arg){
/* do what you have to do */
$output=array();
$output['key'] =$key;
$output['value']= $value;
return $output;
}
and use the array in your main function
you can use reference so that you can return multiple values
function get_plr_data2($arg,&$key,&$val){
/* do job */
}
//use the function as
$key='';
$val='';
get_plr_data2($arg,$key,$val);
what ever you do to $key in function it will affect the main functions $key
I was over thinking it. Thanks for all they help guys. this is what I finally came up with thanks to your guidance:
<?php
$ch_file = "Thor";
$ch_name = 3;
$ch_lvl = 4;
$ch_clss = 15;
list($a,$b)= get_char($ch_file,$ch_name);//
Echo $a,': ',$b; // Out Puts values from the $cdata array.
function get_char($file,$data){
$myFile = $file.".txt";
$line = file($myFile);
$cdata = preg_split('/\s+/', trim($line[$data]));
return $cdata;
}
Brand new to this community, thanks for all the patience.

PHP: Need an alternative to eval() for dynamically building multidimensional array

Okay, so I know that using eval() isn't great, but I haven't been able to come up with a better solution to my problem, and until recently, there wasn't a performance reason not to use it. However, I am now passing enough data to the function that it is taking unacceptably long.
The function that is being called is:
public static function makeAMultiDimensionalArrayWithSumsBasedOnMultipleFields($inArray, $dimensionFieldNames, $sumFieldNameArray, $staticFieldNameArray = array())
{
$outArray = array();
// Just in case the array has indices, sort it so array_pop works as expected.
ksort($dimensionFieldNames);
foreach ($inArray as $row)
{
// make sure each row in the inArray has all keys specified by $dimensionFieldNames
$allFieldsPresent = TRUE;
foreach ($dimensionFieldNames as $keyFieldName)
{
if (!array_key_exists($keyFieldName, $row))
{
// Note that alternatively we could set the field to a specified default value.
$allFieldsPresent = FALSE;
}
}
if ($allFieldsPresent)
{
$indexString = '';
$keyFieldNameArrayCopy = $dimensionFieldNames;
foreach ($dimensionFieldNames as $keyFieldName)
{
$indexString .= "['" . $row[$keyFieldName] . "']";
// lets sum values
foreach ($sumFieldNameArray as $sumFieldName)
{
eval ('$outArray' . $indexString . '[' . $sumFieldName . '] += $row[' . $sumFieldName . '];');
}
foreach ($staticFieldNameArray as $staticFieldName)
{
eval ('$outArray' . $indexString . '[' . $staticFieldName . '] = $row[' . $staticFieldName . '];');
}
}
}
}
return $outArray;
}
It is being called like this:
makeAMultiDimensionalArrayWithSumsBasedOnMultipleFields($data, $dimensionArray, $sumArray, $staticArray);
And the variables being passed to the function are similar to:
$dimensionArray = array("firstLevelID", "secondLevelID", "thirdLevelID", "fourthLevelID", "fifthLevelID");
$sumArray = array("revenue", "cost", "profit", "sales", "inquires", "cost", "walkins");
$staticArray = array("date", "storeID", "storeName", "productID", "productName", "managerID", "managerName", "salesperson");
So I want to rewrite the function so that I'm not using eval() any more. I've spent a considerable amount of time on this, and feel that it's time to seek some advice.
The goal is to take an array of arrays, and turn it into a multidimensional array based on the dimensions in the $dimensionArray.
I don't want to bore you with too many details right now, so please ask if you need more or have any other questions
Wow, okay. First time through I missed your indexing concatenation. Try this:
if ($allFieldsPresent) {
$keys = array();
foreach ($dimensionFieldNames as $keyFieldName) {
$keys[] = $row[$keyFieldName];
// lets sum values
foreach ($sumFieldNameArray as $sumFieldName)
self::deepAssign($outArray, $keys, $sumFieldName, $row[$sumFieldName], true);
foreach ($staticFieldNameArray as $staticFieldName)
self::deepAssign($outArray, $keys, $staticFieldName, $row[$staticFieldName]);
}
}
protected static function deepAssign(&$array, $keys, $fieldName, $value, $sum = false) {
$target =& $array;
foreach ($keys as $key) {
if (!isset($target[$key]))
$target[$key] = array();
$target =& $target[$key];
}
if($sum)
$target[$fieldName] += $value;
else
$target[$fieldName] = $value;
}
You should look at create_function() (docs here)

How to recursively create a multidimensional array?

I am trying to create a multi-dimensional array whose parts are determined by a string. I'm using . as the delimiter, and each part (except for the last) should be an array
ex:
config.debug.router.strictMode = true
I want the same results as if I were to type:
$arr = array('config' => array('debug' => array('router' => array('strictMode' => true))));
This problem's really got me going in circles, any help is appreciated. Thanks!
Let’s assume we already have the key and value in $key and $val, then you could do this:
$key = 'config.debug.router.strictMode';
$val = true;
$path = explode('.', $key);
Builing the array from left to right:
$arr = array();
$tmp = &$arr;
foreach ($path as $segment) {
$tmp[$segment] = array();
$tmp = &$tmp[$segment];
}
$tmp = $val;
And from right to left:
$arr = array();
$tmp = $val;
while ($segment = array_pop($path)) {
$tmp = array($segment => $tmp);
}
$arr = $tmp;
I say split everything up, start with the value, and work backwards from there, each time through, wrapping what you have inside another array. Like so:
$s = 'config.debug.router.strictMode = true';
list($parts, $value) = explode(' = ', $s);
$parts = explode('.', $parts);
while($parts) {
$value = array(array_pop($parts) => $value);
}
print_r($parts);
Definitely rewrite it so it has error checking.
Gumbo's answer looks good.
However, it looks like you want to parse a typical .ini file.
Consider using library code instead of rolling your own.
For instance, Zend_Config handles this kind of thing nicely.
I really like JasonWolf answer to this.
As to the possible errors: yes, but he supplied a great idea, now it is up to the reader to make it bullet proof.
My need was a bit more basic: from a delimited list, create a MD array. I slightly modified his code to give me just that. This version will give you an array with or without a define string or even a string without the delimiter.
I hope someone can make this even better.
$parts = "config.debug.router.strictMode";
$parts = explode(".", $parts);
$value = null;
while($parts) {
$value = array(array_pop($parts) => $value);
}
print_r($value);
// The attribute to the right of the equals sign
$rightOfEquals = true;
$leftOfEquals = "config.debug.router.strictMode";
// Array of identifiers
$identifiers = explode(".", $leftOfEquals);
// How many 'identifiers' we have
$numIdentifiers = count($identifiers);
// Iterate through each identifier backwards
// We do this backwards because we want the "innermost" array element
// to be defined first.
for ($i = ($numIdentifiers - 1); $i >=0; $i--)
{
// If we are looking at the "last" identifier, then we know what its
// value is. It is the thing directly to the right of the equals sign.
if ($i == ($numIdentifiers - 1))
{
$a = array($identifiers[$i] => $rightOfEquals);
}
// Otherwise, we recursively append our new attribute to the beginning of the array.
else
{
$a = array($identifiers[$i] => $a);
}
}
print_r($a);

Categories