Call time pass by reference was removed in PHP 5.4. But I have a special case where it seems to be still available in PHP 8 (tried it here: www.w3schools.com):
$myVar = "original";
testFunc([$myVar]);
echo "Variable value: $myVar<br>"; // Output is: "Variable value: original"
testFunc([&$myVar]);
echo "Variable value: $myVar<br>"; // Output is: "Variable value: changed"
testFunc([&$undefinedVar]);
echo "Variable value: $undefinedVar<br>"; // Output is: "Variable value: changed"
testFunc([$undefinedVar_2]);
echo "Variable value: $undefinedVar_2<br>"; // Output is: "Variable value: "
function testFunc( array $arr ) : void
{
if ( !is_array($arr)
|| count($arr) == 0 )
return;
$arr[0] = 'changed';
}
Additionally, this way I can get a C# like parameter out functionality.
Maybe I am misunderstanding something.
Question:
How could I identify within "testFunc" if $arr[0] was passed by reference or normally?
Alternative question (for people who are searching this topic):
Check if variable was passed by reference.
The code by #Foobar pointed me to the right direction. I am using the output of var_dump to analyze it and create a data structure out of it:
class ReferenceInfo
{
public string $Type;
public bool $IsReference;
/** int, float, double, bool are always initialized with default value */
public bool $IsInitialized;
/** #var ?ReferenceInfo[] */
public ?array $SubItems;
public static function FromVariable( mixed $arr ) : ?ReferenceInfo
{
/** #var ReferenceInfo $rootRefInfo */
$rootRefInfo = NULL;
$varInfoStr = self::varDumpToString($arr);
$varInfoStrArray = preg_split("/\r\n|\n|\r/", $varInfoStr);
$refInfoObjectStack = [];
$curKey = NULL;
foreach ( $varInfoStrArray as $line ) {
$lineTrimmed = trim($line);
$lineTrimmedLen = strlen($lineTrimmed);
if ( $lineTrimmedLen == 0 )
continue;
if ( $lineTrimmed == '}' ) {
array_pop($refInfoObjectStack);
$curKey = NULL;
continue;
}
if ( $lineTrimmed[0] == '[' ) {
// Found array key
$bracketEndPos = strpos($lineTrimmed, ']');
if ( $bracketEndPos === false )
return NULL;
$keyName = self::convertToRealType(substr($lineTrimmed, 1, $bracketEndPos - 1));
$curKey = $keyName;
continue;
}
$parenPos = strpos($lineTrimmed, '(');
if ( $parenPos === false ) {
// Must be a NULL type
$parenPos = $lineTrimmedLen;
}
$type = substr($lineTrimmed, 0, $parenPos);
$isInitialized = true;
if ( $type == 'uninitialized' ) {
$parenEndPos = strpos($lineTrimmed, ')', $parenPos);
if ( $parenEndPos === false )
return NULL;
$type = substr($lineTrimmed, $parenPos + 1, $parenEndPos - $parenPos - 1);
$isInitialized = false;
}
$refInfoObj = new ReferenceInfo();
$refInfoObj->IsReference = str_starts_with($type, '&');
$refInfoObj->IsInitialized = $isInitialized;
$refInfoObj->Type = substr($type, $refInfoObj->IsReference ? 1 : 0);
if ( $rootRefInfo == NULL ) {
$rootRefInfo = $refInfoObj;
} else {
$refInfoObjectStack[count($refInfoObjectStack) - 1]->SubItems[$curKey] = $refInfoObj;
}
if ( $refInfoObj->Type == 'array'
|| $refInfoObj->Type == 'object' ) {
$refInfoObj->SubItems = [];
$refInfoObjectStack[] = $refInfoObj;
}
}
return $rootRefInfo;
}
private static function convertToRealType( string $keyName ) : float|int|string
{
if ( $keyName[0] == '"' ) {
$keyName = substr($keyName, 1, strlen($keyName) - 2);
} else if ( is_numeric($keyName) ) {
if ( str_contains($keyName, '.') )
$keyName = doubleval($keyName);
else
$keyName = intval($keyName);
}
return $keyName;
}
private static function varDumpToString( mixed $var ) : string
{
ob_start();
var_dump($var);
return ob_get_clean();
}
}
This is not call-time pass by reference. The parameter to the function is a PHP array, which is passed by value.
However, individual elements of PHP arrays can be references, and the new by-value array will copy over the reference, not the value it points to. As the PHP manual puts it:
In other words, the reference behavior of arrays is defined in an element-by-element basis; the reference behavior of individual elements is dissociated from the reference status of the array container.
To see more clearly, let's look at an example with no functions involved:
$myVar = 42;
// Make an array with a value and a reference
$array = [
'value' => $myVar,
'reference' => &$myVar
];
// Make a copy of the array
$newArray = $array;
// Assigning to the value in the new array works as normal
// - i.e. it doesn't affect the original array or variable
echo "Assign to 'value':\n";
$newArray['value'] = 101;
var_dump($myVar, $array, $newArray);
echo "\n\n";
// Assigning to the reference in the new array follows the reference
// - i.e. it changes the value shared between both arrays and the variable
echo "Assign to 'reference':\n";
$newArray['reference'] = 101;
var_dump($myVar, $array, $newArray);
Output:
Assign to 'value':
int(42)
array(2) {
["value"]=>
int(42)
["reference"]=>
&int(42)
}
array(2) {
["value"]=>
int(101)
["reference"]=>
&int(42)
}
Assign to 'reference':
int(101)
array(2) {
["value"]=>
int(42)
["reference"]=>
&int(101)
}
array(2) {
["value"]=>
int(101)
["reference"]=>
&int(101)
}
Additionally, this way I can get a C# like parameter out functionality.
You do not need any hacks to achieve an out parameter. Pass-by-reference is still fully supported, it's just the responsibility of the function to say that it uses it, not the code that calls the function.
function doSomething(&$output) {
$output = 'I did it!';
}
$output = null;
doSomething($output);
echo $output;
C# out parameters also need to be part of the function definition:
To use an out parameter, both the method definition and the calling method must explicitly use the out keyword.
The only difference is that PHP only has an equivalent for ref, not out and in:
[The out keyword] is like the ref keyword, except that ref requires that the variable be initialized before it is passed.
Related
I'm trying to implement my own serialization / var_dump style function in PHP. It seems impossible if there is the possibility of circular arrays (which there is).
In recent PHP versions, var_dump seems to detect circular arrays:
php > $a = array();
php > $a[] = &$a;
php > var_dump($a);
array(1) {
[0]=>
&array(1) {
[0]=>
*RECURSION*
}
}
How would I implement my own serialization type of method in PHP that can detect similarly? I can't just keep track of which arrays I've visited, because strict comparison of arrays in PHP returns true for different arrays that contain the same elements and comparing circular arrays causes a Fatal Error, anyways.
php > $b = array(1,2);
php > $c = array(1,2);
php > var_dump($b === $c);
bool(true)
php > $a = array();
php > $a[] = &$a;
php > var_dump($a === $a);
PHP Fatal error: Nesting level too deep - recursive dependency? in php shell code on line 1
I've looked for a way to find a unique id (pointer) for an array, but I can't find one. spl_object_hash only works on objects, not arrays. If I cast multiple different arrays to objects they all get the same spl_object_hash value (why?).
EDIT:
Calling print_r, var_dump, or serialize on each array and then using some mechanism to detect the presence of recursion as detected by those methods is an algorithmic complexity nightmare and will basically render any use too slow to be practical on large nested arrays.
ACCEPTED ANSWER:
I accepted the answer below that was the first to suggest temporarily altering the an array to see if it is indeed the same as another array. That answers the "how do I compare two arrays for identity?" from which recursion detection is trivial.
The isRecursiveArray(array) method below detects circular/recursive arrays. It keeps track of which arrays have been visited by temporarily adding an element containing a known object reference to the end of the array.
If you want help writing the serialization method, please update your topic question and provide a sample serialization format in your question.
function removeLastElementIfSame(array & $array, $reference) {
if(end($array) === $reference) {
unset($array[key($array)]);
}
}
function isRecursiveArrayIteration(array & $array, $reference) {
$last_element = end($array);
if($reference === $last_element) {
return true;
}
$array[] = $reference;
foreach($array as &$element) {
if(is_array($element)) {
if(isRecursiveArrayIteration($element, $reference)) {
removeLastElementIfSame($array, $reference);
return true;
}
}
}
removeLastElementIfSame($array, $reference);
return false;
}
function isRecursiveArray(array $array) {
$some_reference = new stdclass();
return isRecursiveArrayIteration($array, $some_reference);
}
$array = array('a','b','c');
var_dump(isRecursiveArray($array));
print_r($array);
$array = array('a','b','c');
$array[] = $array;
var_dump(isRecursiveArray($array));
print_r($array);
$array = array('a','b','c');
$array[] = &$array;
var_dump(isRecursiveArray($array));
print_r($array);
$array = array('a','b','c');
$array[] = &$array;
$array = array($array);
var_dump(isRecursiveArray($array));
print_r($array);
Funny method (I know it is stupid :)), but you can modify it and track the "path" to the recursive element. This is just an idea :) Based on the property of the serialized string, when recursion starts in will be the same as the string for the original array. As you can see - I tried it on many different variations and might be something is able to 'fool' it, but it 'detects' all listed recursions. And I did not try recursive arrays with objects.
$a = array('b1'=>'a1','b2'=>'a2','b4'=>'a3','b5'=>'R:1;}}}');
$a['a1'] = &$a;
$a['b6'] = &$a;
$a['b6'][] = array(1,2,&$a);
$b = serialize($a);
print_r($a);
function WalkArrayRecursive(&$array_name, &$temp){
if (is_array($array_name)){
foreach ($array_name as $k => &$v){
if (is_array($v)){
if (strpos($temp, preg_replace('#R:\d+;\}+$#', '',
serialize($v)))===0)
{
echo "\n Recursion detected at " . $k ."\n";
continue;
}
WalkArrayRecursive($v, $temp);
}
}
}
}
WalkArrayRecursive($a, $b);
regexp is for the situation when element with recursion is at the 'end' of the array. and, yes, this recursion is related to the whole array. It is possible to make recursion of the subelements, but it is too late for me to think about them. Somehow every element of the array should be checked for the recursion in its subelements. The same way, like above, through the output of the print_r function, or looking for specific record for recursion in serialized string (R:4;} something like this). And tracing should start from that element, comparing everything below by my script. All that is only if you want to detect where recursion starts, not just whether you have it or not.
ps: but the best thing should be, as I think, to write your own unserialize function from serailized string created by php itself.
My approach is to have a temp array that holds a copy of all objects that were already iterated. like this here:
// We use this to detect recursion.
global $recursion;
$recursion = [];
function dump( $data, $label, $level = 0 ) {
global $recursion;
// Some nice output for debugging/testing...
echo "\n";
echo str_repeat( " ", $level );
echo $label . " (" . gettype( $data ) . ") ";
// -- start of our recursion detection logic
if ( is_object( $data ) ) {
foreach ( $recursion as $done ) {
if ( $done === $data ) {
echo "*RECURSION*";
return;
}
}
// This is the key-line: Remember that we processed this item!
$recursion[] = $data;
}
// -- end of recursion check
if ( is_array( $data ) || is_object( $data ) ) {
foreach ( (array) $data as $key => $item ) {
dump( $item, $key, $level + 1 );
}
} else {
echo "= " . $data;
}
}
And here is some quick demo code to illustrate how it works:
$obj = new StdClass();
$obj->arr = [];
$obj->arr[] = 'Foo';
$obj->arr[] = $obj;
$obj->arr[] = 'Bar';
$obj->final = 12345;
$obj->a2 = $obj->arr;
dump( $obj, 'obj' );
This script will generate the following output:
obj (object)
arr (array)
0 (string) = Foo
1 (object) *RECURSION*
2 (string) = Bar
final (integer) = 12345
a2 (array)
0 (string) = Foo
1 (object) *RECURSION*
2 (string) = Bar
This is my approach. The key is to pass the array by reference to the recursive function simple_var_dump(), and use a tag (in this case "iterating_in_a_higher_level") to distinguish the arrays that are being iterated in a higher nesting level.
#!/usr/bin/php
<?php
function simple_var_dump(&$var, $depth = 0)
{
if (!is_array($var)) {
if (is_scalar($var)) {
return (string)$var;
} else {
return '?';
}
}
if (isset($var['__iterating_in_a_higher_level__'])) {
$r = 'array(' . (count($var)-1) . ')';
return $r . ' *RECURSION*';
}
$r = 'array(' . count($var) . ')';
$var['__iterating_in_a_higher_level__'] = true;
foreach ($var as $key => &$value) {
if ($key !== '__iterating_in_a_higher_level__') {
$r .= "\n" . str_repeat(' ', $depth + 1) . '[' . $key . '] => ' . simple_var_dump($value, $depth + 1);
}
}
unset($var['__iterating_in_a_higher_level__']);
return $r;
}
// example:
//
$a = [new stdClass(), &$a, 30, [40, [[&$a]]], [1, true, &$a], []];
echo simple_var_dump($a) . "\n";
Output:
array(6)
[0] => ?
[1] => array(6) *RECURSION*
[2] => 30
[3] => array(2)
[0] => 40
[1] => array(1)
[0] => array(1)
[0] => array(6) *RECURSION*
[4] => array(3)
[0] => 1
[1] => 1
[2] => array(6) *RECURSION*
[5] => array(0)
It's not elegant, but solves your problem (at least if you dont have someone using *RECURSION* as a value).
<?php
$a[] = &$a;
if(strpos(print_r($a,1),'*RECURSION*') !== FALSE) echo 1;
I want to convert my 2D array into 1D array.
When I do var_dump($image_name_db);
It shows :
array(2) {
[0]=>
array(1) {
["image"]=>
string(7) "pic.PNG"
}
[1]=>
array(1) {
["image"]=>
string(14) "abouttown3.jpg"
}
}
Now how can I convert It into 1D array. As I want to compare two arrays. One array is 1D and other array is 2D, that is why i want 2D array in 1D. So i need both of them in 1D to compare easily.
I am using codeigniter.
Use array_column() function for it, if php version is 5.5+
array_column($image_name_db, 'image');
See: http://php.net/manual/en/function.array-column.php
For below unsupported version use https://github.com/ramsey/array_column
if (!function_exists('array_column')) {
/**
* Returns the values from a single column of the input array, identified by
* the $columnKey.
*
* Optionally, you may provide an $indexKey to index the values in the returned
* array by the values from the $indexKey column in the input array.
*
* #param array $input A multi-dimensional array (record set) from which to pull
* a column of values.
* #param mixed $columnKey The column of values to return. This value may be the
* integer key of the column you wish to retrieve, or it
* may be the string key name for an associative array.
* #param mixed $indexKey (Optional.) The column to use as the index/keys for
* the returned array. This value may be the integer key
* of the column, or it may be the string key name.
* #return array
*/
function array_column($input = null, $columnKey = null, $indexKey = null)
{
// Using func_get_args() in order to check for proper number of
// parameters and trigger errors exactly as the built-in array_column()
// does in PHP 5.5.
$argc = func_num_args();
$params = func_get_args();
if ($argc < 2) {
trigger_error("array_column() expects at least 2 parameters, {$argc} given", E_USER_WARNING);
return null;
}
if (!is_array($params[0])) {
trigger_error(
'array_column() expects parameter 1 to be array, ' . gettype($params[0]) . ' given',
E_USER_WARNING
);
return null;
}
if (!is_int($params[1])
&& !is_float($params[1])
&& !is_string($params[1])
&& $params[1] !== null
&& !(is_object($params[1]) && method_exists($params[1], '__toString'))
) {
trigger_error('array_column(): The column key should be either a string or an integer', E_USER_WARNING);
return false;
}
if (isset($params[2])
&& !is_int($params[2])
&& !is_float($params[2])
&& !is_string($params[2])
&& !(is_object($params[2]) && method_exists($params[2], '__toString'))
) {
trigger_error('array_column(): The index key should be either a string or an integer', E_USER_WARNING);
return false;
}
$paramsInput = $params[0];
$paramsColumnKey = ($params[1] !== null) ? (string) $params[1] : null;
$paramsIndexKey = null;
if (isset($params[2])) {
if (is_float($params[2]) || is_int($params[2])) {
$paramsIndexKey = (int) $params[2];
} else {
$paramsIndexKey = (string) $params[2];
}
}
$resultArray = array();
foreach ($paramsInput as $row) {
$key = $value = null;
$keySet = $valueSet = false;
if ($paramsIndexKey !== null && array_key_exists($paramsIndexKey, $row)) {
$keySet = true;
$key = (string) $row[$paramsIndexKey];
}
if ($paramsColumnKey === null) {
$valueSet = true;
$value = $row;
} elseif (is_array($row) && array_key_exists($paramsColumnKey, $row)) {
$valueSet = true;
$value = $row[$paramsColumnKey];
}
if ($valueSet) {
if ($keySet) {
$resultArray[$key] = $value;
} else {
$resultArray[] = $value;
}
}
}
return $resultArray;
}
}
or use array_map
$image_name_arr = array_map(function($arr){
return $arr['image'];
},$image_name_db);
You need to traverse through the array and store images in to a 1D array.
<?php
$arr = array();
$arr[0]['image'] = 'pic.PNG';
$arr[1]['image'] = 'abouttown3.jpg';
$images = array();
if (! empty($arr)) {
foreach ($arr as $row) {
$images[] = $row['image'];
}
}
echo "<br/> Existing";
echo '<pre>';
print_r($arr);
echo '</pre>';
echo "<br/> New";
echo '<pre>';
print_r($images);
echo '</pre>';
Working demo:
Try with -
$array = array(array("image" => "pic.PNG"), array("image" => "abouttown3.jpg"));
$new = array_map(function($arr) {
return $arr['image'];
}, $array);
OutPut
array(2) {
[0]=>
string(7) "pic.PNG"
[1]=>
string(14) "abouttown3.jpg"
}
The best way is to use array_map, according to php doc:
array_map() returns an array containing all the elements of array1 after applying the callback function to each one. The number of parameters that the callback function accepts should match the number of arrays passed to the array_map()
example :
$output = array_map(function($current){
return $current['image'];
},$your_array);
explanations :
The callback function receive the current element ($current) in the iterated array ($your_array, the 2D array) and returns the value to push to a new array (the output array is $output, it is a 1D array).
Why create a new problem when your original problem "How to compare two multidimensional arrays" can be solved easily?
Check out Compare multidimensional arrays in PHP for more input.
If you really want to convert a multidimensional array into a single dimension, check out this post: How to convert two dimensional array to one dimensional array in php5
I would parse the following string:
$str = 'ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1';
parse_str($str,$f);
I wish that $f be parsed into:
array(
'ProceduresCustomer.tipi_id' => '10',
'ProceduresCustomer.id' => '1'
)
Actually, the parse_str returns
array(
'ProceduresCustomer_tipi_id' => '10',
'ProceduresCustomer_id' => '1'
)
Beside writing my own function, does anybody know if there is a php function for that?
From the PHP Manual:
Dots and spaces in variable names are converted to underscores. For example <input name="a.b" /> becomes $_REQUEST["a_b"].
So, it is not possible. parse_str() will convert all periods to underscores. If you really can't avoid using periods in your query variable names, you will have to write custom function to achieve this.
The following function (taken from this answer) converts the names of each key-value pair in the query string to their corresponding hexadecimal form and then does a parse_str() on it. Then, they're reverted back to their original form. This way, the periods aren't touched:
function parse_qs($data)
{
$data = preg_replace_callback('/(?:^|(?<=&))[^=[]+/', function($match) {
return bin2hex(urldecode($match[0]));
}, $data);
parse_str($data, $values);
return array_combine(array_map('hex2bin', array_keys($values)), $values);
}
Example usage:
$data = parse_qs($_SERVER['QUERY_STRING']);
Quick 'n' dirty.
$str = "ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1";
function my_func($str){
$expl = explode("&", $str);
foreach($expl as $r){
$tmp = explode("=", $r);
$out[$tmp[0]] = $tmp[1];
}
return $out;
}
var_dump(my_func($str));
array(2) {
["ProceduresCustomer.tipi_id"]=> string(2) "10"
["ProceduresCustomer.id"]=>string(1) "1"
}
This quick-made function attempts to properly parse the query string and returns an array.
The second (optional) parameter $break_dots tells the parser to create a sub-array when encountering a dot (this goes beyond the question, but I included it anyway).
/**
* parse_name -- Parses a string and returns an array of the key path
* if the string is malformed, only return the original string as a key
*
* $str The string to parse
* $break_dot Whether or not to break on dots (default: false)
*
* Examples :
* + parse_name("var[hello][world]") = array("var", "hello", "world")
* + parse_name("var[hello[world]]") = array("var[hello[world]]") // Malformed
* + parse_name("var.hello.world", true) = array("var", "hello", "world")
* + parse_name("var.hello.world") = array("var.hello.world")
* + parse_name("var[hello][world") = array("var[hello][world") // Malformed
*/
function parse_name ($str, $break_dot = false) {
// Output array
$out = array();
// Name buffer
$buf = '';
// Array counter
$acount = 0;
// Whether or not was a closing bracket, in order to avoid empty indexes
$lastbroke = false;
// Loop on chars
foreach (str_split($str) as $c) {
switch ($c) {
// Encountering '[' flushes the buffer to $out and increments the
// array counter
case '[':
if ($acount == 0) {
if (!$lastbroke) $out[] = $buf;
$buf = "";
$acount++;
$lastbroke = false;
// In this case, the name is malformed. Return it as-is
} else return array($str);
break;
// Encountering ']' flushes rge buffer to $out and decrements the
// array counter
case ']':
if ($acount == 1) {
if (!$lastbroke) $out[] = $buf;
$buf = '';
$acount--;
$lastbroke = true;
// In this case, the name is malformed. Return it as-is
} else return array($str);
break;
// If $break_dot is set to true, flush the buffer to $out.
// Otherwise, treat it as a normal char.
case '.':
if ($break_dot) {
if (!$lastbroke) $out[] = $buf;
$buf = '';
$lastbroke = false;
break;
}
// Add every other char to the buffer
default:
$buf .= $c;
$lastbroke = false;
}
}
// If the counter isn't back to 0 then the string is malformed. Return it as-is
if ($acount > 0) return array($str);
// Otherwise, flush the buffer to $out and return it.
if (!$lastbroke) $out[] = $buf;
return $out;
}
/**
* decode_qstr -- Take a query string and decode it to an array
*
* $str The query string
* $break_dot Whether or not to break field names on dots (default: false)
*/
function decode_qstr ($str, $break_dots = false) {
$out = array();
// '&' is the field separator
$a = explode('&', $str);
// For each field=value pair:
foreach ($a as $param) {
// Break on the first equal sign.
$param = explode('=', $param, 2);
// Parse the field name
$key = parse_name($param[0], $break_dots);
// This piece of code creates the array structure according to th
// decomposition given by parse_name()
$array = &$out; // Reference to the last object. Starts to $out
$append = false; // If an empty key is given, treat it like $array[] = 'value'
foreach ($key as $k) {
// If the current ref isn't an array, make it one
if (!is_array($array)) $array = array();
// If the current key is empty, break the loop and append to current ref
if (empty($k)) {
$append = true;
break;
}
// If the key isn't set, set it :)
if (!isset($array[$k])) $array[$k] = NULL;
// In order to walk down the array, we need to first save the ref in
// $array to $tmp
$tmp = &$array;
// Deletes the ref from $array
unset($array);
// Create a new ref to the next item
$array =& $tmp[$k];
// Delete the save
unset($tmp);
}
// If instructed to append, do that
if ($append) $array[] = $param[1];
// Otherwise, just set the value
else $array = $param[1];
// Destroy the ref for good
unset($array);
}
// Return the result
return $out;
}
I tried to correctly handle multi-level keys. The code is a bit hacky, but it should work. I tried to comment the code, comment if you have any question.
Test case:
var_dump(decode_qstr("ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1"));
// array(2) {
// ["ProceduresCustomer.tipi_id"]=>
// string(2) "10"
// ["ProceduresCustomer.id"]=>
// string(1) "1"
// }
var_dump(decode_qstr("ProceduresCustomer.tipi_id=10&ProceduresCustomer.id=1", true));
// array(1) {
// ["ProceduresCustomer"]=>
// array(2) {
// ["tipi_id"]=>
// string(2) "10"
// ["id"]=>
// string(1) "1"
// }
// }
I would like to add my solution as well, because I had trouble finding one that did all I needed and would handle all circumstances. I tested it quite thoroughly. It keeps dots and spaces and unmatched square brackets (normally changed to underscores), plus it handles arrays in the input well. Tested in PHP 8.0.0 and 8.0.14.
const periodPlaceholder = 'QQleQPunT';
const spacePlaceholder = 'QQleQSpaTIE';
function parse_str_clean($querystr): array {
// without the converting of spaces and dots etc to underscores.
$qquerystr = str_ireplace(['.','%2E','+',' ','%20'], [periodPlaceholder,periodPlaceholder,spacePlaceholder,spacePlaceholder,spacePlaceholder], $querystr);
$arr = null ; parse_str($qquerystr, $arr);
sanitizeArr($arr, $querystr);
return $arr;
}
function sanitizeArr(&$arr, $querystr) {
foreach($arr as $key=>$val) {
// restore values to original
if ( is_string($val)) {
$newval = str_replace([periodPlaceholder,spacePlaceholder], ["."," "], $val);
if ( $val != $newval) $arr[$key]=$newval;
}
}
unset($val);
foreach($arr as $key=>$val) {
$newkey = str_replace([periodPlaceholder,spacePlaceholder], ["."," "], $key);
if ( str_contains($newkey, '_') ) {
// periode of space or [ or ] converted to _. Restore with querystring
$regex = '/&('.str_replace('_', '[ \.\[\]]', preg_quote($newkey, '/')).')=/';
$matches = null ;
if ( preg_match_all($regex, "&".urldecode($querystr), $matches) ) {
if ( count(array_unique($matches[1])) === 1 && $key != $matches[1][0] ) {
$newkey = $matches[1][0] ;
}
}
}
if ( $newkey != $key ) $arr = array_replace_key($arr,$key, $newkey);
if ( is_array($val)) {
sanitizeArr($arr[$newkey], $querystr);
}
}
}
function array_replace_key($array, $oldKey, $newKey): array {
// preserves order of the array
if( ! array_key_exists( $oldKey, $array ) ) return $array;
$keys = array_keys( $array );
$keys[ array_search( $oldKey, $keys ) ] = $newKey;
return array_combine( $keys, $array );
}
First replaces spaces and . by placeholders in querystring before coding before parsing, later undoes that within the array keys and values. This way we can use the normal parse_str.
Unmatched [ and ] are also replaced by underscores by parse_str, but these cannot be reliably replaced by a placeholder. And we definitely don't want to replace matched []. Hence we don't replace [ and ], en let them be replaced by underscores by parse_str. Then we restore the _ in the resulting keys and seeing in the original querystring if there was a [ or ] there.
Known bug: keys 'something]something' and almost identical 'something[something' may be confused. It's occurrence will be zero, so I left it.
Test:
var_dump(parse_str_clean("code.1=printr%28hahaha&code 1=448044&test.mijn%5B%5D%5B2%5D=test%20Roemer&test%20mijn%5B=test%202e%20Roemer"));
yields correctly
array(4) {
["code.1"]=>
string(13) "printr(hahaha"
["code 1"]=>
string(6) "448044"
["test.mijn"]=>
array(1) {
[0]=>
array(1) {
[2]=>
string(11) "test Roemer"
}
}
["test[mijn"]=>
string(14) "test 2e Roemer"
}
whereas the original parse_str only yields, with the same string:
array(2) {
["code_1"]=>
string(6) "448044"
["test_mijn"]=>
string(14) "test 2e Roemer"
}
I have some logic that is being used to sort data but depending on the user input the data is grouped differently. Right now I have five different functions that contain the same logic but different groupings. Is there a way to combine these functions and dynamically set a value that will group properly. Within the function these assignments are happening
For example, sometimes I store the calculations simply by:
$calcs[$meter['UnitType']['name']] = ...
but other times need a more specific grouping:
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] =...
As you can see sometimes it is stored in a multidiminesional array and other times not. I have been trying to use eval() but without success (not sure that is the correct approach). Storing the data in a temporary variable does not really save much because there are many nested loops and if statements so the array would have to be repeated in multiple places.
EDIT
I hope the following example explains my problem better. It is obviously a dumbed down version:
if(){
$calcs[$meter['UnitType']['name']] = $data;
} else {
while () {
$calcs[$meter['UnitType']['name']] = $data;
}
}
Now the same logic can be used but for storing it in different keys:
if(){
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
} else {
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
}
Is there a way to abstract out the keys in the $calc[] array so that I can have one function instead of having multiple functions with different array keys?
You can use this if you want to get&set array values dynamically.
function getVal($data,$chain){
$level = $data;
for($i=0;$i<count($chain);$i++){
if(isset($level[$chain[$i]]))
$level = $level[$chain[$i]];
else
return null; // key does not exist, return null
}
return $level;
}
function setVal(&$data,$chain,$value){
$level = &$data;
for($i=0;$i<count($chain);$i++){
$level = &$level[$chain[$i]]; // set reference (&) in order to change the value of the object
}
$level = $value;
}
How it works:
Calling getVal($data,array('foo','bar','2017-08')) will return the equivalent of $data['foo']['bar']['2017-08'].
Calling setVal($data,array('foo','bar','2017-08'),'hello') will set value as if you called
$data['foo']['bar']['2017-08'] = 'hello'. non-existent keys will be created automatically by php magic.
This can be useful if you want to build the structure of the array dynamically.
Here's a function I wrote for setting deeply nested members on arrays or objects:
function dict_set($var, $path, $val) {
if(empty($var))
$var = is_array($var) ? array() : new stdClass();
$parts = explode('.', $path);
$ptr =& $var;
if(is_array($parts))
foreach($parts as $part) {
if('[]' == $part) {
if(is_array($ptr))
$ptr =& $ptr[];
} elseif(is_array($ptr)) {
if(!isset($ptr[$part]))
$ptr[$part] = array();
$ptr =& $ptr[$part];
} elseif(is_object($ptr)) {
if(!isset($ptr->$part))
$ptr->$part = array();
$ptr =& $ptr->$part;
}
}
$ptr = $val;
return $var;
}
Using your example data:
$array = [];
$array = dict_set($array, 'resource1.unit1.2017-10', 'value1');
$array = dict_set($array, 'resource1.unit2.2017-11', 'value2');
$array = dict_set($array, 'resource2.unit1.2017-10', 'value3');
print_r($array);
Results in output like:
Array
(
[resource1] => Array
(
[unit1] => Array
(
[2017-10] => value1
)
[unit2] => Array
(
[2017-11] => value2
)
)
[resource2] => Array
(
[unit1] => Array
(
[2017-10] => value3
)
)
)
The second argument to dict_set() is a $path string in dot-notation. You can build this using dynamic keys with period delimiters between the parts. The function works with arrays and objects.
It can also append incremental members to deeply nested array by using [] as an element of the $path. For instance: parent.child.child.[]
Would it not be easier to do the following
$calcs = array(
$meter['Resource']['name'] => array(
$meter['UnitType']['name'] => 'Some Value',
$meter['UnitType']['name2'] => 'Some Value Again'
),
);
or you can use Objects
$calcs = new stdClass();
$calcs->{$meter['UnitType']['name']} = 'Some Value';
but I would advice you build your structure in arrays and then do!
$calcs = (object)$calcs_array;
or you can loop your first array into a new array!
$new = array();
$d = date('Y-m',$start);
foreach($meter as $key => $value)
{
$new[$key]['name'][$d] = array();
}
Give it ago and see how the array structure comes out.
Try to use a switch case.
<?php
$userinput = $calcs[$meter['UnitType']['name']] = $data;;
switch ($userinput) {
case "useriput1":
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
break;
case "userinput2":
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
break;
...
default:
while () {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
}
}
?>
I agree with the comment on the OP by #Jake N that perhaps using objects is a better approach. Nonetheless, if you want to use arrays, you can check for the existence of keys in a conditional, like so:
if(
array_key_exists('Resource', $meter)
) {
$calcs[$meter['Resource']['name']][$meter['UnitType']['name']][date('Y-m',$start)] = $data;
} else {
$calcs[$meter['UnitType']['name']] = $data;
}
On the other hand, if you want to use objects, you can create a MeterReading object type, and then add MeterReading instances as array elements to your $calcs array, like so:
// Object defintion
class MeterReading {
private $data;
private $resource;
private $startDate;
private $unitType;
public function __construct(Array $meter, $start, $data) {
$this->unitType = $meter['UnitType']['name'];
$this->resource = $meter['Resource']['name'];
$this->startDate = date('Y-m',$start);
}
public function data() {
return $this->data;
}
public function resource() {
return $this->resource;
}
public function startDate() {
return $this->startDate;
}
public function unitType() {
return $this->unitType;
}
}
// Example population
$calcs[] = new MeterReading($meter, $start, $data);
// Example usage
foreach($calcs as $calc) {
if($calc->resource()) {
echo 'Resource: ' . $calc->resource() . '<br>';
}
echo 'Unit Type: ' . $calc->unitType() . '<br>';
echo 'Start Date: ' . $calc->startDate() . '<br>';
echo 'Data: ' . $calc->data() . '<br>';
}
Obviously you can take this further, such as checking the existence of array keys in the object constructor, giving the object property resource a default value if not provided, and so on, but this is a start to an OO approach.
You can use this library to get or set value in multidimensional array using array of keys:
Arr::getNestedElement($calcs, [
$meter['Resource']['name'],
$meter['UnitType']['name'],
date('Y-m', $start)
]);
to get value or:
Arr::handleNestedElement($calcs, [
$meter['Resource']['name'],
$meter['UnitType']['name'],
date('Y-m', $start)
], $data);
to set $data as value.
My goal is to echo the argument passed to a function. For example, how can this be done?
$contact_name = 'foo';
function do_something($some_argument){
// echo 'contact_name' .... How???
}
do_something($contact_name);
You can't. If you want to do that, you need to pass the names as well, e.g:
$contact_name = 'foo';
$contact_phone = '555-1234';
function do_something($args = array()) {
foreach ($args as $name => $value) {
echo "$name: $value<br />";
}
}
do_something(compact('contact_name', 'contact_phone'));
Straight off the PHP.net variables page:
<?php
function vname(&$var, $scope=false, $prefix='unique', $suffix='value')
{
if($scope) $vals = $scope;
else $vals = $GLOBALS;
$old = $var;
$var = $new = $prefix.rand().$suffix;
$vname = FALSE;
foreach($vals as $key => $val) {
if($val === $new) $vname = $key;
}
$var = $old;
return $vname;
}
?>
Not possible.
Variables are just means to address values or areas in the memory. You cannot get the variable name that’s value has been passed to a function.
Disclaimer: this will oonly work if you pass a variable to the function, not a value, and it only works when your not in a function or a class. So only the GLOBAL scope works :)
Good funct($var)
Bad funct(1)
You can do it actually contrary to popular believe ^_^. but it involves a few lookup tricks with the $GLOBALS variable.
you do it like so:
$variable_name = "some value, better if its unique";
function funct($var) {
foreach ($GLOBALS as $name => $value) {
if ($value == $var) {
echo $name; // will echo variable_name
break;
}
}
}
this method is not fool proof tho. Because if two variables have the same value, the function will get the name of the first one it finds. Not the one you want :P
Its best to make the variable value unique before hand if you want accuracy on variable names
Another method would be to use reference to be accurate like so
$variable_name = 123;
function funct(&$var) {
$old = $var;
$var = $checksum = md5(time()); // give it unique value
foreach ($GLOBALS as $name => $value) {
if ($value == $var) {
echo $name; // will echo variable_name
$var = $old; // reassign old value
break;
}
}
}
so it is entirely possible :)
Based on PTBNL's (most definately correct) answer i came up with a more readable (at least i think so) approach:
/**
* returns the name of the variable posted as the first parameter.
* If not called from global scope, pass in get_defined_vars() as the second parameter
*
* behind the scenes:
*
* this function only works because we are passing the first argument by reference.
* 1. we store the old value in a known variable
* 2. we overwrite the argument with a known randomized hash value
* 3. we loop through the scope's symbol table until we find the known value
* 4. we restore the arguments original value and
* 5. we return the name of the symbol we found in the table
*/
function variable_name( & $var, array $scope = null )
{
if ( $scope == null )
{
$scope = $GLOBALS;
}
$__variable_name_original_value = $var;
$__variable_name_temporary_value = md5( number_format( microtime( true ), 10, '', '' ).rand() );
$var = $__variable_name_temporary_value;
foreach( $scope as $variable => $value )
{
if ( $value == $__variable_name_temporary_value && $variable != '__variable_name_original_value' )
{
$var = $__variable_name_original_value;
return $variable;
}
}
return null;
}
// prove that it works:
$test = 1;
$hello = 1;
$world = 2;
$foo = 100;
$bar = 10;
$awesome = 1;
function test_from_local_scope()
{
$local_test = 1;
$local_hello = 1;
$local_world = 2;
$local_foo = 100;
$local_bar = 10;
$local_awesome = 1;
return variable_name( $local_awesome, get_defined_vars() );
}
printf( "%s\n", variable_name( $awesome, get_defined_vars() ) ); // will echo 'awesome'
printf( "%s\n", test_from_local_scope() ); // will also echo awesome;
Sander has the right answer, but here is the exact thing I was looking for:
$contact_name = 'foo';
function do_something($args = array(), $another_arg) {
foreach ($args as $name => $value) {
echo $name;
echo '<br>'.$another_arg;
}
}
do_something(compact(contact_name),'bar');
class Someone{
protected $name='';
public function __construct($name){
$this->name=$name;
}
public function doSomthing($arg){
echo "My name is: {$this->name} and I do {$arg}";
}
}
//in main
$Me=new Someone('Itay Moav');
$Me->doSomething('test');