I'v recently came accross a problem with some code as shown below:
$key = "upload_8_fid_aids.tmp";
public function to_key($key) {
$s = $this->table;//$s = kv
foreach((array)$key as $k=>$v) {
$s .= '-'.$this->primarykey[$k].'-'.$v;
}
return $s;
}
There's a (array)$key signature out there in the foreach loop,the first thing I'm stucking in is the "array" that prefixed with the variabls $k,what does this mean?The very first idea that hit upon me is that it converts the $k to an array,though,the variable $k is a string,is it plausible to convert string to array in php?I think it is unreasonable.So what does that array mean?
Thanks in advance!
When you cast a string to an array in PHP it becomes an array with the string pushed to it.
Example:
$test = "This is a string!";
print_r((array) $test);
Output:
Array
(
[0] => This is a string!
)
That said I find the code strange, I don't see the need for the loop, it could just be:
$key = "upload_8_fid_aids.tmp";
public function to_key($key) {
$s = $this->table; //$s = kv
$s .= '-' . $this->primarykey[0] . '-' . $key;
return $s;
}
Any type enclosed in parentheses is telling PHP to cast the following thing to that type.
In this case, it's a cheap way to avoid having to check if( is_array($key)), by just forcing it to be one.
Converting an object to an array:
<?php
/*** create an object ***/
$obj = new stdClass;
$obj->foo = 'foo';
$obj->bar = 'bar';
$obj->baz = 'baz';
/*** cast the object ***/
$array = (array) $obj;
/*** show the results ***/
print_r( $array );
?>
Result:
Array
(
[foo] => foo
[bar] => bar
[baz] => baz
)
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;
Good day, I am trying to convert an array "$list" into string or object. I have used following methods:
<?php
include "medclass.php";
session_start();
if (isset($_SESSION['mail']))
{
$list = $_SESSION['basket'];
}
else
header("location: clientsigninpage.php?msg= Log-in First");
$obj = new med_class;
$obj->connectdb();
$val = implode(";",$list); //implode method
$val = (object) $list; //object method
$val = serialize($list); //serialize method
$result = $obj->searchMed($val);
while ($row = $result->fetchObject())
{
echo $row->MedPrice;
}
?>
With "(object)" its giving me following error: "Object of class stdClass could not be converted to string", with "implode": "Array to string conversion" and with "serialize()" it does not print anything.
The function that I am passing value is:
function searchMed($v1)
{
$sql = "select * from storepreview where MedName = '$v1'";
$ret = $this->con->query($sql);
return $ret;
}
I have used these methods by seen following links: (http://www.dyn-web.com/php/arrays/convert.php) ; (Convert an array to a string); (How to convert an array to object in PHP?)
I managed to reproduce your "Array to string conversion" error when using the implode command by running the following line of code:
implode(";", [[]]); // PHP Notice: Array to string conversion in php shell code on line 1
For converting a nested array into a string I found that a foreach loop worked:
$nestedArray = ['outerKeyOne' => ['innerKeyOne' => 'valueOne'], 'outerKeyTwo' => ['innerKeyTwo' => 'valueTwo']];
$arrayOfStrings = [];
foreach ($nestedArray as $key => $value) {
$arrayOfStrings[] = implode(",", $value);
}
implode(";", $arrayOfStrings); // string(17) "valueOne;valueTwo"
The second error associated with the line $val = (object) $list; is from trying to embed an object into the $sql string. It seems like an object is not what you want here, unless it is an object that has a __toString() method implemented.
I hope this is of some help. Using var_dump or something similar would provide more debug output to better diagnose the problems along with the above error messages. That's how I came up with the above code.
You can use json_encode to convert Array to String:
$FINAL_VALUE = json_encode($YOUR_OBJECT);
For more information, you can refer this link.
Is it possible to create a variable variable pointing to an array or to nested objects? The php docs specifically say you cannot point to SuperGlobals but its unclear (to me at least) if this applies to arrays in general.
Here is my try at the array var var.
// Array Example
$arrayTest = array('value0', 'value1');
${arrayVarTest} = 'arrayTest[1]';
// This returns the correct 'value1'
echo $arrayTest[1];
// This returns null
echo ${$arrayVarTest};
Here is some simple code to show what I mean by object var var.
${OBJVarVar} = 'classObj->obj';
// This should return the values of $classObj->obj but it will return null
var_dump(${$OBJVarVar});
Am I missing something obvious here?
Array element approach:
Extract array name from the string and store it in $arrayName.
Extract array index from the string and store it in $arrayIndex.
Parse them correctly instead of as a whole.
The code:
$arrayTest = array('value0', 'value1');
$variableArrayElement = 'arrayTest[1]';
$arrayName = substr($variableArrayElement,0,strpos($variableArrayElement,'['));
$arrayIndex = preg_replace('/[^\d\s]/', '',$variableArrayElement);
// This returns the correct 'value1'
echo ${$arrayName}[$arrayIndex];
Object properties approach:
Explode the string containing the class and property you want to access by its delimiter (->).
Assign those two variables to $class and $property.
Parse them separately instead of as a whole on var_dump()
The code:
$variableObjectProperty = "classObj->obj";
list($class,$property) = explode("->",$variableObjectProperty);
// This now return the values of $classObj->obj
var_dump(${$class}->{$property});
It works!
Use = & to assign by reference:
$arrayTest = array('value0', 'value1');
$arrayVarTest = &$arrayTest[1];
$arrayTest[1] = 'newvalue1'; // to test if it's really passed by reference
print $arrayVarTest;
In echo $arrayTest[1]; the vars name is $arrayTest with an array index of 1, and not $arrayTest[1]. The brackets are PHP "keywords". Same with the method notation and the -> operator. So you'll need to split up.
// bla[1]
$arr = 'bla';
$idx = 1;
echo $arr[$idx];
// foo->bar
$obj = 'foo';
$method = 'bar';
echo $obj->$method;
What you want to do sounds more like evaluating PHP code (eval()). But remember: eval is evil. ;-)
Nope you can't do that. You can only do that with variable, object and function names.
Example:
$objvar = 'classObj';
var_dump(${$OBJVarVar}->var);
Alternatives can be via eval() or by doing pre-processing.
$arrayTest = array('value0', 'value1');
$arrayVarTest = 'arrayTest[1]';
echo eval('return $'.$arrayVarTest.';');
eval('echo $'.$arrayVarTest.';');
That is if you're very sure of what's going to be the input.
By pre-processing:
function varvar($str){
if(strpos($str,'->') !== false){
$parts = explode('->',$str);
global ${$parts[0]};
return $parts[0]->$parts[1];
}elseif(strpos($str,'[') !== false && strpos($str,']') !== false){
$parts = explode('[',$str);
global ${$parts[0]};
$parts[1] = substr($parts[1],0,strlen($parts[1])-1);
return ${$parts[0]}[$parts[1]];
}else{
return false;
}
}
$arrayTest = array('value0', 'value1');
$test = 'arrayTest[1]';
echo varvar($test);
there is a dynamic approach for to many nested levels:
$attrs = ['level1', 'levelt', 'level3',...];
$finalAttr = $myObject;
foreach ($attrs as $attr) {
$finalAttr = $finalAttr->$attr;
}
return $finalAttr;
I have a class with constants like this:
class AClass
{
const CODE_NAME_123 = "value1";
const CODE_NAME_456 = "value2";
}
Is there a way to convert the name of a constant like AClass::CODE_NAME_123 into a string in order to, e.g., extract the trailing digits from the string?
You can use something like this:
//PHP's ReflectionClass will allow us to get the details of a particular class
$r = new ReflectionClass('AClass');
$constants = $r->getConstants();
//all constants are now stored in an array called $constants
var_dump($constants);
//example showing how to get trailing digits from constant names
$digits = array();
foreach($constants as $constantName => $constantValue){
$exploded = explode("_", $constantName);
$digits[] = $exploded[2];
}
var_dump($digits);
You can use ReflectionClass::getConstants() and iterate through the result to find a constant with a specific value, and then get the last digits from the constant name with regex:
<?php
class AClass
{
const CODE_NAME_123 = "foo";
const CODE_NAME_456 = "bar";
}
function findConstantWithValue($class, $searchValue) {
$reflectionClass = new ReflectionClass($class);
foreach ($reflectionClass->getConstants() as $constant => $value) {
if ($value === $searchValue) {
return $constant;
}
}
return null;
}
function findConstantDigitsWithValue($class, $searchValue) {
$constant = findConstantWithValue($class, $searchValue);
if ($constant !== null && preg_match('/\d+$/', $constant, $matches)) {
return $matches[0];
}
return null;
}
var_dump( findConstantDigitsWithValue('AClass', 'foo') ); //string(3) "123"
var_dump( findConstantDigitsWithValue('AClass', 'bar') ); //string(3) "456"
var_dump( findConstantDigitsWithValue('AClass', 'nop') ); //NULL
?>
DEMO
Use ReflectionClass() and loop through the resulting keys applying a preg_match to each value to extract the last 3 numbers of the string:
class AClass
{
const CODE_NAME_123 = "";
const CODE_NAME_456 = "";
}
$constants = new ReflectionClass('AClass');
$constants_array = $constants->getConstants();
foreach ($constants_array as $constant_key => $constant_value) {
preg_match('/\d{3}$/i', $constant_key, $matches);
echo '<pre>';
print_r($matches);
echo '</pre>';
}
The output would be:
Array
(
[0] => 123
)
Array
(
[0] => 456
)
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.