你可以在PHP中动态创建实例属性吗?

时间:2009-05-06 14:17:40

标签: php design-patterns oop class

有没有办法动态创建所有实例属性?例如,我希望能够在构造函数中生成所有属性,并且在实例化类之后仍然能够访问它们:$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 个答案:

答案 0 :(得分:61)

排序。有一些神奇的方法可以让你在运行时挂起你自己的代码来实现类行为:

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

答案 1 :(得分:23)

这完全取决于你想要的。你能动态修改吗?并不是的。但是,您是否可以动态创建对象属性,就像在该类的一个特定实例中一样?是。

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

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

输出:

  

动态

因此,在构造函数中动态创建了名为“bar”的对象属性。

答案 2 :(得分:7)

您可以使用实例变量充当任意值的持有者,然后使用__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 :(得分:7)

是的,你可以。

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-&gt;列进行访问。使用__get和__set魔术方法来处理未在类中定义的属性也是一种好习惯。更多信息可以在这里找到。

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

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

答案 4 :(得分:7)

为什么每个例子都这么复杂?

<?php namespace example;

error_reporting(E_ALL | E_STRICT); 

class Foo
{
    // class completely empty
}

$testcase = new Foo();
$testcase->example = 'Dynamic property';
echo $testcase->example;

答案 5 :(得分:3)

这是一个简单的功能,可以在不将类成员公开的情况下填充对象成员。 它还为您自己的用法留下了构造函数,在不调用构造函数的情况下创建了对象的新实例!所以,你的域对象不依赖于数据库!


/**
 * 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-&gt; setValue($ entity,$ value); P.P.S.这个功能深受Doctrine2 ORM的启发,非常棒!

答案 6 :(得分:2)

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

答案 7 :(得分:1)

你可以:

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

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

您还可以使用功能:

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

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

答案 8 :(得分:1)

扩展stdClass。

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

我希望这是你需要的。

答案 9 :(得分:0)

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

我已经制作了连接数据库的模板,读取所有列及其数据类型并相应地生成整个类。

这样我就没有错误(没有拼写错误)可读代码。如果您的数据库模型更改再次运行生成器...它适用于我。

答案 10 :(得分:0)

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

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

这是我昨天工作的一个样本,注意这也是强烈的类型。因此,如果您尝试提供“字符串”,则标记为“整数”的属性将引发错误。

你当然可以删除它。

还有一个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);
            }
    }

答案 11 :(得分:0)

阅读@Udo&#39; answer后。我已经提出了以下模式,它不会使用构造函数数组参数中的任何项目膨胀类实例,但仍然允许您输入较少的内容并轻松地向类添加新属性。 / p>

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'
]