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));
Related
I'm trying to JSON encode some objects in PHP, but I'm facing a problem: I want to encode data which is kept by a class private members.
I found this piece of code to encode this object by calling an encode function like:
public function encodeJSON()
{
foreach ($this as $key => $value)
{
$json->$key = $value;
}
return json_encode($json);
}
However, this only works if the object I want to encode does not contain other objects inside, which is the case. How can I do to encode not only the "outer" object, but encode as well any members that are objects too?
The best method to serialize an object with private properties is to implement the \JsonSerializable interface and then implement your own JsonSerialize method to return the data you require to be serialized.
<?php
class Item implements \JsonSerializable
{
private $var;
private $var1;
private $var2;
public function __construct()
{
// ...
}
public function jsonSerialize()
{
$vars = get_object_vars($this);
return $vars;
}
}
json_encode will now serialize your object correctly.
If you're using php 5.4 you can use the JsonSerializable interface: http://www.php.net/manual/en/class.jsonserializable.php
You just implement a jsonSerialize method in your class which returns whatever you want to be encoded.
Then when you pass your object into json_encode, it'll encode the result of jsonSerialize.
Anyway. You need create public method in your class to return all their fields json encoded
public function getJSONEncode() {
return json_encode(get_object_vars($this));
}
I think #Petah's got the best approach, but that way you lose properties that are array or object. So I added a function wich do that recursively:
function json_encode_private($object) {
function extract_props($object) {
$public = [];
$reflection = new ReflectionClass(get_class($object));
foreach ($reflection->getProperties() as $property) {
$property->setAccessible(true);
$value = $property->getValue($object);
$name = $property->getName();
if(is_array($value)) {
$public[$name] = [];
foreach ($value as $item) {
if (is_object($item)) {
$itemArray = extract_props($item);
$public[$name][] = $itemArray;
} else {
$public[$name][] = $item;
}
}
} else if(is_object($value)) {
$public[$name] = extract_props($value);
} else $public[$name] = $value;
}
return $public;
}
return json_encode(extract_props($object));
}
EDIT: Added is_object() check inside the array loop to avoid a get_class() exception in the next extract_props() call when the array elements are not objects, like strings or numbers.
I think this may be a great case for the Usage of Traits
using the below guist I implemented jsonSerializable interface in multiple points of my app while keeping the code manageable
https://gist.github.com/zburgermeiszter/7dc5e65b06bb34a325a0363726fd8e14
trait JsonSerializeTrait
{
function jsonSerialize()
{
$reflect = new \ReflectionClass($this);
$props = $reflect->getProperties(\ReflectionProperty::IS_STATIC | \ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PRIVATE);
$propsIterator = function() use ($props) {
foreach ($props as $prop) {
yield $prop->getName() => $this->{$prop->getName()};
}
};
return iterator_to_array($propsIterator());
}
}
then you just have to do
class YourClass implements JsonSerializable
{
use JsonSerializeTrait;
... normal encapsulated code...
}
public function jsonSerialize()
{
$objectArray = [];
foreach($this as $key => $value) {
$objectArray[$key] = $value;
}
return json_encode($objectArray);
}
I personally think this is a way of doing it. It is similar to Petah's, except It keeps in line with encapsulation well, because the array is populated from the object.
Put this function in either your object or as a trait to be used by your object. To each their own though.
This would print a JSON with all of the properties (public, private and protected) of class foo:
$reflection = new ReflectionClass('Foo');
$properties = $reflection->getdefaultProperties();
echo json_encode($properties);
It would work from any context.
You can only encode an object's private members from within the class. As a side note though, does the json_enocde function not work for you? http://php.net/manual/en/function.json-encode.php
Using reflection you can json_encode private properties, although its not considered best practice:
function json_encode_private($object) {
$public = [];
$reflection = new ReflectionClass($object);
foreach ($reflection->getProperties() as $property) {
$property->setAccessible(true);
$public[$property->getName()] = $property->getValue($object);
}
return json_encode($public);
}
E.g.
class Foo {
public $a = 1;
public $b = 2;
}
class Bar {
private $c = 3;
private $d = 4;
}
var_dump(json_encode(new Foo()));
var_dump(json_encode_private(new Bar()));
Outputs:
string(13) "{"a":1,"b":2}"
string(13) "{"c":3,"d":4}"
http://codepad.viper-7.com/nCcKYW
I would like to use the list() statement in combination with an object.
$tuple = new Tuple();
// ..
list ($guestbook, $entry, $author) = $tuple;
This would work if $tuple was an array with three elements. But its an object.
Is there any way without using a method of Tuple (returning that kind of array) like implementing a fancy native interface I yet don't know?
You can implement the interface ArrayAccess to do so:
class Tuple implements ArrayAccess {
private $arr;
public function __construct($arr) {
$this->arr = $arr;
}
public function offsetExists($offset) {
return array_key_exists($offset, $this->arr);
}
public function offsetGet($offset) {
return $this->arr[$offset];
}
public function offsetSet($offset, $value) {
return $this->arr[$offset] = $value;
}
public function offsetUnset($offset) {
unset($this->arr[$offset]);
}
}
$tuple = new Tuple([1, 2, 3]);
list($am, $stram, $gram) = $tuple;
echo $am;
echo $stram;
echo $gram;
// outputs: 123
See this previous post:
Convert PHP object to associative array
I am assuming (I haven't tested it) you could then do:
$tuple = new Tuple();
list ($guestbook, $entry, $author) = (array) $tuple;
You can do this:
$tumple = new Tumple();
$properties = get_object_vars($tumple);// ["guestbook" => "Feel", "entry" => "Good", "author" => "Inc"];
Here is the way I do:
$aNewObject = new MyObj();
$aNewObject->set_id($row->id);
$aNewObject->set_user_id($row->user_id);
$aNewObject->set_title($row->title);
$aNewObject->set_url($row->url);
$aNewObject->set_description($row->description);
$aNewObject->set_status($row->status);
as you can see, I follow a name convention, which the object and the data base field is 100% match, I think there should have a way to help me to do it lazier, any recommendation?
You can get even lazier by only writing
$aNewObject = new MyObj($row);
and having a constructor that sets the object's properties based on the contents of $row
You could do the setting dynamically by iterating over the fields (if that is meant by lazier):
$fields = array('user_id', ...);
foreach($fields as $field)
{
$setter = "set_{$field}";
$aNewObject->$setter($row->{$field});
}
It depends then where you want to place that code. Either just inline, as part of a function of MyObj (importRow($row)) or in a global helper function that always calls all setters matching object properties.
Within the class constructor:
$aNewObject = new MyObj($row);
class MyObj
{
public function __construct($row = null)
{
...
$this->importRow($row);
}
public function importRow($row = null)
{
if (null === $row)
return;
foreach($row as $field => $value)
{
$setter = "set_{$field}";
$this->$setter($value);
}
}
...
}
To prevent duplicate code across different classes (missing traits support in PHP < 5.4), a global static function or object can do it:
$aNewObject = new MyObj();
new Setter($aNewObject, $row);
# or
$aNewObject = Setter::fill('MyObj', $row);
class Setter
{
private $object;
public function __construct($class, $data)
{
// works on classnames or objects
if (is_string($class))
$object = new $class();
else
$object = $class;
$this->object = $this->import($object, $data);
}
private function import($object, $data)
{
foreach($data as $field => $value)
{
$setter = "set_{$field}";
$object->$setter($value);
}
return $object;
}
public function getObject()
{
return $this->object;
}
public static function fill($class, $data)
{
$self = new __CLASS__($class, $data);
return $self->getObject();
}
}
Lazier, it is not recommended, because it will make it hard to maintain by other programmers.
If you still want to do that, you should do with PHP reflection: http://www.php.net/manual/en/intro.reflection.php
or as #hakre answered.
I'm trying to JSON encode some objects in PHP, but I'm facing a problem: I want to encode data which is kept by a class private members.
I found this piece of code to encode this object by calling an encode function like:
public function encodeJSON()
{
foreach ($this as $key => $value)
{
$json->$key = $value;
}
return json_encode($json);
}
However, this only works if the object I want to encode does not contain other objects inside, which is the case. How can I do to encode not only the "outer" object, but encode as well any members that are objects too?
The best method to serialize an object with private properties is to implement the \JsonSerializable interface and then implement your own JsonSerialize method to return the data you require to be serialized.
<?php
class Item implements \JsonSerializable
{
private $var;
private $var1;
private $var2;
public function __construct()
{
// ...
}
public function jsonSerialize()
{
$vars = get_object_vars($this);
return $vars;
}
}
json_encode will now serialize your object correctly.
If you're using php 5.4 you can use the JsonSerializable interface: http://www.php.net/manual/en/class.jsonserializable.php
You just implement a jsonSerialize method in your class which returns whatever you want to be encoded.
Then when you pass your object into json_encode, it'll encode the result of jsonSerialize.
Anyway. You need create public method in your class to return all their fields json encoded
public function getJSONEncode() {
return json_encode(get_object_vars($this));
}
I think #Petah's got the best approach, but that way you lose properties that are array or object. So I added a function wich do that recursively:
function json_encode_private($object) {
function extract_props($object) {
$public = [];
$reflection = new ReflectionClass(get_class($object));
foreach ($reflection->getProperties() as $property) {
$property->setAccessible(true);
$value = $property->getValue($object);
$name = $property->getName();
if(is_array($value)) {
$public[$name] = [];
foreach ($value as $item) {
if (is_object($item)) {
$itemArray = extract_props($item);
$public[$name][] = $itemArray;
} else {
$public[$name][] = $item;
}
}
} else if(is_object($value)) {
$public[$name] = extract_props($value);
} else $public[$name] = $value;
}
return $public;
}
return json_encode(extract_props($object));
}
EDIT: Added is_object() check inside the array loop to avoid a get_class() exception in the next extract_props() call when the array elements are not objects, like strings or numbers.
I think this may be a great case for the Usage of Traits
using the below guist I implemented jsonSerializable interface in multiple points of my app while keeping the code manageable
https://gist.github.com/zburgermeiszter/7dc5e65b06bb34a325a0363726fd8e14
trait JsonSerializeTrait
{
function jsonSerialize()
{
$reflect = new \ReflectionClass($this);
$props = $reflect->getProperties(\ReflectionProperty::IS_STATIC | \ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED | \ReflectionProperty::IS_PRIVATE);
$propsIterator = function() use ($props) {
foreach ($props as $prop) {
yield $prop->getName() => $this->{$prop->getName()};
}
};
return iterator_to_array($propsIterator());
}
}
then you just have to do
class YourClass implements JsonSerializable
{
use JsonSerializeTrait;
... normal encapsulated code...
}
public function jsonSerialize()
{
$objectArray = [];
foreach($this as $key => $value) {
$objectArray[$key] = $value;
}
return json_encode($objectArray);
}
I personally think this is a way of doing it. It is similar to Petah's, except It keeps in line with encapsulation well, because the array is populated from the object.
Put this function in either your object or as a trait to be used by your object. To each their own though.
This would print a JSON with all of the properties (public, private and protected) of class foo:
$reflection = new ReflectionClass('Foo');
$properties = $reflection->getdefaultProperties();
echo json_encode($properties);
It would work from any context.
You can only encode an object's private members from within the class. As a side note though, does the json_enocde function not work for you? http://php.net/manual/en/function.json-encode.php
Using reflection you can json_encode private properties, although its not considered best practice:
function json_encode_private($object) {
$public = [];
$reflection = new ReflectionClass($object);
foreach ($reflection->getProperties() as $property) {
$property->setAccessible(true);
$public[$property->getName()] = $property->getValue($object);
}
return json_encode($public);
}
E.g.
class Foo {
public $a = 1;
public $b = 2;
}
class Bar {
private $c = 3;
private $d = 4;
}
var_dump(json_encode(new Foo()));
var_dump(json_encode_private(new Bar()));
Outputs:
string(13) "{"a":1,"b":2}"
string(13) "{"c":3,"d":4}"
http://codepad.viper-7.com/nCcKYW
I'm using a third party storage system that only returns me stdClass objects no matter what I feed in for some obscure reason. So I'm curious to know if there is a way to cast/convert an stdClass object into a full fledged object of a given type.
For instance something along the lines of:
//$stdClass is an stdClass instance
$converted = (BusinessClass) $stdClass;
I am just casting the stdClass into an array and feed it to the BusinessClass constructor, but maybe there is a way to restore the initial class that I am not aware of.
Note: I am not interested in 'Change your storage system' type of answers since it is not the point of interest. Please consider it more an academic question on the language capacities.
Cheers
See the manual on Type Juggling on possible casts.
The casts allowed are:
(int), (integer) - cast to integer
(bool), (boolean) - cast to boolean
(float), (double), (real) - cast to float
(string) - cast to string
(array) - cast to array
(object) - cast to object
(unset) - cast to NULL (PHP 5)
You would have to write a Mapper that does the casting from stdClass to another concrete class. Shouldn't be too hard to do.
Or, if you are in a hackish mood, you could adapt the following code:
function arrayToObject(array $array, $className) {
return unserialize(sprintf(
'O:%d:"%s"%s',
strlen($className),
$className,
strstr(serialize($array), ':')
));
}
which pseudocasts an array to an object of a certain class. This works by first serializing the array and then changing the serialized data so that it represents a certain class. The result is unserialized to an instance of this class then. But like I said, it's hackish, so expect side-effects.
For object to object, the code would be
function objectToObject($instance, $className) {
return unserialize(sprintf(
'O:%d:"%s"%s',
strlen($className),
$className,
strstr(strstr(serialize($instance), '"'), ':')
));
}
You can use above function for casting not similar class objects (PHP >= 5.3)
/**
* Class casting
*
* #param string|object $destination
* #param object $sourceObject
* #return object
*/
function cast($destination, $sourceObject)
{
if (is_string($destination)) {
$destination = new $destination();
}
$sourceReflection = new ReflectionObject($sourceObject);
$destinationReflection = new ReflectionObject($destination);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$sourceProperty->setAccessible(true);
$name = $sourceProperty->getName();
$value = $sourceProperty->getValue($sourceObject);
if ($destinationReflection->hasProperty($name)) {
$propDest = $destinationReflection->getProperty($name);
$propDest->setAccessible(true);
$propDest->setValue($destination,$value);
} else {
$destination->$name = $value;
}
}
return $destination;
}
EXAMPLE:
class A
{
private $_x;
}
class B
{
public $_x;
}
$a = new A();
$b = new B();
$x = cast('A',$b);
$x = cast('B',$a);
To move all existing properties of a stdClass to a new object of a specified class name:
/**
* recast stdClass object to an object with type
*
* #param string $className
* #param stdClass $object
* #throws InvalidArgumentException
* #return mixed new, typed object
*/
function recast($className, stdClass &$object)
{
if (!class_exists($className))
throw new InvalidArgumentException(sprintf('Inexistant class %s.', $className));
$new = new $className();
foreach($object as $property => &$value)
{
$new->$property = &$value;
unset($object->$property);
}
unset($value);
$object = (unset) $object;
return $new;
}
Usage:
$array = array('h','n');
$obj=new stdClass;
$obj->action='auth';
$obj->params= &$array;
$obj->authKey=md5('i');
class RestQuery{
public $action;
public $params=array();
public $authKey='';
}
$restQuery = recast('RestQuery', $obj);
var_dump($restQuery, $obj);
Output:
object(RestQuery)#2 (3) {
["action"]=>
string(4) "auth"
["params"]=>
&array(2) {
[0]=>
string(1) "h"
[1]=>
string(1) "n"
}
["authKey"]=>
string(32) "865c0c0b4ab0e063e5caa3387c1a8741"
}
NULL
This is limited because of the new operator as it is unknown which parameters it would need. For your case probably fitting.
I have a very similar problem. Simplified reflection solution worked just fine for me:
public static function cast($destination, \stdClass $source)
{
$sourceReflection = new \ReflectionObject($source);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$name = $sourceProperty->getName();
$destination->{$name} = $source->$name;
}
return $destination;
}
Hope that somebody find this useful
// new instance of stdClass Object
$item = (object) array(
'id' => 1,
'value' => 'test object',
);
// cast the stdClass Object to another type by passing
// the value through constructor
$casted = new ModelFoo($item);
// OR..
// cast the stdObject using the method
$casted = new ModelFoo;
$casted->cast($item);
class Castable
{
public function __construct($object = null)
{
$this->cast($object);
}
public function cast($object)
{
if (is_array($object) || is_object($object)) {
foreach ($object as $key => $value) {
$this->$key = $value;
}
}
}
}
class ModelFoo extends Castable
{
public $id;
public $value;
}
Changed function for deep casting (using recursion)
/**
* Translates type
* #param $destination Object destination
* #param stdClass $source Source
*/
private static function Cast(&$destination, stdClass $source)
{
$sourceReflection = new \ReflectionObject($source);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$name = $sourceProperty->getName();
if (gettype($destination->{$name}) == "object") {
self::Cast($destination->{$name}, $source->$name);
} else {
$destination->{$name} = $source->$name;
}
}
}
consider adding a new method to BusinessClass:
public static function fromStdClass(\stdClass $in): BusinessClass
{
$out = new self();
$reflection_object = new \ReflectionObject($in);
$reflection_properties = $reflection_object->getProperties();
foreach ($reflection_properties as $reflection_property)
{
$name = $reflection_property->getName();
if (property_exists('BusinessClass', $name))
{
$out->{$name} = $in->$name;
}
}
return $out;
}
then you can make a new BusinessClass from $stdClass:
$converted = BusinessClass::fromStdClass($stdClass);
And yet another approach using the decorator pattern and PHPs magic getter & setters:
// A simple StdClass object
$stdclass = new StdClass();
$stdclass->foo = 'bar';
// Decorator base class to inherit from
class Decorator {
protected $object = NULL;
public function __construct($object)
{
$this->object = $object;
}
public function __get($property_name)
{
return $this->object->$property_name;
}
public function __set($property_name, $value)
{
$this->object->$property_name = $value;
}
}
class MyClass extends Decorator {}
$myclass = new MyClass($stdclass)
// Use the decorated object in any type-hinted function/method
function test(MyClass $object) {
echo $object->foo . '<br>';
$object->foo = 'baz';
echo $object->foo;
}
test($myclass);
Yet another approach.
The following is now possible thanks to the recent PHP 7 version.
$theStdClass = (object) [
'a' => 'Alpha',
'b' => 'Bravo',
'c' => 'Charlie',
'd' => 'Delta',
];
$foo = new class($theStdClass) {
public function __construct($data) {
if (!is_array($data)) {
$data = (array) $data;
}
foreach ($data as $prop => $value) {
$this->{$prop} = $value;
}
}
public function word4Letter($letter) {
return $this->{$letter};
}
};
print $foo->word4Letter('a') . PHP_EOL; // Alpha
print $foo->word4Letter('b') . PHP_EOL; // Bravo
print $foo->word4Letter('c') . PHP_EOL; // Charlie
print $foo->word4Letter('d') . PHP_EOL; // Delta
print $foo->word4Letter('e') . PHP_EOL; // PHP Notice: Undefined property
In this example, $foo is being initialized as an anonymous class that takes one array or stdClass as only parameter for the constructor.
Eventually, we loop through the each items contained in the passed object and dynamically assign then to an object's property.
To make this approch event more generic, you can write an interface or a Trait that you will implement in any class where you want to be able to cast an stdClass.
BTW: Converting is highly important if you are serialized, mainly because the de-serialization breaks the type of objects and turns into stdclass, including DateTime objects.
I updated the example of #Jadrovski, now it allows objects and arrays.
example
$stdobj=new StdClass();
$stdobj->field=20;
$obj=new SomeClass();
fixCast($obj,$stdobj);
example array
$stdobjArr=array(new StdClass(),new StdClass());
$obj=array();
$obj[0]=new SomeClass(); // at least the first object should indicates the right class.
fixCast($obj,$stdobj);
code: (its recursive). However, i don't know if its recursive with arrays. May be its missing an extra is_array
public static function fixCast(&$destination,$source)
{
if (is_array($source)) {
$getClass=get_class($destination[0]);
$array=array();
foreach($source as $sourceItem) {
$obj = new $getClass();
fixCast($obj,$sourceItem);
$array[]=$obj;
}
$destination=$array;
} else {
$sourceReflection = new \ReflectionObject($source);
$sourceProperties = $sourceReflection->getProperties();
foreach ($sourceProperties as $sourceProperty) {
$name = $sourceProperty->getName();
if (is_object(#$destination->{$name})) {
fixCast($destination->{$name}, $source->$name);
} else {
$destination->{$name} = $source->$name;
}
}
}
}
Convert it to an array, return the first element of that array, and set the return param to that class. Now you should get the autocomplete for that class as it will regconize it as that class instead of stdclass.
/**
* #return Order
*/
public function test(){
$db = new Database();
$order = array();
$result = $db->getConnection()->query("select * from `order` where productId in (select id from product where name = 'RTX 2070')");
$data = $result->fetch_object("Order"); //returns stdClass
array_push($order, $data);
$db->close();
return $order[0];
}