【问题标题】:Extending ArrayObject in PHP properly?在 PHP 中正确扩展 ArrayObject?
【发布时间】:2011-11-07 10:54:10
【问题描述】:

问题: 我正在尝试扩展 PHP 的 ArrayObject,如下所示。不幸的是,在设置多维对象时我无法让它正常工作,而是抛出错误,因为我在 PHP 中启用了严格的设置。 (Error: Strict standards: Creating default object from empty value)

问题:如何修改我的班级以自动为我创建不存在的关卡?

代码:

$config = new Config;
$config->lvl1_0 = true; // Works
$config->lvl1_1->lvl2 = true; // Throws error as "lvl1" isn't set already

class Config extends ArrayObject
{
    function __construct() {
        parent::__construct(array(), self::ARRAY_AS_PROPS);
    }

    public function offsetSet($k, $v) {
        $v = is_array($v) ? new self($v) : $v;
        return parent::offsetSet($k, $v);
    }
}

【问题讨论】:

  • 你使用的是什么版本的 PHP?
  • 你好工业!你真的需要你的Config 类是ArrayObject 的专业化,还是仅仅因为它提供的信息存储设施而需要?
  • 嗨尼克!不,我已经解决了这个问题之后的ArrayObject 实现:stackoverflow.com/questions/7202784/…

标签: php arrays oop spl arrayobject


【解决方案1】:

对您的问题采取更全面的观点,您可以创建一个类来对多维对象的概念进行建模。

即时发布的解决方案并未从ArrayObject 扩展以实现您提到的目标。当您将问题标记为 oop 时,我认为加强存储对象状态的方式与如何访问它的分离很重要。

希望这将帮助您实现所需!

根据您的说法,多维对象是:

  • 处理多层嵌套信息
  • 它通过属性提供对信息的读/写访问权限
  • 在访问未定义的属性时表现良好。这意味着,例如,您对空实例执行以下操作:$config->database->host = 'localhost' databasehost 级别会自动初始化,host 将在查询时返回 'localhost'
  • 理想情况下,将从关联数组初始化(因为您已经可以将配置文件解析到其中)

建议的解决方案

那么,如何实现这些功能呢?

第二个很简单:使用 PHP 的 __get__set 方法。只要对不可访问的属性(未在对象中定义的属性)进行读/写操作,就会调用这些。 诀窍是不声明任何属性并通过这些方法处理属性的操作,并将作为键访问的属性名称映射到用作存储的关联数组。它们基本上会提供一个接口来访问内部存储的信息。

对于第三个,我们需要一种方法来在读取未声明的属性时创建新的嵌套级别。 这里的关键点是要意识到属性的返回值必须是一个多维对象,因此也可以从中创建更多级别的嵌套:每当我们被要求提供名称不存在于内部数组中的属性时,我们将该名称与MultiDimensionalObject 的新实例相关联并返回它。返回的对象也将能够处理已定义或未定义的属性。

当一个未声明的属性被写入时,我们所要做的就是用内部数组中提供的值为其命名。

第四个很简单(参见__construct 实现)。当属性的值是一个数组时,我们只需要确保创建一个MultiDimensionalObject

最后,第一个:我们处理第二个和第三个特性的方式允许我们在任何级别的嵌套中读取和写入属性(已声明和未声明)。 您可以在空实例上执行$config->foo->bar->baz = 'hello' 之类的操作,然后成功查询$config->foo->bar->baz

重要 请注意 MultiDimensionalObject 而不是 beign 本身是一个数组,它与一个数组组合,让您可以根据需要更改存储对象状态的方式。

实施

/* Provides an easy to use interface for reading/writing associative array based information */
/* by exposing properties that represents each key of the array */
class MultiDimensionalObject {

    /* Keeps the state of each property  */
    private $properties;
    
    /* Creates a new MultiDimensionalObject instance initialized with $properties */
    public function __construct($properties = array()) {
        $this->properties = array();
        $this->populate($properties);
    }
    
    /* Creates properties for this instance whose names/contents are defined by the keys/values in the $properties associative array */
    private function populate($properties) {
        foreach($properties as $name => $value) {
            $this->create_property($name, $value);
        }
    }
    
    /* Creates a new property or overrides an existing one using $name as property name and $value as its value */
    private function create_property($name, $value) {
        $this->properties[$name] = is_array($value) ? $this->create_complex_property($value)
                                                    : $this->create_simple_property($value);
    }
    
