Можете ли вы динамически создавать свойства экземпляра в PHP? - PullRequest
58 голосов
/ 06 мая 2009

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

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

Ответы [ 12 ]

61 голосов
/ 06 мая 2009

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

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) делает то же самое для вызовов метода объекта. Очень полезно в особых случаях. Но в большинстве случаев вы получите:

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

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

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

23 голосов
/ 06 мая 2009

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

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

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

Выходы:

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

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

7 голосов
/ 13 июня 2012

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

<?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 голосов
/ 06 мая 2009

Да, вы можете.

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

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

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

7 голосов
/ 06 мая 2009

Вы можете использовать переменную экземпляра в качестве держателя для произвольных значений, а затем использовать магический метод __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;
    }
}
3 голосов
/ 30 июня 2010

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


/**
 * 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. Извлечение данных из объекта аналогично, например, использовать $ reflProp-> setValue ($ entity, $ value); P.P.S. Эта функция в значительной степени вдохновлена ​​ Doctrine2 ORM , которая потрясающая!

2 голосов
/ 31 марта 2011
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
1 голос
/ 16 июля 2010

Расширение stdClass.

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

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

1 голос
/ 06 мая 2009

Вы можете:

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

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

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

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

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

0 голосов
/ 16 марта 2017

После прочтения @Udo 's ответ . Я пришел к следующему шаблону, который не переполняет экземпляр класса какими-либо элементами, которые есть в аргументе массива вашего конструктора, но все же позволяет печатать меньше и легко добавлять новые свойства в класс.

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'
]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...