Можете ли вы динамически создавать свойства экземпляра в PHP?


Есть ли способ динамически создавать все свойства экземпляра? Например, я хотел бы иметь возможность генерировать все атрибуты в конструкторе и по-прежнему иметь доступ к ним после создания экземпляра класса следующим образом: $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;

Чтобы быть более конкретным, когда я имею дело с классами, которые имеют большое количество свойств, я хотел бы иметь возможность выбирать все столбцы в базе данных (которые представляют свойства) и создавать из них свойства экземпляра. Каждое значение столбца должно храниться в отдельном свойстве экземпляра.

Author: outis, 2009-05-06

12 answers

Вроде того. Существуют волшебные методы, которые позволяют вам подключить свой собственный код для реализации поведения класса во время выполнения:

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

Это пример динамических методов получения и установки, он позволяет выполнять поведение при каждом обращении к свойству объекта. Например

print(new foo()->someProperty);

В этом случае напечатал бы "динамический!", и вы также могли бы присвоить значение свойству с произвольным именем, и в этом случае метод __set() вызывается автоматически. Метод __call ($name, $params) __вызов ($name, $params) делает то же самое для вызовов метода объекта. Очень полезно в особых случаях. Но в большинстве случаев вы справитесь с:

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

...потому что в основном все, что вам нужно, это один раз выгрузить содержимое массива в поля класса с соответствующими именами или, по крайней мере, в очень явных точках пути выполнения. Поэтому, если вам действительно не нужно динамическое поведение, используйте этот последний пример для заполнения ваших объектов данными.

Это называется перегрузкой http://php.net/manual/en/language.oop5.overloading.php

 60
Author: Udo, 2012-01-04 22:12:58

Это зависит от того, чего именно вы хотите. Можете ли вы динамически изменять класс ? Не совсем. Но можете ли вы создать свойства объекта динамически, как в одном конкретном экземпляре этого класса? Да.

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

$a = new Test("bar");
print $a->bar;

Выходные данные:

Динамический

Таким образом, свойство объекта с именем "bar" было создано динамически в конструкторе.

 23
Author: Chad Birch, 2009-05-06 14:26:17

Да, ты можешь.

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 для работы со свойствами, которые не определены в классе. Более подробную информацию о них можно найти здесь.

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

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

 7
Author: The Pixel Developer, 2009-05-06 14:31:08

Вы можете использовать переменную экземпляра в качестве держателя для произвольных значений, а затем использовать метод __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;
    }
}
 7
Author: Carlton Gibson, 2012-05-16 09:18:19

Почему каждый пример такой сложный?

<?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
Author: srcspider, 2014-07-19 12:10:15

Вот простая функция для заполнения членов объекта, не делая членов класса общедоступными. Он также оставляет конструктор для вашего собственного использования, создавая новый экземпляр объекта без вызова конструктора! Таким образом, ваш доменный объект не зависит от базы данных!


/**
 * 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' => '[email protected]');
$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(); // [email protected]
//...

P.S. Извлечение данных из объекта аналогично, например, используйте $REFLPROP->setValue($сущность, $значение); P.P.S. Эта функция сильно вдохновлена Доктриной2 ORM, которая потрясающая!

 3
Author: Sergiy Sokolenko, 2015-03-31 13:36:33
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
Author: Anthony, 2011-03-31 07:33:03

Вы можете:

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

Установит атрибут foo объекта, к которому он вызывается bar.

Вы также можете использовать функции:

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

Это также установило бы foo (не FOO) в bar.

 1
Author: Koraktor, 2009-05-06 14:25:42

Расширить стандартный класс.

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

Я надеюсь, что это то, что вам нужно.

 1
Author: Anthony, 2011-09-09 11:21:25

Это действительно сложный способ справиться с таким быстрым развитием. Мне нравятся ответы и магические методы, но, на мой взгляд, лучше использовать генераторы кода, такие как CodeSmith.

Я создал шаблон, который подключается к базе данных, считывает все столбцы и их типы данных и соответственно генерирует весь класс.

Таким образом, у меня есть безошибочный (без опечаток) читаемый код. И если ваша модель базы данных изменится, запустите генератор снова... это работает на меня.

 0
Author: zidane, 2009-05-08 22:12:08

Если вам действительно действительно нужно это сделать, лучший способ - перегрузить объект ArrayObject, который позволяет поддерживать поддержку итераций (foreach), которая все равно будет проходить через все ваши свойства.

Я отмечаю, что вы сказали "без использования массива", и я просто хочу заверить вас, что, хотя технически массив используется в фоновом режиме, вам НИКОГДА НЕ ПРИДЕТСЯ ЕГО ВИДЕТЬ. Вы получаете доступ ко всем свойствам через ->собственное имя или для каждого ($класс в $имя = > $значение).

Вот пример Я работал над вчера, обратите внимание, что это также СТРОГО ТИПИЗИРОВАНО. Таким образом, свойства, помеченные как "целое число", выдадут ошибку, если вы попытаетесь ввести "строку".

Вы, конечно, можете это удалить.

Существует также функция-член 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);
            }
    }
 0
Author: Orwellophile, 2012-01-11 05:35:10

После прочтения ответа @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'
]
 0
Author: dotnetCarpenter, 2017-05-23 11:54:01