你能否在PHP中动态创建实例属性?

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

更具体地说,当我处理具有大量属性的类时,我希望能够选择数据库中的所有列(表示属性),并从它们创建实例属性。每个列值都应存储在单独的实例属性中。

1
数组和__set和__get方法应该足以满足普通需求。您是否有任何特殊关注点需要坚持“一个属性=一个变量”的解决方案?顺便说一下,封装的原则规定,没有其他对象应该知道变量是真实存在的还是在数组中的值? 甚至是扩展此基类的对象。 - Csaba Kétszeri
http://php.net/manual/en/language.oop5.overloading.php - Keyne Viana
12个回答

62

有点类似。有些魔术方法可以使你的代码在运行时实现类的行为:

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

这是动态获取器和设置器方法的示例,它允许您在访问对象属性时执行行为。例如:

print(new foo()->someProperty);

在这种情况下,会打印"dynamic!",你也可以给任意命名的属性分配一个值,在这种情况下,__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


这正是我需要的,但这些属性会是私有的还是公共的?或者我可以像这样将它们设为私有:private $this->{$k} => $value;? - Brayn
5
据我所知,它们将是公共的,无法在运行时将它们设为私有或受保护。如果想要实现“私有”可见性,您可以声明一个私有数组类型的字段,然后填充它。对于“受保护”的情况也一样。最好尽可能保持简单,所以您可以引入一个名为$this->ds的私有数组,在其中可以通过$this->ds ['fieldname']之类的语句来访问其内部字段。或者,如果您想要更加高级一些,可以实现一个小型类,基本上包装了数组的功能,这样您就可以像$this->myDatasetObject->fieldname一样使用它。 - Udo
1
在PHP5中,您可以重载任何对象,而无需使用__get和__set,但属性始终是公共的。如果您需要保护某些内容,则需要明确声明它们。 - James Socol
2
我相信 ReflectionMethod::setAccessible 可以帮助你改变方法的私有访问。 - B T

23

这取决于你想要什么。你能动态修改吗?不行。但你能动态创建对象属性,例如该类的一个特定实例吗?可以。

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

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

输出:

动态

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


2
这也表明动态属性是公共的。 - Doug Amos

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;
    }
}

我个人喜欢你的解决方案,因为你可以更好地控制变量在范围和其他方面的使用方式,但他刚刚发布了这个。编辑:抱歉,忘记提到:不使用数组来保存属性,每个属性都是单独的“变量”。 - The Pixel Developer
感谢您的评论 - 我认为这是解决问题的自然方式。恐怕我不太明白Brayn在他的编辑中具体意思 - 由于我是新手,没有足够的声望点数来发表评论进行询问。哎... - Carlton Gibson
嗨,我真的很喜欢这个答案。我尝试编辑一下以进行修复,但 Stackoverflow 拒绝了更改,因为它只是一个字符的编辑:return this->_properties[$name]; 应该改为 return $this->_properties[$name]; - Iain

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->column 访问它们。最好使用 __get 和 __set 魔术方法来处理未在类中定义的属性。有关它们的更多信息可以在此处找到。

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

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


6
为什么每个例子都这么复杂?
<?php namespace example;

error_reporting(E_ALL | E_STRICT); 

class Foo
{
    // class completely empty
}

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

@AdiCumpanasu \error_reporting 实际上只是表示“全局命名空间 error_reporting 函数”,它不是转义字符;既然有人提出了这个问题,我发现在 PHP 中编写正确版本比编写“PHP 可以解决它”的版本更令人烦恼(特别是对于那些太习惯非命名空间 PHP 的人造成的困惑),所以修改是可以的。 - srcspider

4
这里有一个简单的函数,用于填充对象成员而不需要将类成员公开。 它还为您自己的使用留下了构造函数,创建新对象实例而不调用构造函数!因此,您的域对象不依赖于数据库!
/**
 * 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
//...

附言:从对象中检索数据类似,例如使用 $reflProp->setValue($entity, $value); 附加附言:这个函数受到了Doctrine2 ORM的启发,它真是太棒了!


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

1
class DataStore {} var_export(is_subclass_of('DataStore', 'StdClass'));false!在 PHP 中,类不会自动扩展 StdClass。 - BenMorel
先生,我确信我在php.net上读到过这个!另外,这段代码会输出“123” . . . [code] class foo { public $a=1; public function __construct() { $this->b = 2; } }$bar = new foo; $bar->c = 3;echo $bar->a.$bar->b.$bar->c;[code] - Anthony
@Anthony 在 PHP 中,类不会自动扩展 stdClass,但是 PHP 允许向现有对象添加动态属性(从版本 8 开始),这可能会在未来发生变化。 - Faizan Akram Dar

1

扩展stdClass。

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

希望这是你需要的。


这不是“动态的”,正如问题中所指定的那样。 - Luke H

1

您可以:

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

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

您还可以使用函数:

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

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


第一个例子对我不起作用。您确定它是有效的吗? - johnk

0

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

我注意到你说“不使用数组”,我只想向你保证,虽然在后台技术上使用了数组,但你永远不必看到它。你通过->propertyname或foreach($class in $name => $value)访问所有属性。

这是昨天我正在工作的一个示例,注意这也是强类型的。因此,标记为“整数”的属性将在尝试提供“字符串”时引发错误。

当然,你可以删除它。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接