    /* Creates a new complex property. Complex properties are created from arrays and are represented by instances of MultiDimensionalObject */
    private function create_complex_property($value = array()){
        return new MultiDimensionalObject($value);
    }
    
    /* Creates a simple property. Simple properties are the ones that are not arrays: they can be strings, bools, objects, etc. */
    private function create_simple_property($value) {
        return $value;
    }
    
    /* Gets the value of the property named $name */
    /* If $name does not exists, it is initilialized with an empty instance of MultiDimensionalObject before returning it */
    /* By using this technique, we can initialize nested properties even if the path to them don't exist */
    /* I.e.: $config->foo
                    - property doesn't exists, it is initialized to an instance of MultiDimensionalObject and returned
             
             $config->foo->bar = "hello";
                    - as explained before, doesn't exists, it is initialized to an instance of MultiDimensionalObject and returned.
                    - when set to "hello"; bar becomes a string (it is no longer an MultiDimensionalObject instance) */    
    public function __get($name) {
        $this->create_property_if_not_exists($name);
        return $this->properties[$name];
    }
    
    private function create_property_if_not_exists($name) {
        if (array_key_exists($name, $this->properties)) return;
        $this->create_property($name, array());
    }
    
    public function __set($name, $value) {
        $this->create_property($name, $value);
    }
}

演示

代码:

var_dump(new MultiDimensionalObject());

结果:

object(MultiDimensionalObject)[1]
    private 'properties' => 
        array
            empty

代码:

$data = array( 'database' => array ( 'host' => 'localhost' ) );
$config = new MultiDimensionalObject($data);        
var_dump($config->database);

结果:

object(MultiDimensionalObject)[2]
    private 'properties' => 
        array
            'host' => string 'localhost' (length=9)

代码:

$config->database->credentials->username = "admin";
$config->database->credentials->password = "pass";
var_dump($config->database->credentials);

结果:

object(MultiDimensionalObject)[3]
    private 'properties' => 
        array
          'username' => string 'admin' (length=5)
          'password' => string 'pass' (length=4)

代码:

$config->database->credentials->username;

结果:

admin

【讨论】:

    【解决方案2】:

    实现offsetGet 方法。如果您正在访问一个不存在的属性,您可以根据需要创建一个。

    由于你是扩展ArrayObject,你应该使用数组方式[]来设置或获取。

    【讨论】:

      【解决方案3】:

      复制粘贴了您的代码,它在我的 PHP 测试盒(运行 PHP 5.3.6)上运行良好。它确实提到了严格标准警告,但它仍然按预期工作。这是 print_r 的输出:

      Config Object
      (
          [storage:ArrayObject:private] => Array
              (
                  [lvl1_0] => 1
                  [lvl1_1] => stdClass Object
                      (
                          [lvl2] => 1
                      )
      
              )
      
      )
      

      值得注意的是,在 PHP 文档中有一条评论,其中包含与您尝试执行的操作相关的指导:

      sfinktah at php dot spamtrak dot org 17-Apr-2011 07:27
      如果您计划从 ArrayObject 派生自己的类,并希望保持完整的 ArrayObject 功能(例如能够转换为数组),则有必要使用 ArrayObject 自己的私有属性“存储”。

      上面链接了详细的解释,但是除了你有的offsetSet和xdazz提到的offsetGet之外,你还必须实现offsetExistsoffsetUnset。这应该与您当前的错误无关,但您应该注意这一点。

      更新: xdazz 的下半场有你的问题的答案。如果您将 Config 对象作为数组访问,则它可以正常工作:

      $config = new Config;
      $config[ 'lvl1_0' ] = true;
      $config[ 'lvl1_1' ][ 'lvl2' ] = true;
      

      你能做到吗?还是因为某种原因你受限于 Object 语法?

      【讨论】:

      • 你好法雷!我可以将它作为数组访问,尽管不能以一致的方式读取和写入 ArrayObject 类有点遗憾。
      • @Industrial 不幸的是(对于您的情况),它按预期工作。您正在从一个空值创建一个对象,因此它会告诉您。您可以通过使用ini_set('display_errors', 0); 或关闭严格错误报告error_reporting(E_ALL); 来隐藏此选项是否可以选择?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-28
      • 1970-01-01
      • 1970-01-01
      • 2013-09-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多