Можно ли динамически создавать свойства экземпляра в 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;


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

1129   12  

12 ответов:

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

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

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

print(new foo()->someProperty);

напечатал бы, в этом случае, "динамический!"и вы также можете присвоить значение произвольно названному свойству, и в этом случае метод __set() вызывается молча. Вызов __($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 magic для работы со свойствами, которые не определены в классе. Более подробную информацию можно найти здесь.

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

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

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

П. С. получение данных из объекта аналогична, например, использовать $reflProp->метод setValue($образование, $значение); P. P. S. Эта функция сильно вдохновлена использует Doctrine2 ОРМ, который является удивительным!

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.

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

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

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

Я отмечаю, что вы сказали "без использования массива", и я просто хочу заверить вас, что это в то время как технически массив используется в фоновом режиме, вы никогда не должны видеть его. Вы получаете доступ ко всем свойствам через ->properyname или foreach ($class in $name => $значение.)

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

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

существует также функция-член 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 ' 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'
]

Comments

    Ничего не найдено.