Can you dynamically create instance properties in PHP?

Is there a way to create all the properties of an instance dynamically? For example, I would like to be able to generate all the attributes in the constructor and still have access to them after creating the class instance as follows: $object->property . Note that I want to access the properties separately and not use an array; here is 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 deal with classes that have a large number of properties, I would like to be able to select all the columns in the database (which represent the properties) and create instance properties from them. Each column value must be stored in a separate instance.

+56
oop php design-patterns class
May 6 '09 at 14:17
source share
12 answers

Grade. There are magic methods that let you hook your own code to implement class behavior at runtime:

 class foo { public function __get($name) { return('dynamic!'); } public function __set($name, $value) { $this->internalData[$name] = $value; } } 

This example for the dynamic getter and setter methods allows you to perform behavior when accessing an object. for example

 print(new foo()->someProperty); 

would print in this case "dynamic!" and you can also assign a value to a randomly named property, in which case the __set () method will be forced to be called. The __call ($ name, $ params) method does the same for object method calls. Very useful in special cases. But most of the time you will pass:

 class foo { public function __construct() { foreach(getSomeDataArray() as $k => $value) $this->{$k} = $value; } } 

... because, basically, all you need to do is dump the contents of the array into the appropriate fields of the classes by default once, or at least at very explicit points in the execution path. So, if you really don't need dynamic behavior, use this last example to populate your objects with data.

This is called overloading http://php.net/manual/en/language.oop5.overloading.php

+60
May 6 '09 at 14:31
source share

It depends on what you want. Can you change the dynamic class ? Not really. But can you create properties of an object dynamically, as in one specific instance of this class? Yes.

 class Test { public function __construct($x) { $this->{$x} = "dynamic"; } } $a = new Test("bar"); print $a->bar; 

Outputs:

dynamic

Thus, a property of the property with the name "bar" was created dynamically in the constructor.

+23
May 6 '09 at 2:26 p.m.
source share

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; } } 
+7
May 6 '09 at 2:29 p.m.
source share

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; 

Exit

 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 using the $ this-> column. It is also useful to use the __get and __set magic methods to handle properties that are not defined in the class. More detailed information can be found here.

http://www.tuxradar.com/practicalphp/6/14/2

http://www.tuxradar.com/practicalphp/6/14/3

+7
May 6 '09 at 14:31
source share

Why is every example so complex?

 <?php namespace example; error_reporting(E_ALL | E_STRICT); class Foo { // class completely empty } $testcase = new Foo(); $testcase->example = 'Dynamic property'; echo $testcase->example; 
+7
Jun 13 '12 at 8:13
source share

Here is a simple function to populate the members of an object without opening a class. It also leaves the constructor for your own use, creating a new instance of the object without calling the constructor! Thus, your domain object is database independent!




 /** * Create new instance of a specified class and populate it with given data. * * @param string $className * @param array $data eg array(columnName => value, ..) * @param array $mappings Map column name to class field name, eg 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 //... 



PS Extracting data from an object is similar, for example. use $ reflProp-> setValue ($ entity, $ value); PPS This feature is heavily inspired by the Doctrine2 ORM , which is amazing!

+3
Jun 30 '10 at 11:40
source share
 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 
+2
Mar 31 '11 at 7:33
source share

You can:

 $variable = 'foo'; $this->$variable = 'bar'; 

Would set the foo attribute of the object it called on to bar .

You can also use functions:

 $this->{strtolower('FOO')} = 'bar'; 

This would also set foo (not foo ) to bar .

+1
May 6 '09 at 2:25
source share

Extend stdClass.

 class MyClass extends stdClass { public function __construct() { $this->prop=1; } } 

Hope this is what you need.

+1
Jul 16 '10 at 10:40
source share

This is a really difficult way to cope with such a rapid development. I like answers and magic methods, but in my opinion it is better to use code generators like CodeSmith.

I created a template that connects to the database, reads all columns and their data types, and generates the entire class, respectively.

So I have error-free (no typos) readable code. And if your database model changes the generator again ... it works for me.

0
May 08 '09 at 22:12
source share

If you really need to do this, the best way is to overload ArrayObject, which allows you to support foreach support, which will still cover all your properties.

I note that you said “without using an array”, and I just want to assure you that when using the array in technical mode in the background, you NEVER SHOULD SEE THIS. You get access to all properties through → properyname or foreach ($ class in $ name => $ value).

Here is a sample that I worked on yesterday, please note that this is also STRONGLY TYPE. Thus, properties marked as "integer" will throw an error if you try to put a "string".

You can remove it, of course.

There is also a member function AddProperty (), although this is not shown in the example. This 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); } } 
0
Jan 11 '12 at 5:35
source share

After reading @Udo answer . I came up with the following template, which does not inflate the class instance with any elements that are in the argument of the constructor array, but still allows you to introduce 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' ] 
0
Mar 16 '17 at 16:15
source share



All Articles