Related
My problem is that I have an object shared through two classes that contains an array inside of it and along the script, someone will request some of the classes the value and a foreach loop will change such value and I want this change to affect every reference of the value.
class bar {
protected $obj;
function __construct(&$obj) {
$this->obj = $obj;
}
public function output() {
print_r($this->obj->value);
}
}
class foo {
protected $obj;
function __construct(&$obj) {
$this->obj = $obj;
}
public function val() {
$result = array();
foreach($this->obj->value as $it){
$result[] = $it;
}
return $result;
}
}
// Shared Object
$obj = new stdClass();
// Default value
$obj->value = array('teste', 'banana', 'maca');
// Class 1
$bar = new bar($obj);
// Class 2
$foo = new foo($obj);
// Someone requests from class 2 the values and changes it
$new = $foo->val();
$new[] = 'abc';
// Class 1 outputs the value
$bar->output(); // this will print the default value. I want this to also have 'abc' value.
The main problem, is that you are building a new array at foo:val, you must return the original object to be modified.
I suggest use ArrayObject, have the same behavior of array but is a object, then always is passed by reference.
<?php
class MyArrayObject extends ArrayObject {
public function replace(Array $array)
{
foreach($this->getArrayCopy() as $key => $value) {
$this->offsetUnset($key);
}
foreach ($array as $key => $value) {
$this[$key] = $value;
}
}
}
class bar {
protected $obj;
function __construct(MyArrayObject $obj) {
$this->obj = $obj;
}
public function output() {
print_r($this->obj);
}
}
class foo {
protected $obj;
function __construct(MyArrayObject $obj) {
$this->obj = $obj;
}
public function val() {
$result = array('foo', 'bar');
$this->obj->replace($result);
return $this->obj;
}
}
// Shared Object
$obj = new MyArrayObject(array('teste', 'banana', 'maca'));
// Class 1
$bar = new bar($obj);
// Class 2
$foo = new foo($obj);
// Someone requests from class 2 the values and changes it
$new = $foo->val();
$new[] = 'abc';
// Class 1 outputs the value
$bar->output(); // this will print the default value. I want this to also
var_dump($obj);
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));
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];
}
Is there any way to create all instance properties dynamically? For example, I would like to be able to generate all attributes in the constructor and still be able to access them after the class is instantiated like this: $object->property. Note that I want to access the properties separately, and not using an array; here's an example of what I don't want:
class Thing {
public $properties;
function __construct(array $props=array()) {
$this->properties = $props;
}
}
$foo = new Thing(array('bar' => 'baz');
# I don't want to have to do this:
$foo->properties['bar'];
# I want to do this:
//$foo->bar;
To be more specific, when I'm dealing with classes that have a large number of properties, I would like to be able to select all columns in a database (which represent the properties) and create instance properties from them. Each column value should be stored in a separate instance property.
Sort of. There are magic methods that allow you to hook your own code up to implement class behavior at runtime:
class foo {
public function __get($name) {
return('dynamic!');
}
public function __set($name, $value) {
$this->internalData[$name] = $value;
}
}
That's an example for dynamic getter and setter methods, it allows you to execute behavior whenever an object property is accessed. For example
print(new foo()->someProperty);
would print, in this case, "dynamic!" and you could also assign a value to an arbitrarily named property in which case the __set() method is silently invoked. The __call($name, $params) method does the same for object method calls. Very useful in special cases. But most of the time, you'll get by with:
class foo {
public function __construct() {
foreach(getSomeDataArray() as $k => $value)
$this->{$k} = $value;
}
}
...because mostly, all you need is to dump the content of an array into correspondingly named class fields once, or at least at very explicit points in the execution path. So, unless you really need dynamic behavior, use that last example to fill your objects with data.
This is called overloading
http://php.net/manual/en/language.oop5.overloading.php
It depends exactly what you want. Can you modify the class dynamically? Not really. But can you create object properties dynamically, as in one particular instance of that class? Yes.
class Test
{
public function __construct($x)
{
$this->{$x} = "dynamic";
}
}
$a = new Test("bar");
print $a->bar;
Outputs:
dynamic
So an object property named "bar" was created dynamically in the constructor.
Yes, you can.
class test
{
public function __construct()
{
$arr = array
(
'column1',
'column2',
'column3'
);
foreach ($arr as $key => $value)
{
$this->$value = '';
}
}
public function __set($key, $value)
{
$this->$key = $value;
}
public function __get($value)
{
return 'This is __get magic '.$value;
}
}
$test = new test;
// Results from our constructor test.
var_dump($test);
// Using __set
$test->new = 'variable';
var_dump($test);
// Using __get
print $test->hello;
Output
object(test)#1 (3) {
["column1"]=>
string(0) ""
["column2"]=>
string(0) ""
["column3"]=>
string(0) ""
}
object(test)#1 (4) {
["column1"]=>
string(0) ""
["column2"]=>
string(0) ""
["column3"]=>
string(0) ""
["new"]=>
string(8) "variable"
}
This is __get magic hello
This code will set dynamic properties in the constructor which can then be accessed with $this->column. It's also good practice to use the __get and __set magic methods to deal with properties that are not defined within the class. More information them can be found here.
http://www.tuxradar.com/practicalphp/6/14/2
http://www.tuxradar.com/practicalphp/6/14/3
You can use an instance variable to act as a holder for arbitrary values and then use the __get magic method to retrieve them as regular properties:
class My_Class
{
private $_properties = array();
public function __construct(Array $hash)
{
$this->_properties = $hash;
}
public function __get($name)
{
if (array_key_exists($name, $this->_properties)) {
return $this->_properties[$name];
}
return null;
}
}
Why is every example so complicated?
<?php namespace example;
error_reporting(E_ALL | E_STRICT);
class Foo
{
// class completely empty
}
$testcase = new Foo();
$testcase->example = 'Dynamic property';
echo $testcase->example;
Here is simple function to populate object members without making class members public.
It also leaves constructor for your own usage, creating new instance of object without invoking constructor! So, your domain object doesn't depend on database!
/**
* Create new instance of a specified class and populate it with given data.
*
* #param string $className
* #param array $data e.g. array(columnName => value, ..)
* #param array $mappings Map column name to class field name, e.g. array(columnName => fieldName)
* #return object Populated instance of $className
*/
function createEntity($className, array $data, $mappings = array())
{
$reflClass = new ReflectionClass($className);
// Creates a new instance of a given class, without invoking the constructor.
$entity = unserialize(sprintf('O:%d:"%s":0:{}', strlen($className), $className));
foreach ($data as $column => $value)
{
// translate column name to an entity field name
$field = isset($mappings[$column]) ? $mappings[$column] : $column;
if ($reflClass->hasProperty($field))
{
$reflProp = $reflClass->getProperty($field);
$reflProp->setAccessible(true);
$reflProp->setValue($entity, $value);
}
}
return $entity;
}
/******** And here is example ********/
/**
* Your domain class without any database specific code!
*/
class Employee
{
// Class members are not accessible for outside world
protected $id;
protected $name;
protected $email;
// Constructor will not be called by createEntity, it yours!
public function __construct($name, $email)
{
$this->name = $name;
$this->emai = $email;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function getEmail()
{
return $this->email;
}
}
$row = array('employee_id' => '1', 'name' => 'John Galt', 'email' => 'john.galt#whoisjohngalt.com');
$mappings = array('employee_id' => 'id'); // Employee has id field, so we add translation for it
$john = createEntity('Employee', $row, $mappings);
print $john->getName(); // John Galt
print $john->getEmail(); // john.galt#whoisjohngalt.com
//...
P.S. Retrieving data from object is similar, e.g. use $reflProp->setValue($entity, $value);
P.P.S. This function is heavily inspired by Doctrine2 ORM which is awesome!
class DataStore // Automatically extends stdClass
{
public function __construct($Data) // $Data can be array or stdClass
{
foreach($Data AS $key => $value)
{
$this->$key = $value;
}
}
}
$arr = array('year_start' => 1995, 'year_end' => 2003);
$ds = new DataStore($arr);
$gap = $ds->year_end - $ds->year_start;
echo "Year gap = " . $gap; // Outputs 8
You can:
$variable = 'foo';
$this->$variable = 'bar';
Would set the attribute foo of the object it's called on to bar.
You can also use functions:
$this->{strtolower('FOO')} = 'bar';
This would also set foo (not FOO) to bar.
Extend stdClass.
class MyClass extends stdClass
{
public function __construct()
{
$this->prop=1;
}
}
I hope this is what you need.
This is really complicated way to handle this kind of rapid development. I like answers and magic methods but in my opinion it is better to use code generators like CodeSmith.
I have made template that connect to database, read all columns and their data types and generate whole class accordingly.
This way I have error free (no typos) readable code. And if your database model changes run generator again... it works for me.
If you really really must do it, the best way is to overload an ArrayObject, that allows to maintain iteration support (foreach) that will still loop through all your properties.
I note that you said "without using an array", and I just want to assure you that that while technically an array is being used in the background, you NEVER HAVE TO SEE IT. You access all properties via ->properyname or foreach ($class in $name => $value).
Here is a sample I was working on yesterday, note this is also STRONGLY TYPED. So properties that are marked "integer" will throw an error if you try and supply a "string".
You can remove that of course.
There is also an AddProperty() member function, although it is not demonstrated in the example. That will allow you to add properties later.
Sample usage:
$Action = new StronglyTypedDynamicObject("Action",
new StrongProperty("Player", "ActionPlayer"), // ActionPlayer
new StrongProperty("pos", "integer"),
new StrongProperty("type", "integer"),
new StrongProperty("amount", "double"),
new StrongProperty("toCall", "double"));
$ActionPlayer = new StronglyTypedDynamicObject("ActionPlayer",
new StrongProperty("Seat", "integer"),
new StrongProperty("BankRoll", "double"),
new StrongProperty("Name", "string"));
$ActionPlayer->Seat = 1;
$ActionPlayer->Name = "Doctor Phil";
$Action->pos = 2;
$Action->type = 1;
$Action->amount = 7.0;
$Action->Player = $ActionPlayer;
$newAction = $Action->factory();
$newAction->pos = 4;
print_r($Action);
print_r($newAction);
class StrongProperty {
var $value;
var $type;
function __construct($name, $type) {
$this->name = $name;
$this->type = $type;
}
}
class StronglyTypedDynamicObject extends ModifiedStrictArrayObject {
static $basic_types = array(
"boolean",
"integer",
"double",
"string",
"array",
"object",
"resource",
);
var $properties = array(
"__objectName" => "string"
);
function __construct($objectName /*, [ new StrongProperty("name", "string"), [ new StrongProperty("name", "string"), [ ... ]]] */) {
$this->__objectName = $objectName;
$args = func_get_args();
array_shift($args);
foreach ($args as $arg) {
if ($arg instanceof StrongProperty) {
$this->AddProperty($arg->name, $arg->type);
} else {
throw new Exception("Invalid Argument");
}
}
}
function factory() {
$new = clone $this;
foreach ($new as $key => $value) {
if ($key != "__objectName") {
unset($new[$key]);
}
}
// $new->__objectName = $this->__objectName;
return $new;
}
function AddProperty($name, $type) {
$this->properties[$name] = $type;
return;
if (in_array($short_type, self::$basic_types)) {
$this->properties[$name] = $type;
} else {
throw new Exception("Invalid Type: $type");
}
}
public function __set($name, $value) {
self::sdprintf("%s(%s)\n", __FUNCTION__, $name);
$this->check($name, $value);
$this->offsetSet($name, $value);
}
public function __get($name) {
self::sdprintf("%s(%s)\n", __FUNCTION__, $name);
$this->check($name);
return $this->offsetGet($name);
}
protected function check($name, $value = "r4nd0m") {
if (!array_key_exists($name, $this->properties)) {
throw new Exception("Attempt to access non-existent property '$name'");
}
$value__objectName = "";
if ($value != "r4nd0m") {
if ($value instanceof StronglyTypedDynamicObject) {
$value__objectName = $value->__objectName;
}
if (gettype($value) != $this->properties[$name] && $value__objectName != $this->properties[$name]) {
throw new Exception("Attempt to set {$name} ({$this->properties[$name]}) with type " . gettype($value) . ".$value__objectName");
}
}
}
}
class ModifiedStrictArrayObject extends ArrayObject {
static $debugLevel = 0;
/* Some example properties */
static public function StaticDebug($message) {
if (static::$debugLevel > 1) {
fprintf(STDERR, "%s\n", trim($message));
}
}
static public function sdprintf() {
$args = func_get_args();
$string = call_user_func_array("sprintf", $args);
self::StaticDebug("D " . trim($string));
}
protected function check($name) {
if (!array_key_exists($name, $this->properties)) {
throw new Exception("Attempt to access non-existent property '$name'");
}
}
//static public function sget($name, $default = NULL) {
/******/ public function get ($name, $default = NULL) {
self::sdprintf("%s(%s)\n", __FUNCTION__, $name);
$this->check($name);
if (array_key_exists($name, $this->storage)) {
return $this->storage[$name];
}
return $default;
}
public function offsetGet($name) {
self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
$this->check($name);
return call_user_func_array(array(parent, __FUNCTION__), func_get_args());
}
public function offsetSet($name, $value) {
self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
$this->check($name);
return call_user_func_array(array(parent, __FUNCTION__), func_get_args());
}
public function offsetExists($name) {
self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
$this->check($name);
return call_user_func_array(array(parent, __FUNCTION__), func_get_args());
}
public function offsetUnset($name) {
self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
$this->check($name);
return call_user_func_array(array(parent, __FUNCTION__), func_get_args());
}
public function __toString() {
self::sdprintf("%s(%s)\n", __FUNCTION__, $name);
foreach ($this as $key => $value) {
$output .= "$key: $value\n";
}
return $output;
}
function __construct($array = false, $flags = 0, $iterator_class = "ArrayIterator") {
self::sdprintf("%s(%s)\n", __FUNCTION__, implode(",", func_get_args()));
parent::setFlags(parent::ARRAY_AS_PROPS);
}
}
After reading #Udo 's answer. I've come up with the following pattern, that doesn't bloat a class instance with what-ever items that is in your constructor array argument but still let you type less and easily add new properties to the class.
class DBModelConfig
{
public $host;
public $username;
public $password;
public $db;
public $port = '3306';
public $charset = 'utf8';
public $collation = 'utf8_unicode_ci';
public function __construct($config)
{
foreach ($config as $key => $value) {
if (property_exists($this, $key)) {
$this->{$key} = $value;
}
}
}
}
Then you can pass arrays like:
[
'host' => 'localhost',
'driver' => 'mysql',
'username' => 'myuser',
'password' => '1234',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'db' => 'key not used in receiving class'
]
I've run into an odd problem and I'm not sure how to fix it. I have several classes that are all PHP implementations of JSON objects. Here' an illustration of the issue
class A
{
protected $a;
public function __construct()
{
$this->a = array( new B, new B );
}
public function __toString()
{
return json_encode( $this->a );
}
}
class B
{
protected $b = array( 'foo' => 'bar' );
public function __toString()
{
return json_encode( $this->b );
}
}
$a = new A();
echo $a;
The output from this is
[{},{}]
When the desired output is
[{"foo":"bar"},{"foo":"bar"}]
The problem is that I was relying on the __toString() hook to do my work for me. But it can't, because the serialize that json_encode() uses won't call __toString(). When it encounters a nested object it simply serializes public properties only.
So, the question then become this: Is there a way I can develop a managed interface to JSON classes that both lets me use setters and getters for properties, but also allows me to get the JSON serialization behavior I desire?
If that's not clear, here's an example of an implementation that won't work, since the __set() hook is only called for the initial assignment
class a
{
public function __set( $prop, $value )
{
echo __METHOD__, PHP_EOL;
$this->$prop = $value;
}
public function __toString()
{
return json_encode( $this );
}
}
$a = new a;
$a->foo = 'bar';
$a->foo = 'baz';
echo $a;
I suppose I could also do something like this
class a
{
public $foo;
public function setFoo( $value )
{
$this->foo = $value;
}
public function __toString()
{
return json_encode( $this );
}
}
$a = new a;
$a->setFoo( 'bar' );
echo $a;
But then I would have to rely on the diligence of the other developers to use the setters - I can't force adherence programmtically with this solution.
---> EDIT <---
Now with a test of Rob Elsner's response
<?php
class a implements IteratorAggregate
{
public $foo = 'bar';
protected $bar = 'baz';
public function getIterator()
{
echo __METHOD__;
}
}
echo json_encode( new a );
When you execute this, you can see that the getIterator() method isn't ever invoked.
A late answers but might be useful for others with the same problem.
In PHP < 5.4.0 json_encode doesn't call any method from the object. That is valid for getIterator, __serialize, etc...
In PHP > v5.4.0, however, a new interface was introduced, called JsonSerializable.
It basically controls the behaviour of the object when json_encode is called on that object.
Example:
class A implements JsonSerializable
{
protected $a = array();
public function __construct()
{
$this->a = array( new B, new B );
}
public function jsonSerialize()
{
return $this->a;
}
}
class B implements JsonSerializable
{
protected $b = array( 'foo' => 'bar' );
public function jsonSerialize()
{
return $this->b;
}
}
$foo = new A();
$json = json_encode($foo);
var_dump($json);
Outputs:
string(29) "[{"foo":"bar"},{"foo":"bar"}]"
Isn't your answer in the PHP docs for json_encode?
For anyone who has run into the problem of private properties not being added, you can simply implement the IteratorAggregate interface with the getIterator() method. Add the properties you want to be included in the output into an array in the getIterator() method and return it.
In PHP > v5.4.0 you can implement the interface called JsonSerializable as described in the answer by Tivie.
For those of us using PHP < 5.4.0 you can use a solution which employs get_object_vars() from within the object itself and then feeds those to json_encode(). That is what I have done in the following example, using the __toString() method, so that when I cast the object as a string, I get a JSON encoded representation.
Also included is an implementation of the IteratorAggregate interface, with its getIterator() method, so that we can iterate over the object properties as if they were an array.
<?php
class TestObject implements IteratorAggregate {
public $public = "foo";
protected $protected = "bar";
private $private = 1;
private $privateList = array("foo", "bar", "baz" => TRUE);
/**
* Retrieve the object as a JSON serialized string
*
* #return string
*/
public function __toString() {
$properties = $this->getAllProperties();
$json = json_encode(
$properties,
JSON_FORCE_OBJECT | JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT
);
return $json;
}
/**
* Retrieve an external iterator
*
* #link http://php.net/manual/en/iteratoraggregate.getiterator.php
* #return \Traversable
* An instance of an object implementing \Traversable
*/
public function getIterator() {
$properties = $this->getAllProperties();
$iterator = new \ArrayIterator($properties);
return $iterator;
}
/**
* Get all the properties of the object
*
* #return array
*/
private function getAllProperties() {
$all_properties = get_object_vars($this);
$properties = array();
while (list ($full_name, $value) = each($all_properties)) {
$full_name_components = explode("\0", $full_name);
$property_name = array_pop($full_name_components);
if ($property_name && isset($value)) $properties[$property_name] = $value;
}
return $properties;
}
}
$o = new TestObject();
print "JSON STRING". PHP_EOL;
print "------" . PHP_EOL;
print strval($o) . PHP_EOL;
print PHP_EOL;
print "ITERATE PROPERTIES" . PHP_EOL;
print "-------" . PHP_EOL;
foreach ($o as $key => $val) print "$key -> $val" . PHP_EOL;
print PHP_EOL;
?>
This code produces the following output:
JSON STRING
------
{"public":"foo","protected":"bar","private":1,"privateList":{"0":"foo","1":"bar","baz":true}}
ITERATE PROPERTIES
-------
public -> foo
protected -> bar
private -> 1
privateList -> Array
Even if your protected variable was public instead of protected, you won't have the desired input since this will output the entire object like this:
[{"b":{"foo":"bar"}},{"b":{"foo":"bar"}}]
Instead of:
[{"foo":"bar"},{"foo":"bar"}]
It will most likely defeat your purpose, but i'm more inclined to convert to json in the original class with a default getter and calling for the values directly
class B
{
protected $b = array( 'foo' => 'bar' );
public function __get($name)
{
return json_encode( $this->$name );
}
}
Then you could do with them whatever you desire, even nesting the values in an additional array like your class A does, but using json_decode.. it still feels somewhat dirty, but works.
class A
{
protected $a;
public function __construct()
{
$b1 = new B;
$b2 = new B;
$this->a = array( json_decode($b1->b), json_decode($b2->b) );
}
public function __toString()
{
return json_encode( $this->a );
}
}
In the documentation there are some responses to this problem (even if i don't like most of them, serializing + stripping the properties makes me feel dirty).
You're right the __toString() for the class B is not being called, because there is no reason to. So to call it, you can use a cast
class A
{
protected $a;
public function __construct()
{
$this->a = array( (string)new B, (string)new B );
}
public function __toString()
{
return json_encode( $this->a );
}
}
Note: the (string) cast before the new B's ... this will call the _toString() method of the B class, but it won't get you what you want, because you will run into the classic "double encoding" problems, because the array is encoded in the B class _toString() method, and it will be encoded again in the A class _toString() method.
So there is a choice of decoding the result after the cast, ie:
$this->a = array( json_decode((string)new B), json_decode((string)new B) );
or you're going to need to get the array, by creating a toArray() method in the B class that returns the straight array. Which will add some code to the line above because you can't use a PHP constructor directly (you can't do a new B()->toArray(); ) So you could have something like:
$b1 = new B;
$b2 = new B;
$this->a = array( $b1->toArray(), $b2->toArray() );
A function to prepare data for json_encode that uses __toString() if available
This is useful if you don't have control over the classes / objects.
/**
* Prepares data for encoding by replacing any object in the input with output of its get_data()
* or __toString() method if available.
*
* #param mixed $data .
* #return mixed
*/
function prepare_for_encoding($data) {
if (is_array($data)) {
foreach ($data as $i => $value) {
$data[$i] = prepare_for_encoding($data[$i]);
}
} elseif (is_object($data)) {
if (method_exists($data, 'get_data')) {
$data = $data->get_data();
} elseif (method_exists($data, '__toString')) {
$data = $data->__toString();
// Sometimes toString() may return encoded json data - see if it is decodeable so it can
// be encoded together with the entire structure.
$decoded = json_decode($data, true);
if ($decoded !== null) {
$data = $decoded;
}
}
}
return $data;
}
Usage:
class Foo {
public $a = 1;
private $b= 2;
public __toString() {
return json_encode(array(
'a' => $this->a,
'b' => $this->b,
}
}
$foo = new Foo();
json_encode(prepare_for_encoding($foo));
json_encode(prepare_for_encoding(array('foo' => $foo));