I have this example array.
$data = new stdClass();
$data->foo = [
'foo1' => &$data,
'foo2' => 23,
];
$data->bar = new stdClass();
$data->nar->object = [
'bar1' => &$data->bar,
'bar2' => 43,
];
I want to parse this to:
$data = new stdClass();
$data->foo = [
'foo1' => "RECURSION DETECTED",
'foo2' => 23,
];
$data->bar = new stdClass();
$data->nar->object = [
'bar1' => "RECURSION DETECTED",
'bar2' => 43,
];
I need it, because json_encode can't encode data when recursion is detected.
I tried so many times and in different ways, I did a lot of research, but I did not find anything to really help me.
My last attempt was:
function _stack(&$object, &$stack = [], $key = 'original')
{
if (isObjectOrArray($object)) {
if (!in_array($object, $stack, true)) {
if (is_object($object)) {
$stack[$key] = &$object;
}
foreach ($object as $key => &$value) {
_stack($value, $stack, $key);
}
}
}
return $stack;
}
function _remove($object, $stack, $objectO = false, $key = 'original')
{
/**
* #var $objectO false | object
*/
if (!$objectO) {
$objectO = $object;
}
if (isObjectOrArray($object)) {
foreach ($object as $prop => $value) {
if (is_object($objectO)) {
if (in_array($object->{$prop}, $stack, true) && $prop !== $key) {
$objectO->{$prop} = "RECURSION DETECTED";
} else {
$objectO->{$prop} = _remove($object->{$prop}, $stack, $objectO->{$prop}, $prop);
}
} else {
if (in_array($object[$prop], $stack, true) && $prop !== $key) {
$objectO[$prop] = "RECURSION DETECTED";
} else {
$objectO[$prop] = _remove($object[$prop], $stack, $objectO[$prop], $prop);
}
}
}
}
return $objectO;
}
First i crate an stack with original objects (not reference / pointer).
The key is passed to the function, within itself in recursion, so I know exactly where recursion meets the original object. I need it so I can then tell what the pointer is and what the original object is.
After create stack i run the same looping, but the current value inside foreach statement is an object and he is inside stack and the current key is diferent of current key pass to the function call, the reference / pointer is breaked.
Array
(
[foo1] => RECURSION DETECTED
[foo2] => 23
)
But at the end of all function calls I get only:
RECURSION DETECTED
I am still looking at another way since this is interesting, but it is easy to replace the reference pointer in a serialized string and then unserialize it:
$data = unserialize(preg_replace('/R:\d+/', 's:18:"RECURSION DETECTED"', serialize($data)));
Another option for PHP >= 7.3.0 is exporting and forcing it to break the references. var_export will complain about recursion, however it will happily display it with the references replaced with NULL. var_export has a second argument to return the output instead of displaying, but this doesn't work with recursion so I buffered and captured the output.
ob_start();
#var_export($data);
$var = ob_get_clean();
eval("\$data = $var;");
For PHP < 7.3.0 you can use the above code with your own class that implements __set_state instead of stdClass:
class myClass {
public static function __set_state($array) {
$o = new self;
foreach($array as $key => $val) {
$o->$key = $val;
}
return $o;
}
}
$data = new myClass();
Related
I have a recursive $data structure that I need to modify. Each node considered an $item should get a property with the value of $value added. Things that I tried (and how they failed) are:
array_walk_recursive: Visits only leaf nodes.
Stack/queue: I failed to modify the original structure but only altered the copies on the stack/queue.
Loops: Without the stack/queue approach I would need to know the nesting level and write an awful lot of nested loops.
array_map: I failed to write a proper recursive callback given that the value of $value is not static but the result of previous code. So it must somehow get "into" the callback. Since use is only available to anonymous functions I did not manage to write a recursive one.
Loop and recursive function: This answer to a similar question failed for the same reason as the array_map approach.
My situation in code looks similar to this example:
<?php
$value = 'example';
$data = array(
'foo' => 'bar'
'items' => array(
array(
'foo' => 'bar',
'items' => array(
array('foo' => 'bar')
)
)
)
);
// do this recursively to every member of an 'items' property:
$item['baz'] = $value;
Can you think of a different approach or help me straighten out one of those that I failed at so far?
Update
Some code that I tried that did not work:
// Parse error: syntax error, unexpected 'use' (T_USE), expecting '{'
function do (&$item) use ($value) {
$item['baz'] = $value;
foreach ($item['items'] as $next) {
do($next);
}
}
// Undefined variable: value
function do (&$item) {
$item['baz'] = $value;
foreach ($item['items'] as $next) {
do($next);
}
}
foreach ($data['items'] as $item) {
do($item);
}
Works for now (I would prefer not having to pass the $value parameter, though):
function do (&$item, $value) {
$item['baz'] = $value;
foreach ($item['items'] as &$next) {
do($next, $value);
}
}
foreach ($data['items'] as &$item) {
do($item, $value);
}
Check this code for get each key and value:
<?php
error_reporting(0);
$value = 'example';
$data = array(
'foo' => 'bar',
'items' => array( array( 'foo' => 'bar','items' => array(array('foo' => 'bar') ) ) )
);
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($data));
foreach ($iterator as $k => $v) {
echo $k.'=>'.$v;
echo '</br>';
}
?>
Formatting was necessary but did not suffice for the minimum edit length. So I added this otherwise useless text.
The following recursive function works for me. Note that it requires to pass parameters by reference also inside the foreach loop:
$value = 'example';
function do (&$item, $value) {
$item['baz'] = $value;
foreach ($item['items'] as &$next) {
do($next, $value);
}
}
foreach ($data['items'] as &$item) {
do($item, $value);
}
I use this method:
<?php
/**
* #param array $arr
* #param callable $callback
* #param array $options
*
*
* Example:
* (this will add the link property to every node in the array recursively)
*
*
* $linkFmt = "/mylink/{type}/{slug}";
* ArrayTool::updateNodeRecursive($ret, function (array &$row) use ($linkFmt) {
* $row['link'] = str_replace([
* "{type}",
* "{slug}",
* ], [
* $row['type'],
* $row['slug'],
* ], $linkFmt);
* });
*
*
*
*
*/
public static function updateNodeRecursive(array &$arr, callable $callback, array $options = [])
{
$childrenKey = $options['childrenKey'] ?? "children";
foreach ($arr as $k => $v) {
call_user_func_array($callback, [&$v]);
if (array_key_exists($childrenKey, $v) && $v[$childrenKey]) {
$children = $v[$childrenKey];
self::updateNodeRecursive($children, $callback, $options);
$v[$childrenKey] = $children;
}
$arr[$k] = $v;
}
}
Is it possible to set multiple properties at a time for an object in php?
Instead of doing:
$object->prop1 = $something;
$object->prop2 = $otherthing;
$object->prop3 = $morethings;
do something like:
$object = (object) array(
'prop1' => $something,
'prop2' => $otherthing,
'prop3' => $morethings
);
but without overwriting the object.
Not like the way you want. but this can be done by using a loop.
$map = array(
'prop1' => $something,
'prop2' => $otherthing,
'prop3' => $morethings
);
foreach($map as $k => $v)
$object->$k = $v;
See only 2 extra lines.
You should look at Object Oriented PHP Best Practices :
"since the setter functions return $this you can chain them like so:"
$object->setName('Bob')
->setHairColor('green')
->setAddress('someplace');
This incidentally is known as a fluent interface.
I would recommend you don't do it. Seriously, don't.
Your code is much MUCH cleaner the first way, it's clearer of your intentions, and you aren't obfocusing your code to the extent where sometime in the future someone would look at your code and think "What the hell was the idiot thinking"?
If you insist on doing something which is clearly the wrong way to go, you can always create an array, iterate it and set all the properties in a loop. I won't give you code though. It's evil.
You could write some setters for the object that return the object:
public function setSomething($something)
{
$this->something = $something;
return $this; //this will return the current object
}
You could then do:
$object->setSomething("something")
->setSomethingelse("somethingelse")
->setMoreThings("some more things");
You would need to write a setter for each property as a __set function is not capable of returning a value.
Alternatively, set a single function to accept an array of property => values and set everything?
public function setProperties($array)
{
foreach($array as $property => $value)
{
$this->{$property} = $value;
}
return $this;
}
and pass in the array:
$object->setProperties(array('something' => 'someText', 'somethingElse' => 'more text', 'moreThings'=>'a lot more text'));
I realise this is an old question but for the benefit of others that come across it, I solved this myself recently and wanted to share the result
<?php
//Just some setup
header('Content-Type: text/plain');
$account = (object) array(
'email' => 'foo',
'dob'=>((object)array(
'day'=>1,
'month'=>1,
'year'=>((object)array('century'=>1900,'decade'=>0))
))
);
var_dump($account);
echo "\n\n==============\n\n";
//The functions
function &getObjRef(&$obj,$prop) {
return $obj->{$prop};
}
function updateObjFromArray(&$obj,$array){
foreach ($array as $key=>$value) {
if(!is_array($value))
$obj->{$key} = $value;
else{
$ref = getObjRef($obj,$key);
updateObjFromArray($ref,$value);
}
}
}
//Test
updateObjFromArray($account,array(
'id' => '123',
'email' => 'user#domain.com',
'dob'=>array(
'day'=>19,
'month'=>11,
'year'=>array('century'=>1900,'decade'=>80)
)
));
var_dump($account);
Obviously there are no safeguards built in. The main caveat is that the updateObjFromArray function assumes that for any nested arrays within $array, the corresponding key in $obj already exists and is an object, this must be true or treating it like an object will throw an error.
Hope this helps! :)
I wouldn't actually do this....but for fun I would
$object = (object) ($props + (array) $object);
you end up with an stdClass composed of $objects public properties, so it loses its type.
Method objectThis() to transtypage class array properties or array to stdClass. Using direct transtypage (object) would remove numeric index, but using this method it will keep the numeric index.
public function objectThis($array = null) {
if (!$array) {
foreach ($this as $property_name => $property_values) {
if (is_array($property_values) && !empty($property_values)) {
$this->{$property_name} = $this->objectThis($property_values);
} else if (is_array($property_values) && empty($property_values)) {
$this->{$property_name} = new stdClass();
}
}
} else {
$object = new stdClass();
foreach ($array as $index => $values) {
if (is_array($values) && empty($values)) {
$object->{$index} = new stdClass();
} else if (is_array($values)) {
$object->{$index} = $this->objectThis($values);
} else if (is_object($values)) {
$object->{$index} = $this->objectThis($values);
} else {
$object->{$index} = $values;
}
}
return $object;
}
}
I am trying to recurse through a multidimensional Object/Array structure to create JSON, but the following isn't working. $data is reset, but I'm not sure how to prevent this.
public function encodeJSON($data) {
foreach ($data as $key => $value) {
if (is_array($value) || is_object($value)) {
$json->$key = $this->encodeJSON($value);
} else {
$json->$key = $value;
}
}
return json_encode($json);
}
If you're trying to learn recursion that's one thing, but at least for me json_encode automatically encodes objects and arrays recursively so it's not really necessary to write the extra function.
Tested with this code:
class TestClass {
var $c1;
var $c2;
function __construct() {
$this->c1 = 'member variable 1';
$this->c2 = 8080;
}
}
$test = array('hello' => 'world', 'age' => 30,
'arr' => array('a' => 'b', 'c' => 'd'), 'obj' => new TestClass());
echo(json_encode($test));
// I get the following JSON object:
// {"hello":"world","age":30,"arr":{"a":"b","c":"d"},"obj":{"c1":"member variable 1","c2":8080}}
Is there a way to instantiate a new PHP object in a similar manner to those in jQuery? I'm talking about assigning a variable number of arguments when creating the object. For example, I know I could do something like:
...
//in my Class
__contruct($name, $height, $eye_colour, $car, $password) {
...
}
$p1 = new person("bob", "5'9", "Blue", "toyota", "password");
But I'd like to set only some of them maybe. So something like:
$p1 = new person({
name: "bob",
eyes: "blue"});
Which is more along the lines of how it is done in jQuery and other frameworks. Is this built in to PHP? Is there a way to do it? Or a reason I should avoid it?
the best method to do this is using an array:
class Sample
{
private $first = "default";
private $second = "default";
private $third = "default";
function __construct($params = array())
{
foreach($params as $key => $value)
{
if(isset($this->$key))
{
$this->$key = $value; //Update
}
}
}
}
And then construct with an array
$data = array(
'first' => "hello"
//Etc
);
$Object = new Sample($data);
class foo {
function __construct($args) {
foreach($args as $k => $v) $this->$k = $v;
echo $this->name;
}
}
new foo(array(
'name' => 'John'
));
The closest I could think of.
If you want to be more fancy and just want to allow certain keys, you can use __set() (only on php 5)
var $allowedKeys = array('name', 'age', 'hobby');
public function __set($k, $v) {
if(in_array($k, $this->allowedKeys)) {
$this->$k = $v;
}
}
get args won't work as PHP will see only one argument being passed.
public __contruct($options) {
$options = json_decode( $options );
....
// list of properties with ternary operator to set default values if not in $options
....
}
have a looksee at json_decode()
The closest I can think of is to use array() and extract().
...
//in your Class
__contruct($options = array()) {
// default values
$password = 'password';
$name = 'Untitled 1';
$eyes = '#353433';
// extract the options
extract ($options);
// stuff
...
}
And when creating it.
$p1 = new person(array(
'name' => "bob",
'eyes' => "blue"
));
Background
Assume I have the following nested variable in PHP.
$data = Array(
Array('lname' => 'Simpson','fname' => 'Homer','age' => '35','motto' => '_blank_'),
Array('lname' => 'Simpson','fname' => 'Marge','age' => '34','motto' => '_blank_'),
Array('lname' => 'Flintstone','fname' => 'Fred','age' => '33','motto' => '_blank_'),
Array('lname' => 'Flintstone','fname' => 'Wilma','age' => '29','motto' => '_blank_')
);
Assume also the standard methods for accessing specific values:
print($data[0]['fname']); // Homer
print($data[1]['age']); // 34
Question
Is there an existing library or framework that would allow me to easily
acess specific values declaratively, without using foreach loops?
$test = $data->get_record_by_fname['Homer']
print $test['age'] //35
If you really wanted to overkill everything, you could try an approach using magical methods!
class Simpsons
{
protected $_data = array();
public function __construct(array $data)
{
$this->_data = array_map(function ($i) { return (object)$i; }, $data);
}
public function __call($method, $args)
{
if (count($args) == 0)
return NULL;
foreach ($this->_data as $row)
{
if (property_exists($row, $method) && $row->$method == $args[0])
{
return $row;
}
}
return NULL;
}
}
Usage:
$p = new Simpsons($data); // Stored in the format provided
var_dump($p->fname('Homer')); // Gets the record with fname = Homer
Is there a particular reason you don't want to use foreach loops? If it's merely for conciseness, you could just declare the function yourself, it's fairly trivial:
function get_record($set, $field, $value) {
foreach($set as $key => $val) {
if($val[$field] === $value) return $set[$key];
}
return NULL;
}
Then your example would become:
$test = get_record($data, 'fname', 'Homer');
print $test['age']; //35
class SomeClass{
// Stores the Array of Data
public $data;
// Sets up the object. Only accepts arrays
public function __construct(array $data)
{
$this->data = $data;
}
// Gets a record based on the key/value pair
public function getByKey($key, $value)
{
foreach($this->data as $array)
{
if(is_array($array)
{
if(array_key_exists($key, $array) && $array[$key] == $value)
{
return $array;
}
}
}
}
}
$array = array( 1 => array("Test" => "Hello"));
$obj = new SomeClass($array);
$record = $obj->getByKey('Test', 'Hello');
This lets you get a record based on what a key/value pair inside the array is. Note, the type hinting in the constructor is PHP 5.3(?)
BTW, No, there is no way to escape the foreach as even internal PHP functions (anything beginning with array_) uses a foreach or some other type of loop. However, if you encapsulate the loop into a class, you don't have to think about it.