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}}
Related
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();
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 have this simple function which I pass in an array of strings:
function myfunction( $arg = array() )
{
// do stuff to $arg...
// return a $string;
}
Simple so far, but I need some of the strings in the $arg array to be formatted, while some remain unformatted. I can't figure out how to do it?
Say I run this $arg through myfunction():
echo myfunction( array( 'format me!', 'do not format me!' ) );
My tiny little brain can't figure out how to tell myfunction() that the first value in $arg array needs to have formatting, and it should not format the second value.
I thought about an associative array, but I think that could be the wrong approach because of having identical indexes.
echo myfunction( array( 'format' => 'hi', 'format' => 'bye', 'noformat' => 'foo');
Just looking for a "nudge" in the right direction.
EDIT 1:
Forgot to mention, I can only have one $arg array because I need the keys to be in a specific order.
EDIT 2:
The $arg array can have as many keys as the user wants.
You can do:
function myfunction(array $format, array $noformat) {
...
}
or
function myfunction(array $strings) {
foreach ($strings['format'] as $format) {
// do stuff
}
foreach ($strings['noformat'] as $noformat) {
// do stuff
}
}
with:
myfunction(array(
'format' => array('one', 'two', 'three'),
'noformat' => array('four', 'five', 'six'),
));
If (and only if) the strings are unique you can put them in the key instead of the value:
$strings = array(
'one' => true,
'two' => false,
'three' => false,
'four' => true,
);
myfunction($strings);
with:
function myfunction(array $strings) {
foreach ($strings as $k => $v) {
if ($v) {
// format string
}
}
}
But since you can't have duplicate keys this method falls down if you have repeated strings.
Instead of having myfunction() take an array as an argument, why not just have it take a single element as the argument. Then you can use array_map to process each element of the array.
Sort of like this:
function myfunction($element) {
if( do-formatting) {
//do your formatting stuff
$element = formatted-stuff
}
return $element
}
$arr = array("format me", "don't format me");
//This calls myfunction on each element of the array
//A new array is returned where each element has been replaced
//by the return value from myfunction.
$arr = array_map('myfunction', $arr);
Here is how I would to it.
The implementation of __tostring is not mandatory but is syntax sugar inside the myFunction.
<?php
class MyString{
private $_value;
public $to_format;
public function __construct($str, $to_format = true){
$this->_value = $str;
$this->to_format = $to_format;
}
public function __tostring(){
return $this->_value;
}
}
$args = array( new MyString('format me'), new MyString('Not me!', false) );
This is oddly similar to how table rows are formatted (with a different formatting function for each row). I would provide the data as a single array, then provide formatting information as a second array based on keys. For instance:
function rowFormatter(Array $data, Array $format=array()) {
ob_start();
foreach ($data as $key => $value) {
if (isset($format[$key]))
echo call_user_func($format[$key],$value);
else
echo $value;
}
return ob_get_clean();
}
function makeBold($x) { return '<b>'.$x.'</b>'; }
rowFormatter(array( "Hello", "Mister" ), array( 0 => 'makeBold' ));
I don't know what these strings you are formatting actually represent, so I used generic naming on these classes:
class LineItem
{
var $value;
function __construct($val)
{
$this->value = $val;
}
function __toString()
{
return $this->value;
}
function getFormatted( $formatter )
{
return $this->value;
}
}
class FormattedLineItem extends LineItem
{
function getFormatted( $formatter )
{
return $formatter( $this->value );
}
}
function myfunction( $arr=array() )
{
$fnFormat = function($str) {
return ucwords($str);
};
foreach($arr as $obj)
{
$str = $obj->getFormatted($fnFormat);
echo "$str\n";
}
}
$arr = array(
new FormattedLineItem('format me!'),
new LineItem('do not format me!')
);
myfunction($arr);
The idea is to use separate classes to distinguish between strings.
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.
I want to take an array and use that array's values to populate an object's properties using the array's keynames. Like so:
$a=array('property1' => 1, 'property2' => 2);
$o=new Obj();
$o->populate($a);
class Obj
{
function Populate($array)
{
//??
}
}
After this, I now have:
$o->property1==1
$o->property2==2
How would I go about doing this?
foreach ($a as $key => $value) {
$o->$key = $value;
}
However, the syntax you are using to declare your array is not valid. You need to do something like this:
$a = array('property1' => 1, 'property2' => 2);
If you don't care about the class of the object, you could just do this (giving you an instance of stdClass):
$o = (Object) $a;
Hm. What about having something like
class Obj
{
var properties = array();
function Populate($array)
{
this->properties = $array;
}
}
Then you can say:
$o->properties['property1'] == 1
...