So I wrote a class that can parse XML documents and create SQL queries from it to update or insert new rows depending on the settings.
Because the script has to work with any amount of nested blocks, the array that I'm putting all the values in has its path dynamically created much like the following example:
$path = array('field1','field2');
$path = "['".implode("']['",$path)."']";
eval("\$array".$path."['value'] = 'test';");
Basically $path contains an array that shows how deep in the array we currently are, if $path contains for instance the values main_table and field I want set $array['main_table']['field']['value'] to 'test'
As you can see I am currently using eval to do this, and this works fine. I am just wondering if there is a way to do this without using eval.
Something like
$array{$path}['value'] = 'test'; but then something that actually works.
Any suggestions?
EDIT
The reason I'm looking for an alternative is because I think eval is bad practice.
SECOND EDIT
Changed actual code to dummy code because it was causing a lot of misunderstandings.
Use something like this:
/**
* Sets an element of a multidimensional array from an array containing
* the keys for each dimension.
*
* #param array &$array The array to manipulate
* #param array $path An array containing keys for each dimension
* #param mixed $value The value that is assigned to the element
*/
function set_recursive(&$array, $path, $value)
{
$key = array_shift($path);
if (empty($path)) {
$array[$key] = $value;
} else {
if (!isset($array[$key]) || !is_array($array[$key])) {
$array[$key] = array();
}
set_recursive($array[$key], $path, $value);
}
}
You can bypass the whole counter business with the array append operator:
$some_array[] = 1; // pushes '1' onto the end of the array
As for the whole path business, I'm assuming that's basically an oddball representation of an xpath-like route through your xml document... any reason you can't simply use that string as an array key itself?
$this->BLOCKS['/path/to/the/node/you're/working/on][] = array('name' => $name, 'target' => $target);
You can use a foreach with variable variables.
// assuming a base called $array, and the path as in your example:
$path = array('field1','field2');
$$path = $array;
foreach ($path as $v) $$path = $$path[$v];
$$path['value'] = 'test';
Short, simple, and much better than eval.
Related
Currently I am attempting to call a multidimensional array, using a string as a key or keys. I would like to use the following code, but I think the key is being interpreted as a string. Any solution?
$data= [];
$data['volvo'] = "nice whip";
$test = "['volvo']";
$data['drivers']['mike'] = "decent";
$test2 = "['drivers']['mike']";
echo $data$test; // should read 'nice whip'
echo $data$test2; // should read 'decent'
You just use the variable (which should just be the string and not PHP syntax) in place of the string literal.
$cars = [];
$cars['volvo'] = 'nice whip';
$test = 'volvo';
echo $cars[$test];
If you need a dynamic array access solution, you could also write a function, which does the actual array access like this:
function path($array, $path) {
$path = is_array($path) ? $path : explode('.', $path);
$current = $array;
while (count($path)) {
$seg = array_shift($path);
if (!isset($current[$seg])) throw new Exception('Invalid path segment: ' . $seg);
$current = $current[$seg];
}
return $current;
}
In your case, this would look like this
echo path($data, 'volvo');
echo path($data, 'drivers.mike');
or
echo path($data, ['volvo']);
echo path($data, ['drivers', 'mike']);
The problem is you can't pass multiple levels in one string like that. (If so, PHP would have to start looking for code fragments inside string array keys. And how would it know whether to interpret them as fragments and then split the string key up, or keep treating it as one string??)
Alt 1
One solution is to change the structure of $data, and make it a single level array. Then you supply keys for all the levels needed to find your data, joined together as a string. You would of course need to find a separator that works in your case. If the keys are plain strings then something simple like underscore should work just fine. Also, this wouldn't change the structure of your database, just the way data is stored.
function getDbItem($keys) {
// Use this to get the "old version" of the key back. (I.e it's the same data)
$joinedKey = "['".implode("'],['", $keys)."']";
$joinedKey = implode('_', $keys);
return $data[$joinedKey];
}
// Example
$data = [
'volvo' => 'nice whip',
'drivers_mike' => 'decent'
];
var_dump(getDbItem(['drivers', 'mike'])); // string(6) "decent"
Alt 2
Another way is to not change number of levels in $data, but simply traverse it using the keys passed in:
$tgt = $data;
foreach($keys as $key) {
if (array_key_exists($key, $tgt)) {
$tgt = $tgt[$key];
}
else {
// Non existing key. Handle properly.
}
}
// Example
$keys = ['drivers', 'mike'];
// run the above code
var_dump($tgt); // string(6) "decent"
To my understanding Input::post(); with no parameters returns an array containing all the data from a specific POST..
I am doing this $all_input = Input::post();
But then I am iterating through the array Java-like (is that how you even do it?)
for ($i=0; $i<count($all_input); $i++)
{
if (strpos($all_input[$i], 'something') == true) // ERROR...
but the application crashes with error Undefined offset: 0 which I believe means that the index was not found?
I have also tried adding this to no avail:
if (!isset($all_input))
{
return;
}
If so how can you access the data to iterate through them? I know it contains data cause I can see them when I press the button during the debugging on the browser if I remove that code.
If you didn't already figure it out I am coming from Java developer and I have just started learning php so bear with me please.
According to this: https://fuelphp.com/docs/classes/input.html#/method_post Input::post(); will return $_POST which is an assoc array.
Here is the source code, because the fuelphp's documentation doesn't cover it exactly.
/**
* Fetch an item from the POST array
*
* #param string $index The index key
* #param mixed $default The default value
* #return string|array
*/
public static function post($index = null, $default = null)
{
return (func_num_args() === 0) ? $_POST : \Arr::get($_POST, $index, $default);
}
You need to refer with your input names, so if you have an input which you call 'name', in that case you need to refer $all_input['name']. You can get your keys by using array_keys() function. Also it's better if you use foreach in this situation. Like:
foreach($all_input as $key => $value) {
echo 'Input key: ' . $key . ' value: ' . $value;
}
If you left the $key => you will get only the value, you can left it, if you doesn't use it inside the foreach.
If you don't want to use foreach somewhy:
$all_input = Input::post();
$keys = array_keys($all_input);
for ($i = 0; $i < count($keys); $i++) {
if (strpos($all_input[$keys[$i]], 'something') == true) {
// Do your stuff here.
}
}
But I still recommend foreach if possible, it's less overhead and cleaner code.
This won't work because you are dealing with an Object (Input) not an array.
I recommend using a foreach loop vice a for loop. To verify the contents/structure of the Input Object you can also do a dd() to see the Input Object in its entirety.
Basically,
$input = Input::post();
foreach($input as $i) {
echo $i; //this is a property of the Input Object. if you have arrays or other objects store inside of it, you may need to go deeper into the oject either with a foreach loop again or by calling the property directly ($i->property)
};
I have an array that I want to alter. I thought that array_walk would be the way to go but I'm having trouble figuring out how to collect that data from array_walk, deleting the data from the old array, and inserting the new data. How should I go about doing this? Here is the code.
$width_array = array(
"2.5%",
"2.6%",
"2.7%",
"2.8%",
);
function adjust_the_width($value)
{
$value = $value * 2;
}
array_walk($width_array, "adjust_the_width");
$random_width = array_rand($width_array, 10);
You where likely looking for array_map, example below:
<?
$width_array = array(
"2.5%",
"2.6%",
"2.7%",
"2.8%",
);
function adjust_the_width($value) {
return $value * 2;
}
$width_array = array_map("adjust_the_width", $width_array);
$random_width = array_rand($width_array, count($width_array));
var_dump($width_array);
Note: the percentages are dropped from the calculations because PHP interprets the string "2.5%" as a float value when it is multiplied by 2.
Also, array_map supplies each element as the parameter to the function provided and uses it's return value to fill the same place in the new array that array_map builds.
This is also why I assign $width_array = array_map(..., array_map builds a new array, it does not replace the old one by default.
You can also do this if you'd rather not build the intermediate array:
foreach($width_array as &$width) {
$width = $width * 2;
}
var_dump($width_array);
This walks the array and modifies each element as a reference to it's location (that's what &$width means).
Without the '&' this foreach loop will do nothing but chew cpu cycles.
I'm creating JSON encoded data from PHP arrays that can be two or three levels deep, that look something like this:
[grandParent] => Array (
[parent] => Array (
[child] => myValue
)
)
The method I have, which is simply to create the nested array manually in the code requires me to use my 'setOption' function (which handles the encoding later) by typing out some horrible nested arrays, however:
$option = setOption("grandParent",array("parent"=>array("child"=>"myValue")));
I wanted to be able to get the same result by using similar notation to javascript in this instance, because I'm going to be setting many options in many pages and the above just isn't very readable, especially when the nested arrays contain multiple keys - whereas being able to do this would make much more sense:
$option = setOption("grandParent.parent.child","myValue");
Can anyone suggest a way to be able to create the multidimensional array by splitting the string on the '.' so that I can json_encode() it into a nested object?
(the setOption function purpose is to collect all of the options together into one large, nested PHP array before encoding them all in one go later, so that's where the solution would go)
EDIT: I realise I could do this in the code:
$options['grandparent']['parent']['child'] = "myValue1";
$options['grandparent']['parent']['child2'] = "myValue2";
$options['grandparent']['parent']['child3'] = "myValue3";
Which may be simpler; but a suggestion would still rock (as i'm using it as part of a wider object, so its $obj->setOption(key,value);
This ought to populate the sub-arrays for you if they haven't already been created and set keys accordingly (codepad here):
function set_opt(&$array_ptr, $key, $value) {
$keys = explode('.', $key);
// extract the last key
$last_key = array_pop($keys);
// walk/build the array to the specified key
while ($arr_key = array_shift($keys)) {
if (!array_key_exists($arr_key, $array_ptr)) {
$array_ptr[$arr_key] = array();
}
$array_ptr = &$array_ptr[$arr_key];
}
// set the final key
$array_ptr[$last_key] = $value;
}
Call it like so:
$opt_array = array();
$key = 'grandParent.parent.child';
set_opt($opt_array, $key, 'foobar');
print_r($opt_array);
In keeping with your edits, you'll probably want to adapt this to use an array within your class...but hopefully this provides a place to start!
What about $option = setOption("grandParent", { parent:{ child:"myValue" } });?
Doing $options['grandparent']['parent']['child'] will produce an error if $options['grandparent']['parent'] was not set before.
The OO version of the accepted answer (http://codepad.org/t7KdNMwV)
$object = new myClass();
$object->setOption("mySetting.mySettingsChild.mySettingsGrandChild","foobar");
echo "<pre>".print_r($object->options,true)."</pre>";
class myClass {
function __construct() {
$this->setOption("grandparent.parent.child","someDefault");
}
function _setOption(&$array_ptr, $key, $value) {
$keys = explode('.', $key);
$last_key = array_pop($keys);
while ($arr_key = array_shift($keys)) {
if (!array_key_exists($arr_key, $array_ptr)) {
$array_ptr[$arr_key] = array();
}
$array_ptr = &$array_ptr[$arr_key];
}
$array_ptr[$last_key] = $value;
}
function setOption($key,$value) {
if (!isset($this->options)) {
$this->options = array();
}
$this->_setOption($this->options, $key, $value);
return true;
}
}
#rjz solution helped me out, tho i needed to create a array from set of keys stored in array but when it came to numerical indexes, it didnt work. For those who need to create a nested array from set of array indexes stores in array as here:
$keys = array(
'variable_data',
'0',
'var_type'
);
You'll find the solution here: Php array from set of keys
I want to compare 2 big xml files and retrieve the differences. Like ExamXML and DiffDog do. The solution I found was cycling through all child nodes of each file simultaneously and check if they are equal. But I have no idea how to achieve that... How can I loop through all child nodes and their properties? How can I check if the first element of the first file is equal to the first element of the second file, the second element of the first file is equal to the second element of the second file and so on?
Do yo have a better idea to compare 2 xml files?
I was looking for something to compare two XML like you, and I found this solution that works very well.
http://www.jevon.org/wiki/Comparing_Two_SimpleXML_Documents
I hope that helps to someone.
Have you looked at using XPath at all? Seems like an easy way to grab all of the child nodes. Then you'd be able to loop through the nodes and compare the attributes/textContent.
This might be a very alternative solution for you but this is how I would do it.
First, I'd try to get the format into something much more manageable like an array so I would convert the XML to an array.
http://www.bytemycode.com/snippets/snippet/445/
This is some simple code to do just that.
Then PHP has an array_diff() function that can show you the differences.
http://www.php.net/manual/en/function.array-diff.php
This may or may not work for you considering what you need to do with the differences but if you're looking to just identify and act upon them this might be a very quick solution to your problem.
Try the xmldiff extension
http://pecl.php.net/xmldiff
It's based on the same library as the perl module DifferenceMarkup, you'll get a diff XML document and can even merge then.
//Child by Child XML files comparison in PHP
//Returns an array of non matched children in variable &$reasons
$reasons = array();
$xml1 = new SimpleXMLElement(file_get_contents($xmlFile1));
$xml2 = new SimpleXMLElement(file_get_contents($xmlFile2));
$result = XMLFileComparison($xml1, $xml2, $reasons);
/**
* XMLFileComparison
* Discription :- This function compares XML files. Returns array
* of nodes do not match in pass by reference parameter
* #param $xml1 Object Node Object
* #param $xml2 Object Node Object
* #param &$reasons Array pass by reference
* returns array of nodes do not match
* #param $strict_comparison Bool default False
* #return bool <b>TRUE</b> on success or array of strings on failure.
*/
function XMLFileComparison(SimpleXMLElement $xml1, SimpleXMLElement $xml2, &$reasons, $strict_comparison = false)
{
static $str;
// compare text content
if ($strict_comparison) {
if ("$xml1" != "$xml2") return "Values are not equal (strict)";
} else {
if (trim("$xml1") != trim("$xml2"))
{
return " Values are not equal";
}
}
// get all children
$XML1ChildArray = array();
$XML2ChildArray = array();
foreach ($xml1->children() as $b) {
if (!isset($XML1ChildArray[$b->getName()]))
$XML1ChildArray[$b->getName()] = array();
$XML1ChildArray[$b->getName()][] = $b;
}
foreach ($xml2->children() as $b) {
if (!isset($XML2ChildArray[$b->getName()]))
$XML2ChildArray[$b->getName()] = array();
$XML2ChildArray[$b->getName()][] = $b;
}
//print_r($XML1ChildArray);
//print_r($XML2ChildArray);
// cycle over children
if (count($XML1ChildArray) != count($XML2ChildArray)) return "mismatched children count";// Second File has less or more children names (we don't have to search through Second File's children too)
foreach ($XML1ChildArray as $child_name => $children) {
if (!isset($XML2ChildArray[$child_name])) return "Second file does not have child $child_name"; // Second file has none of this child
if (count($XML1ChildArray[$child_name]) != count($XML2ChildArray[$child_name])) return "mismatched $child_name children count"; // Second file has less or more children
print_r($child_name);
foreach ($children as $child) {
// do any of search2 children match?
$found_match = false;
//$reasons = array();
foreach ($XML2ChildArray[$child_name] as $id => $second_child) {
$str = $str.$child_name.($id+1)."/"; // Adding 1 to $id to match with XML data nodes numbers
//print_r($child, $second_child);
// recursive function call until reach to the end of node
if (($r = XMLFileComparison($child, $second_child, $reasons, $strict_comparison)) === true) {
// found a match: delete second
$found_match = true;
unset($XML2ChildArray[$child_name][$id]);
$str = str_replace($child_name.($id+1)."/", "", $str);
break;
}
else {
unset($XML2ChildArray[$child_name][$id]);
$reasons[$str] = $r;
$str = str_replace($child_name.($id+1)."/", "", $str);
break;
}
}
}
}
return True;
}