Related
I am using php 7.4.9 and have a class which reads information from a file. These informations should be all the time availabe from outside the class and it also should possible to modify that array, so that this class can write back these information on request.
I have looked for a while but could not fined a useful solution.
I got the functions working, but the array loose the values from call to call.
Edit 2020/12/12
This is the uses structure of my code
<?php
.......
function show(){
$id3 = ID3::create();
$mp3 = &ID3::$mp3Array;
if($mode == "manual"){
if($file == ""){
return "";
}
$fName = $dir . "/" . $file;
$id3->open($fName);
.......
}else if($mode == "save"){
$fName = $dir . "/" . $file;
$id3->save($fName);
return "Save done!";
}
} // end of show
class ID3{
public static $mp3Array = array();
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
function open($fName){
$mp3 = self::$mp3Array;
. // $mp3 will be filled
.........
}
function save($fName) {
$mp3 = &ID3::§mp3Array;
error_log("TagSave: ".var_export($mp3, true),0); // is always empty
foreach($mp3 as $key => $value){
........
}
}
} //end of class>
?>
If I try to save the modified array, it is always empty, if show is called again!
I have also implemented the #Logifire proposal 'create'. I got a valid pointer but the arrayis still empty.
Maybe I should point out, that it is web page. The html code sends information (form) back to the php program.
I figured out, that use of global $id3 = NULL; does not work, because the php grogramm will be always called and set the variable again to NULL each time.
I have also implemented the following code on the beginning
<?php
error_log("PHP call",0);
$id3count = 0;
if(array_key_exists("Test_id3",$GLOBALS)){
error_log("GLOBALS[Test_id3] exist!",0);
}else{
error_log("GLOBALS[Test_id3] does not exist!",0);
$GLOBALS['Test_id3'] = "NEW";
}
The $GLOBAL['Test_id3'] never exist, if the programm will be called!
I got the functions working, but the array loose the values from call to call.
As I understand you, your setup is not a long running app, you can not keep state between requests (calls).
But if you are aware of that, the issue may be you have a new instance of the class each time you call it within the same request flow, you may use a singleton if this is the case. I suggest using accessors in your class.
class MyDataList {
private array $my_array = [];
private function __construct()
{
}
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
public function setArray(array $new_array): void {
$this->my_array = $new_array;
// open, write, close file..
}
public function getArray(): array {
return $this->my_array;
}
}
$my_data_list = MyDataList::create();
Based on your edited question (2020/12/12), I extended the example code:
class MyDataList {
private array $my_array = [];
private $file_path = '';
private function __construct()
{
}
public static function create(string $file_path): self {
static $object;
if ($object === null) {
$object = new self();
$stringified = file_get_contents($file_path) ?: '';
$array = json_decode($stringified, true) ?: [];
$object->file_path = $file_path;
$object->my_array = $array;
}
return $object;
}
public function setArray(array $new_array): void {
$this->my_array = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_path, $stringified);
}
public function getArray(): array {
return $this->my_array;
}
}
$my_data_list = MyDataList::create('/path/to/file');
Note: Be aware, you need to apply error handling
Comment answers:
Is the filepath connected to the array?
Well, you will write your data as JSON to a file each time you "modify" the array via the setArray()
Does it means, that the array is stored into a file and read out each time I try to connect again?
For each request you call create() it will instantiate the internal state of the array based on the stored data in the file. ATM. The file_get_contents call may have been wrapped and only called if the $object was not instantiated. (Now updated in the example)
So I have to call setArray($array); to save the data. I was looking for a soluting to keep the data without an management to save and read the array. Is this not possible with PHP?
Maybe you want to use a session variable to store your data? But it is individual per user and not long lived data - Link: https://www.php.net/manual/en/reserved.variables.session.php
In a standard PHP setup you can not have data/state between requests, but there are solution like Swoole which makes PHP a long running app: https://www.php.net/manual/en/book.swoole.php
I need a possibility to modify the array directly.
Is it a reference to the array you want? https://3v4l.org/OsBC6
class MyDataList {
private array $my_array = [];
private function __construct()
{
}
public static function create(): self {
static $object;
$object = $object ?? new self();
return $object;
}
public function setArray(array &$new_array): void {
$this->my_array = &$new_array;
}
public function getArray(): array {
return $this->my_array;
}
}
There is no easy way to do with PHP!
Finally I use the proposal from Logifire, but had to modified it to fullfill my requirements.
I needed more than 1 array.
One array can ibclude binary data values, which json can't handle. So I have to use base64 for the binary data values.
Here my code:
public array $mp3Array = array();
public array $findArray = array();
private $file_dir = "";
public static function create(string $fileDir): self {
static $object;
if ($object === null) {
$object = new self();
$stringified1 = file_get_contents($fileDir."/mp3Array.obj") ?: '';
$array1 = json_decode($stringified1, true) ?: [];
$stringified2 = file_get_contents($fileDir."/findArray.obj") ?: '';
$array2 = json_decode($stringified2, true) ?: [];
$object->file_dir = $fileDir;
$object->mp3Array = $object->arrayDecode($array1);
$object->findArray = $array2;
}
return $object;
}
private function arrayEncode($arr){
$tmp = [];
foreach($arr as $key => $val){
if(is_array($val)){
$tmp[$key] = $this->arrayEncode($val);
}else if ($key == "data"){
$tmp[$key] = base64_encode($val);
}else{
$tmp[$key] = $val;
}
}
return $tmp;
}
private function arrayDecode($arr){
$tmp = [];
foreach($arr as $key => $val){
if(is_array($val)){
$tmp[$key] = $this->arrayDecode($val);
}else if ($key == "data"){
$tmp[$key] = base64_decode($val);
}else{
$tmp[$key] = $val;
}
}
return $tmp;
}
public function setMp3(array $new_array): void {
$this->mp3Array = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_dir."/mp3Array.obj", $stringified);
}
public function saveMp3(): void {
$base64 = $this->arrayEncode($this->mp3Array);
$stringified = json_encode($base64);
file_put_contents($this->file_dir."/mp3Array.obj", $stringified);
}
public function setFind(array $new_array): void {
$this->findArray = $new_array;
$stringified = json_encode($new_array);
file_put_contents($this->file_dir."/findArray.obj", $stringified);
}
public function saveFind(): void {
$stringified = json_encode( $this->findArray);
file_put_contents($this->file_dir."/findArray.obj", $stringified);
}
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
How would I alter the below code so that it returns itself as an array. E.g using $this
public function toArray($data) {
if (is_array($data) || is_object($data)) {
$result = array();
foreach ($data as $key => $value){
$result[$key] = $this->toArray($value);
}
return $result;
}
return $data;
}
So that instead of passing the $data it converts all the private properties from $this into an array.
Something like this? get_object_vars()
$data = get_object_vars ($this);
also you can do like this(typecast):
$data = (array)$this;
Those two are a little different
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
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));