Есть ли способ создать все свойства экземпляра динамически? Например, я хотел бы иметь возможность генерировать все атрибуты в конструкторе и по-прежнему иметь доступ к ним после создания экземпляра класса следующим образом: $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;
Чтобы быть более конкретным, когда я имею дело с классами, которые имеют большое количество свойств, я хотел бы иметь возможность выбирать все столбцы в базе данных (которые представляют свойства) и создавать из них свойства экземпляра. Каждое значение столбца должно храниться в отдельном экземпляре.
Вроде. Существуют магические методы, позволяющие вам подключить свой собственный код для реализации поведения класса во время выполнения:
class foo { public function __get($name) { return('dynamic!'); } public function __set($name, $value) { $this->internalData[$name] = $value; } }
Это пример для динамических методов getter и setter, который позволяет выполнять поведение при доступе к объекту. Например
print(new foo()->someProperty);
напечатал бы в этом случае «динамический»! и вы также можете присвоить значение произвольно именованному свойству, и в этом случае метод __set () будет принудительно вызываться. Метод __call ($ name, $ params) делает то же самое для вызовов метода объекта. Очень полезно в особых случаях. Но большую часть времени вы будете:
class foo { public function __construct() { foreach(getSomeDataArray() as $k => $value) $this->{$k} = $value; } }
… потому что, в основном, все, что вам нужно, – это сбросить содержимое массива в соответствующие имена полей классов один раз или, по крайней мере, в очень явных точках пути выполнения. Итак, если вам действительно не нужно динамическое поведение, используйте этот последний пример, чтобы заполнить ваши объекты данными.
Это называется перегрузкой http://php.net/manual/en/language.oop5.overloading.php
Это зависит именно от того, что вы хотите. Можете ли вы изменить класс динамически? На самом деле, нет. Но можете ли вы создавать свойства объекта динамически, как в одном конкретном экземпляре этого класса? Да.
class Test { public function __construct($x) { $this->{$x} = "dynamic"; } } $a = new Test("bar"); print $a->bar;
Выходы:
динамический
Таким образом, свойство свойства с именем «bar» было создано динамически в конструкторе.
Да, ты можешь.
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
Этот код будет устанавливать динамические свойства в конструкторе, к которому затем можно получить доступ с помощью столбца $ this->. Также полезно использовать методы магии __get и __set для обработки свойств, которые не определены в классе. Более подробную информацию можно найти здесь.
Вы можете использовать переменную экземпляра, чтобы действовать как держатель для произвольных значений, а затем использовать метод __get magic для извлечения их в качестве обычных свойств:
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;
Вот простая функция для заполнения членов объекта, не открывая класс. Он также оставляет конструктор для вашего собственного использования, создавая новый экземпляр объекта без вызова конструктора! Таким образом, ваш объект домена не зависит от базы данных!
/** * 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 Эта функция сильно вдохновлена 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
для bar
.
Вы также можете использовать функции:
$this->{strtolower('FOO')} = 'bar';
Это также foo
бы foo
(not FOO
).
Расширьте stdClass.
class MyClass extends stdClass { public function __construct() { $this->prop=1; } }
Надеюсь, это то, что вам нужно.
Это действительно сложный способ справиться с таким быстрым развитием. Мне нравятся ответы и магические методы, но, на мой взгляд, лучше использовать генераторы кода, такие как CodeSmith.
Я создал шаблон, который подключается к базе данных, читает все столбцы и их типы данных и генерирует весь класс соответственно.
Таким образом, я имею свободный от ошибок (без опечаток) читаемый код. И если ваша модель базы данных меняет генератор снова … это работает для меня.
Если вам действительно нужно это сделать, лучший способ – перегрузить ArrayObject, который позволяет поддерживать поддержку итерации (foreach), которая все равно будет прокручивать все ваши свойства.
Я отмечаю, что вы сказали «без использования массива», и я просто хочу заверить вас, что при техническом использовании массива в фоновом режиме вы НИКОГДА НЕ ДОЛЖНЫ ВИДЕТЬ ЭТО. Вы получаете доступ ко всем свойствам через -> properyname или foreach ($ class в $ name => $ value).
Вот образец, над которым я работал вчера, отметим, что это также СИЛЬНО ТИП. Таким образом, свойства, отмеченные как «integer», будут вызывать ошибку, если вы попытаетесь поставить «строку».
Конечно, вы можете удалить это.
Существует также функция-член 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); } }
с$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. Я придумал следующий шаблон, который не раздувает экземпляр класса с любыми элементами, которые находятся в аргументе массива конструктора, но все же позволяет вам вводить меньше и легко добавлять новые свойства в класс.
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' ]