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

Posted

技术标签:

【中文标题】您可以在 PHP 中动态创建实例属性吗?【英文标题】:Can you create instance properties dynamically in PHP? 【发布时间】:2010-10-24 05:01:45 【问题描述】:

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

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

【问题讨论】:

数组以及 __set 和 __get 方法对于正常用途应该足够了。您对坚持“一个属性=一个变量”的解决方案有什么特别的顾虑吗?顺便说一句,封装原则规定没有其他对象应该知道变量是真实变量还是数组中的值?甚至不是通过扩展此基础制成的对象。 php.net/manual/en/language.oop5.overloading.php 【参考方案1】:

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

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

【讨论】:

这是我需要的,但是这些属性是私有的还是公共的?或者我可以像这样将它们设为私有: private $this->$k => $value; ? 它们将是公开的,据我所知,在运行时不可能将它们设为私有或受保护。对于“私有”可见性,您可以声明一个私有数组类型字段,然后填充它。 “受保护”也是如此。最好让事情尽可能简单,因此您可以只引入一个名为 $this->ds 的私有数组,然后可以通过 $this->ds['fieldname'] 之类的语句来处理其中的字段。或者,如果您想真正花哨,您将实现一个基本上包装数组功能的迷你类,因此您可以执行 $this->myDatasetObject->fieldname 之类的操作。 您可以在 PHP5 中重载任何对象,无需 __get 和 __set,但属性始终是公共的。如果你需要保护事物,你需要明确声明它们。 我相信ReflectionMethod::setAccessible可以帮助你改变一个方法的私有访问【参考方案2】:

这完全取决于你想要什么。可以动态修改class吗?并不真地。但是你能像在那个类的一个特定实例中那样动态地创建 object 属性吗?是的。

class Test

    public function __construct($x)
    
        $this->$x = "dynamic";
    


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

输出:

动态

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

【讨论】:

这也说明动态属性是公开的。【参考方案3】:

是的,你可以。

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

【讨论】:

【参考方案4】:

您可以使用实例变量作为任意值的持有者,然后使用 __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;
    

【讨论】:

我个人喜欢您的解决方案,因为您可以更好地控制变量在范围方面的使用方式以及您拥有什么,但他只是发布了这个。编辑:对不起,忘了提:不使用数组来保存属性,每个属性都在一个单独的“变量”中。 感谢您的评论 - 我认为这是解决问题的自然方法。恐怕我不明白 Brayn 的编辑到底是什么意思——而且作为新手,我没有足够的声誉点来发表评论询问。哦,好吧... 嗨,我真的很喜欢这个答案。我尝试对其进行编辑以进行修复,但 *** 拒绝更改,因为它是单字符编辑:return this->_properties[$name]; 可能应该是 return $this->_properties[$name];【参考方案5】:

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

<?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 可以弄清楚”版本所以改版就好了。【参考方案6】:

这是一个简单的函数来填充对象成员而不公开类成员。 它还将构造函数留给您自己使用,在不调用构造函数的情况下创建对象的新实例!因此,您的域对象不依赖于数据库!


/**
 * 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,这太棒了!

【讨论】:

【参考方案7】:
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

【讨论】:

class DataStore var_export(is_subclass_of('DataStore', 'StdClass'));false!类在 PHP 中不会自动扩展 StdClass。 先生,我确信我在 php.net 的某个地方读过它!除此之外,这段代码确实输出了 "123" 。 . . [代码] 类 foo public $a=1;公共函数 __construct() $this->b = 2; $bar = 新的 foo; $bar->c = 3; echo $bar->a.$bar->b.$bar->c;[代码] @Anthony 类不会自动扩展 PHP 中的 stdClass,但是 PHP 允许向现有对象添加动态属性(从版本 8 开始),它可能会在未来发生变化【参考方案8】:

你可以:

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

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

你也可以使用函数:

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

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

【讨论】:

第一个例子对我不起作用。你确定它有效吗?【参考方案9】:

扩展标准类。

class MyClass extends stdClass

    public function __construct()
    
        $this->prop=1;
    

我希望这是你需要的。

【讨论】:

这不是“动态的”,正如所讨论的那样。【参考方案10】:

处理这种快速发展的方式确实很复杂。我喜欢答案和魔术方法,但我认为最好使用 CodeSmith 之类的代码生成器。

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

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

【讨论】:

【参考方案11】:

如果你真的必须这样做,最好的方法是重载一个 ArrayObject,这样可以保持迭代支持(foreach),它仍然会循环遍历你的所有属性。

我注意到您说“不使用数组”,我只是想向您保证,虽然 技术上 在后台使用了一个数组,但您永远不必看到它。您可以通过 ->properyname 或 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);
            
    

【讨论】:

【参考方案12】:

读完@Udo 的answer。我提出了以下模式,它不会用构造函数数组参数中的任何项目使类实例膨胀,但仍然可以让您键入更少并轻松地向类添加新属性。

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

【讨论】:

以上是关于您可以在 PHP 中动态创建实例属性吗?的主要内容,如果未能解决你的问题,请参考以下文章

ext actioncolumn 能动态设置tooltip 吗

属性的动态参数

如何在 React 中动态创建状态?

从动态内容创建视频电影

PHP动态获取类构造函数并调用它来创建一个新实例[重复]

动态对象初始化 As3