Best strategy to convert array output to an object? - php

I'm taking array output from a command-line program and parsing it into a PHP object. Consider this example of a very simple way to do this:
$output = explode("\n", shell_exec(myProg));
$obj = new MyObject();
$offset_field1 = 0;
$offset_field2 = 1;
$obj->Field1 = $output[$offset_field1];
$obj->Field2 = $output[$offset_field2];
This is a bit cumbersome, especially when the number of fields increases. Is there a better design pattern or method to accomplish the same feat in a less heavy-handed manner?

Why not put the assignment code in the object?
class MyObject
{
public function __construct(array $data)
{
$this->Field1 = $data['keyname1'];
$this->Field2 = $data['keyname2'];
...
}
}
or use the get magic method.
class MyObject
{
protected $data;
public function __construct(array $data)
{
$this->data = $data;
}
public function __get($key)
{
$map = array('Field1' => 1, 'Feild2' => 2, ...);
if (isset($map[$key])) {
return $this->data[$map[$key]];
}
}
}

I guess this should work:
$output = explode("\n", shell_exec(myProg));
$obj = new MyObject();
foreach ($output as $key => $value)
{
$obj->{'Field' . ($key + 1)} = $value;
}

As it seems you cannot guess the field name from the outpout of your programm, you will have to define it somewhere.
$key_map = array('field_name1', 'field_name2', 'etc');
$obj = new MyObject();
foreach(explode("\n", shell_exec(myProg)) as $k => $v)
{
if(isset($key_map($k))
$obj->$key_map[$k] = $v;
}

Related

PHP change default iterator using foreach for an object

With an object like this:
class test {
var $propa = 'a';
var $propb = 'b';
var $propc = 'c';
var $propd = array(1,2,3,4);
}
How do I iterate with test::$propd using foreach WITHOUT direct reference? eq:
$t = new test;
foreach ($t as $k => $v){
echo 'propd['.$k.']='.$v.', ';
}
// propd[0]=1, propd[1]=2, propd[2]=3, ...
Is there some stuff involving implement ArrayAccess?
Thx!
I would recommend separating functionality, and setting your properties as private.
Example:
class Test {
private $propertyA = 'a';
private $propertyB = 'b';
public function getProperties()
{
return [
'propertyA' => $this->propertyA,
'propertyB' => $this->propertyB
];
}
}
However, if the property values are to change then do getters and setters for the properties.
With the above code you can do:
<?php
$test = new Test;
foreach ($test->getProperties() as $key => $value) {
echo $key . ' = ' . $value;
}
?>
You can also limit what properties you want to output with this method.

Convert String To Object-Variable-Name?

i want to turn a simple string like that "response->dict->words" into a variable name that i can actually work with. I will give an example now. Lets assume the value of $response->dict->words is 67.
Example:
$var = "response->dict->words"
echo $$var; /* PRINT THE VALUE 67 FROM $response->dict->words*/
As you may notice i put an extra dollar sign before the $var because this should work, but it doesn't.
Can anyone help me with this?
class ClassOne {
public function test() {
return 'test';
}
}
class ClassTwo {
public function test2() {
return 'test2';
}
}
$one = new ClassOne();
$two = new ClassTwo();
$objects = array('one', 'two');
$methods = array('test', 'test2');
for ($i = 0; $i < count($objects); $i++) {
echo ${$objects[$i]}->$methods[$i]();
}
You can store classnames or method names as strings and later use them, or even store variable names, like here ${$objects} (variable variables), but you cannot store whole logic.
To evaluate whole logic, you have to use eval(), which is most probably bad idea
$var = "response->dict->words"
eval("?> <?php echo $".$var.";");
You can split your string and make the call as below:
class Response {
public $dict;
public function __construct() {
$this->dict = new stdClass();
$this->dict->words = 'words test';
}
}
$response = new Response();
$var = 'response->dict->words';
$elements = explode('->', $var);
echo ${$elements[0]}->$elements[1]->$elements[2];
Results into words test
Or, if you don't know the level of nesting the object call, you can perform the call in a foreach loop. When the loop exits, the last call will be available after it:
class Response {
public $dict;
public function __construct() {
$this->dict = new stdClass();
$this->dict->words = new stdClass();
$this->dict->words->final = 'test chained string';
}
}
$response = new Response();
$var = 'response->dict->words->final';
$elements = explode('->', $var);
foreach ($elements as $key => $element) {
if ($key == 0) {
$call = ${$element};
continue;
}
$call = $call->$element;
}
echo $call;
Results into: test chained string
There is a better way, why don't you cache the variable like
$var = $response->dict->words;

How to set property value in instance time?

In C# is usual set object property in instance time
Like this code
People p = new People(){Name = "John", Age = 18};
Have any solution like this in PHP?
You can do like this
class People() {
public $name = "";
public $age = "";
public function __construct($name,$age) {
$this->name = $name;
$this->age = $age;
}
}
$obj = new People("John", 18);
For more information you can visit: http://php.net/manual/en/language.oop5.php
At the moment, other than the normal constructor methods, no, you can't do that in PHP. However, there are RFC's here and here for something similar to this, so maybe in a few version's time it will be available.
It would depend on the construction of the class, but you can do it.
<?php
class Person {
public function __construct(array $params = array()) {
foreach ($params as $property => $value) {
$this->{$property} = $value;
}
}
}
$p = new Person(array('Name' => 'Bob', 'Age' => 18));
echo $p->Age; //18

json_decode to custom class

Is it possible to decode a json string to an object other than stdClass?
Not automatically. But you can do it the old fashioned route.
$data = json_decode($json, true);
$class = new Whatever();
foreach ($data as $key => $value) $class->{$key} = $value;
Or alternatively, you could make that more automatic:
class Whatever {
public function set($data) {
foreach ($data AS $key => $value) $this->{$key} = $value;
}
}
$class = new Whatever();
$class->set($data);
Edit: getting a little fancier:
class JSONObject {
public function __construct($json = false) {
if ($json) $this->set(json_decode($json, true));
}
public function set($data) {
foreach ($data AS $key => $value) {
if (is_array($value)) {
$sub = new JSONObject;
$sub->set($value);
$value = $sub;
}
$this->{$key} = $value;
}
}
}
// These next steps aren't necessary. I'm just prepping test data.
$data = array(
"this" => "that",
"what" => "who",
"how" => "dy",
"multi" => array(
"more" => "stuff"
)
);
$jsonString = json_encode($data);
// Here's the sweetness.
$class = new JSONObject($jsonString);
print_r($class);
We built JsonMapper to map JSON objects onto our own model classes automatically. It works fine with nested/child objects.
It only relies on docblock type information for mapping, which most class properties have anyway:
<?php
$mapper = new JsonMapper();
$contactObject = $mapper->map(
json_decode(file_get_contents('http://example.org/contact.json')),
new Contact()
);
?>
You can do it - it's a kludge but totally possible. We had to do when we started storing things in couchbase.
$stdobj = json_decode($json_encoded_myClassInstance); //JSON to stdClass
$temp = serialize($stdobj); //stdClass to serialized
// Now we reach in and change the class of the serialized object
$temp = preg_replace('#^O:8:"stdClass":#','O:7:"MyClass":',$temp);
// Unserialize and walk away like nothing happend
$myClassInstance = unserialize($temp); // Presto a php Class
In our benchmarks this was way faster than trying to iterate through all the class variables.
Caveat: Won't work for nested objects other than stdClass
Edit: keep in mind the data source, it's strongly recommended that you don't do this withe untrusted data from users without a very carful analysis of the risks.
You could use Johannes Schmitt's Serializer library.
$serializer = JMS\Serializer\SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, 'MyNamespace\MyObject', 'json');
In the latest version of the JMS serializer the syntax is:
$serializer = SerializerBuilder::create()->build();
$object = $serializer->deserialize($jsonData, MyObject::class, 'json');
I'm surprised no one mentioned this, yet.
Use the Symfony Serializer component: https://symfony.com/doc/current/components/serializer.html
Serializing from Object to JSON:
use App\Model\Person;
$person = new Person();
$person->setName('foo');
$person->setAge(99);
$person->setSportsperson(false);
$jsonContent = $serializer->serialize($person, 'json');
// $jsonContent contains {"name":"foo","age":99,"sportsperson":false,"createdAt":null}
echo $jsonContent; // or return it in a Response
Deserializing from JSON to Object: (this example uses XML just to demonstrate the flexibility of formats)
use App\Model\Person;
$data = <<<EOF
<person>
<name>foo</name>
<age>99</age>
<sportsperson>false</sportsperson>
</person>
EOF;
$person = $serializer->deserialize($data, Person::class, 'xml');
You can do it in below way ..
<?php
class CatalogProduct
{
public $product_id;
public $sku;
public $name;
public $set;
public $type;
public $category_ids;
public $website_ids;
function __construct(array $data)
{
foreach($data as $key => $val)
{
if(property_exists(__CLASS__,$key))
{
$this->$key = $val;
}
}
}
}
?>
For more details visit
create-custom-class-in-php-from-json-or-array
You can make a wrapper for your object and make the wrapper look like it is the object itself. And it will work with multilevel objects.
<?php
class Obj
{
public $slave;
public function __get($key) {
return property_exists ( $this->slave , $key ) ? $this->slave->{$key} : null;
}
public function __construct(stdClass $slave)
{
$this->slave = $slave;
}
}
$std = json_decode('{"s3":{"s2":{"s1":777}}}');
$o = new Obj($std);
echo $o->s3->s2->s1; // you will have 777
No, this is not possible as of PHP 5.5.1.
The only thing possible is to have json_decode return associate arrays instead of the StdClass objects.
Use Reflection:
function json_decode_object(string $json, string $class)
{
$reflection = new ReflectionClass($class);
$instance = $reflection->newInstanceWithoutConstructor();
$json = json_decode($json, true);
$properties = $reflection->getProperties();
foreach ($properties as $key => $property) {
$property->setAccessible(true);
$property->setValue($instance, $json[$property->getName()]);
}
return $instance;
}
As Gordon says is not possible. But if you are looking for a way to obtain a string that can be decoded as an instance of a give class you can use serialize and unserialize instead.
class Foo
{
protected $bar = 'Hello World';
function getBar() {
return $this->bar;
}
}
$string = serialize(new Foo);
$foo = unserialize($string);
echo $foo->getBar();
I once created an abstract base class for this purpose. Let's call it JsonConvertible. It should serialize and deserialize the public members. This is possible using Reflection and late static binding.
abstract class JsonConvertible {
static function fromJson($json) {
$result = new static();
$objJson = json_decode($json);
$class = new \ReflectionClass($result);
$publicProps = $class->getProperties(\ReflectionProperty::IS_PUBLIC);
foreach ($publicProps as $prop) {
$propName = $prop->name;
if (isset($objJson->$propName) {
$prop->setValue($result, $objJson->$propName);
}
else {
$prop->setValue($result, null);
}
}
return $result;
}
function toJson() {
return json_encode($this);
}
}
class MyClass extends JsonConvertible {
public $name;
public $whatever;
}
$mine = MyClass::fromJson('{"name": "My Name", "whatever": "Whatever"}');
echo $mine->toJson();
Just from memory, so probably not flawless. You will also have to exclude static properties and may give derived classes the chance to make some properties ignored when serialized to/from json. I hope you get the idea, nonetheless.
JSON is a simple protocol to transfer data between various programming languages (and it's also a subset of JavaScript) which supports just certain types: numbers, strings, arrays/lists, objects/dicts. Objects are just key=value maps and Arrays are ordered lists.
So there is no way to express custom objects in a generic way. The solution is defining a structure where your program(s) will know that it's a custom object.
Here's an example:
{ "cls": "MyClass", fields: { "a": 123, "foo": "bar" } }
This could be used to create an instance of MyClass and set the fields a and foo to 123 and "bar".
I went ahead and implemented John Petit's answer, as a function(gist):
function json_decode_to(string $json, string $class = stdClass::class, int $depth = 512, int $options = 0)
{
$stdObj = json_decode($json, false, $depth, $options);
if ($class === stdClass::class) return $stdObj;
$count = strlen($class);
$temp = serialize($stdObj);
$temp = preg_replace("#^O:8:\"stdClass\":#", "O:$count:\"$class\":", $temp);
return unserialize($temp);
}
This worked perfectly for my use case. However Yevgeniy Afanasyev's response seems equally promising to me. It could be possible to have your class have an extra "constructor", like so:
public static function withJson(string $json) {
$instance = new static();
// Do your thing
return $instance;
}
This is also inspired by this answer.
EDIT: I have been using karriereat/json-decoder for some time now, and I have had absolutely no trouble with it. It is lightweight and very easily extensible. Here's an example of a binding I wrote to deserialize JSON into a Carbon/CarbonImmutable object.
All this here inspired me to a generic function:
function loadJSON($Obj, $json)
{
$dcod = json_decode($json);
$prop = get_object_vars ( $dcod );
foreach($prop as $key => $lock)
{
if(property_exists ( $Obj , $key ))
{
if(is_object($dcod->$key))
{
loadJSON($Obj->$key, json_encode($dcod->$key));
}
else
{
$Obj->$key = $dcod->$key;
}
}
}
}
to be called in class declaration:
class Bar{public $bar = " Boss";}
class Bas
{
public $ber ;
public $bas=" Boven";
public function __construct()
{$this->ber = new Bar;}
}
class Baz
{
public $bes ;
public $baz=" Baaz";
public function __construct()
{$this->bes = new Bas;}
}
$Bazjson = '{"bes":{"ber":{"bar":"Baas"}}}';
$Bazobj = new Baz;
loadJSON($Bazobj, $Bazjson);
var_dump($Bazobj);
This worked for me, especially for if you don't have setters or named properties in the target class
function cast($jsonstring, $class)
{
//$class is a string like 'User'
$json= json_decode($jsonstring,true); //array
$reflection = new ReflectionClass($class);
$instance = $reflection->newInstanceWithoutConstructor();
$keys = array_keys($json);
foreach ($keys as $key => $property) {
$instance->{$property} =$json[$property];
}
// print_r($instance);
return $instance;
}
Not directly, but if the class has a constructor with parameter names that match the keys in the JSON object, you can simply decode the JSON into an associative array and pass it to the constructor via the '...' (argument unpacking) operator:
<?php
class MyClass {
public function __construct(
public int $id,
public string $name,
public array $attributes,
){}
}
$json = '{"name":"foo","id":42,"attributes":{"color":"red"}}';
$object = new MyClass(...json_decode($json, true));
print_r($object);
Output:
MyClass Object
(
[id] => 42
[name] => foo
[attributes] => Array
(
[color] => red
)
)
However, in practice, there is often some additional mapping to do, especially sub-objects that need to be recursively decoded too. So usually it is better to have a static fromArray function in each class that pre-processes the json-decoded array before passing the result to the constructor:
class Part {
public function __construct(public float $weight){}
public static function fromArray(array $data): self {
return new self(...$data);
}
}
class System {
public function __construct(
public string $name,
public Part $mainPart,
public array $otherParts,
){}
public static function fromArray(array $data): self {
$data['mainPart'] = Part::fromArray($data['mainPart']);
$data['otherParts'] = array_map(Part::fromArray(...), $data['otherParts']); // php 8.1
return new self(...$data);
}
}
$json = '{"name":"foo","mainPart":{"weight":2},"otherParts":[{"weight":1}, {"weight":0.5}]}';
$object = System::fromArray(json_decode($json, true));

Is an 'invoke'-style callback possible in PHP5?

PHP functions such as 'array_map' take a callback, which can be a simple function or a class or object method:
$array2 = array_map('myFunc', $array);
or
$array2 = array_map(array($object, 'myMethod'), $array);
But is there a syntax to pass a method which is bound within the iteration to the current object (like 'invoke' in Prototype.js)? So that the following could be used:
$array2 = array_map('myMethod', $array);
with the effect of
foreach($array as $obj) $array2[] = $obj->myMethod();
Obviously I can use this form, or I can write a wrapper function to make the call, and even do that inline. But since 'myMethod' is already a method it seems to be going round the houses to have to do one of these.
Not currently. When php 5.3 comes out, you could use the following syntax:
$array2 = array_map(function($obj) { return $obj->myMethod(); }, $array);
function obj_array_map($method, $arr_of_objects) {
$out = array();
$args = array_slice(func_get_args(), 2);
foreach ($arr_of_objects as $key => $obj) {
$out[$key] = call_user_func_array(Array($obj, $method), $args);
}
return $out;
}
// this code
$a = Array($obj1, $obj2);
obj_array_map('method', $a, 1, 2, 3);
// results in the calls:
$obj1->method(1, 2, 3);
$obj2->method(1, 2, 3);
Basically, no. There is no special syntax to make this any easier.
I can think of a fancier way of doing this in PHP 5.3, seeing as there's always more than one way to do things in PHP, but I'd say it wouldn't necessarily be better than your foreach example:
$x = array_reduce(
$array_of_objects,
function($val, $obj) { $val = array_merge($val, $obj->myMethod()); return $val; },
array()
);
Just go with your foreach :)
<?php
// $obj->$method(); works, where $method is a string containing the name of the
// real method
function array_map_obj($method, $array) {
$out = array();
foreach ($array as $key => $obj)
$out[$key] = $obj->$method();
return $out;
}
// seems to work ...
class Foo {
private $y = 0;
public function __construct($x) {
$this->y = $x;
}
public function bar() {
return $this->y*2;
}
}
$objs = array();
for ($i=0; $i<20; $i++)
$objs[] = new Foo($i);
$res = array_map_obj('bar', $objs);
var_dump($res);
?>
voila!
This is a bit of a silly answer, but you could subclass ArrayObject and use that instead of a normal array:
<?php
class ArrayTest extends ArrayObject {
public function invokeMethod() {
$result = array();
$args = func_get_args();
$method = array_shift($args);
foreach ($this as $obj) {
$result[] = call_user_func_array(array($obj, $method), $args);
}
return $result;
}
}
//example class to use
class a {
private $a;
public function __construct($a) {
$this->a = $a;
}
public function multiply($n) {
return $this->a * $n;
}
}
//use ArrayTest instance instead of array
$array = new ArrayTest();
$array[] = new a(1);
$array[] = new a(2);
$array[] = new a(3);
print_r($array->invokeMethod('multiply', 2));
Outputs this:
Array
(
[0] => 2
[1] => 4
[2] => 6
)
I would use create_function() to ... well ... create a temporary function for array_map while waiting for PHP 5.3
$func = create_function('$o', '$o->myMethod();');
array_map($func, $objects);

Categories