你可以在PHP中dynamic创build实例属性吗?

有什么办法来dynamic创build所有的实例属性? 例如,我希望能够在构造函数中生成所有属性,并且在类实例化后仍然可以访问它们: $object->property 。 请注意,我想单独访问属性,而不是使用数组; 这里是我想要的一个例子:

 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; 

更具体地说,当我处理具有大量属性的类时,我希望能够select数据库中的所有列(代表属性)并从中创build实例属性。 每个列值应该存储在一个单独的实例属性中。

有点。 有一些神奇的方法可以让你钩住自己的代码,在运行时实现类的行为:

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

这是dynamicgetter和setter方法的一个例子,它允许您在访问对象属性时执行行为。 例如

 print(new foo()->someProperty); 

在这种情况下将打印“dynamic! 你也可以赋值给一个任意命名的属性,在这种情况下__set()方法被静默地调用。 __call($ name,$ params)方法对于对象方法调用是一样的。 在特殊情况下非常有用。 但是大多数时候,你会得到:

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

…因为大多数情况下,只需将数组的内容转储到相应命名的类字段中,或者至less在执行path中的非常明确的位置。 所以,除非你真的需要dynamic行为,否则用最后一个例子来填充数据。

这被称为重载http://php.net/manual/en/language.oop5.overloading.php

这取决于你想要的。 你可以dynamic修改吗? 不是真的。 但是,您可以dynamic创build对象属性,就像在该类的一个特定实例中一样? 是。

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

输出:

dynamic

所以名为“bar”的对象属性是在构造函数中dynamic创build的。

是的你可以。

 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; 

产量

 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 

此代码将在构造函数中设置dynamic属性,然后可以使用$ this->列来访问它。 使用__get和__set魔术方法处理未在类中定义的属性也是一种很好的做法。 更多的信息可以在这里find。

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

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

您可以使用一个实例variables作为任意值的持有者,然后使用__get魔术方法将它们检索为常规属性:

 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; } } 

为什么每个例子都如此复杂?

 <?php namespace example; error_reporting(E_ALL | E_STRICT); class Foo { // class completely empty } $testcase = new Foo(); $testcase->example = 'Dynamic property'; echo $testcase->example; 

这里是简单的函数来填充对象成员,而不需要公开类成员。 它也为您自己的用法留下构造函数,创build对象的新实例而不调用构造函数! 所以,你的域对象不依赖于数据库!


 /** * 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从对象中检索数据是相似的,例如使用$ reflProp-> setValue($ entity,$ value); PPS这个function很受Doctrine2 ORM的启发,非常棒!

 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 

您可以:

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

将它的被调用对象的属性foo设置为bar

你也可以使用函数:

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

这也将foo (不是FOO )设置为bar

扩展stdClass。

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

我希望这是你所需要的。

这是处理这种快速发展的真正复杂的方法。 我喜欢答案和魔术方法,但在我看来最好是使用像CodeSmith这样的代码生成器。

我做了连接到数据库的模板,读取所有列和它们的数据types,并相应地生成整个类。

这样我有错误免费(没有错别字)可读的代码。 如果你的数据库模型再次运行生成器…它适用于我。

如果你真的必须这样做,最好的方法是重载一个ArrayObject,它允许保持迭代支持(foreach),仍然会遍历所有的属性。

我注意到,你说“不使用数组”,我只是想向你保证,虽然技术上一个数组正在后台使用,你永远不会看到它。 您可以通过 – > properyname或foreach($ name = $ value中的$ class)来访问所有的属性。

这里是我昨天正在做的一个样本,注意这也是很强的types。 因此,如果您尝试提供“string”,那么标记为“整数”的属性将会引发错误。

你当然可以删除。

还有一个AddProperty()成员函数,虽然在示例中没有演示它。 这将允许您稍后添加属性。

示例用法:

  $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); } } 

读完@Udo的回答 。 我想出了以下模式,它不会使用构造函数数组参数中的任何项膨胀一个类实例,但仍允许input较less的内容,并轻松地向该类添加新的属性。

 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; } } } } 

然后你可以传递数组,如:

 [ 'host' => 'localhost', 'driver' => 'mysql', 'username' => 'myuser', 'password' => '1234', 'charset' => 'utf8', 'collation' => 'utf8_unicode_ci', 'db' => 'key not used in receiving class' ]