【问题标题】:Using a Setter with Multidimensional Arrays在多维数组中使用 Setter
【发布时间】:2015-01-02 08:14:26
【问题描述】:

我正在尝试为 PHP 中与多维数组一起使用的对象创建一个 __set。这甚至可能吗?

我希望能够做到以下几点:$post->comments[0]['uid']=3;。但是,cmets 实际上将成为私有缓存变量$_cache['comments']=array() 中的一个键。如果 __set 函数能够以某种方式获取基本键 (cmets) 和索引 (0) 以及它正在设置的键/值 (uid/3),那就太好了。但是,这是不可能的。

我曾考虑过制作 $_cache['comments'] 和 ArrayObjects 数组,但这不会让我定义自定义 _get/_set 重载。相反,我认为我最终可能不得不创建一个新的 Comments 对象,然后用这些对象填充数组。但是,我真的不想这样做,如果 PHP 能以某种方式处理 __set 重载中的嵌套数组,那就太好了。

我正在使用 Mongo,希望每个文档只有一个对象。但是,Mongo 中的数组对象给我带来了一些问题。我想在 PHP 中将它们作为数组处理,但这似乎不可能。设置器需要获取$post->comments[0]['uid']=3 并更新缓存以及设置$this->data['comments'][0]['uid']=3

我知道如果 cmets 是一个对象数组,我可以这样做:

$post->comments[0]->uid=3; 
///Sets $_cache['comments'][0]->uid=3;

它会起作用,因为 cmets 的 getter 会返回对象数组并允许它访问 uid 属性。然后我可以在 cmets 对象中有一个 getter/setter,它可以通过伪“朋友”函数/hack 以某种方式编辑 $post->data。但是,我看不到使用数组完成此任务的简单方法....

有什么建议吗?

【问题讨论】:

    标签: php mongodb object getter-setter


    【解决方案1】:

    这比您实际想象的要复杂。您可以通过大量变通方法完成您想要的,但很少值得付出努力。

    如果->comments 本身是通过getter 方法解析的,那么将某些内容分配给[0] 子数组实际上不会在私有属性中结束。 ->comments[0]= 甚至不会调用你的 setter 方法。相反,这是一个读取访问。

    要完成这项工作,您必须让 __get 方法返回 & $this->_cache['comments'] 的引用。

    如果您想拦截该comments 数组中的集合访问,您确实需要ArrayObject。不同之处在于这需要覆盖offsetGetoffsetSet 而不是__get__set。但是同样,由于您正在访问另一个子数组,因此实际上将使用 __get 方法,并且您需要返回另一个引用,或者再次返回一个级别的 ArrayObject 解决方法 goo。

    【讨论】:

    • 我可以发誓像foreach($post->comments as $x){ $x->uid=4} 这样的东西可以在 post::__get 返回一组评论对象的地方工作。你确定$post->comments[0]->uid=4 不起作用吗?
    • 如果它包含一个子对象[0]->uid而不是数组[0]['uid'],那么它将起作用,因为它引用了同一个对象。但是默认情况下,数组访问是通过复制工作的。
    • 也就是说,我要弄乱参考文献,看看我是否可以让它与我需要的东西一起工作。
    • @Mario 等等,所以如果没有数组中每个元素的对象,真的没有任何方法可以让它“自动”工作。我正在尝试映射一系列相当复杂的 Mongo 文档,这些文档经常更改只是能够使用一个简单的嵌套数组并让一切正常工作,而不必每次都创建一个新对象。你有什么办法可以让它发挥作用吗?
    • 我试图想出类似的东西。您当然可以设计一个 MagicArrayObject 类,将其附加到 __get/offsetGet 方法。它必须自动转换任何访问的子数组,并尽可能保留对原始数组的引用。 -- 但是仅仅使用简单的数组对我来说似乎并不可行。您应该放弃能够在那里观察集合访问。 PHP 让这太难了。
    【解决方案2】:

    我在构建自己的 PHP 包装类时跳过了其中的一些环节。

    https://github.com/gatesvp/MongoModel

    它仍在开发中,但它确实处理了一些基本的“将此对象映射到 DB”。

    【讨论】:

      【解决方案3】:

      几乎没有什么值得写在 PHP 聊天室或 PHP 文档中对你有用的东西,Adam。大多数建议都倾向于在 SPL 中实现interface ArrayAccess 或扩展class ArrayObject。事实上,有一个非常简单的解决方案可以解决您的问题:$post->comments[0]['uid']=3 使用重载的 setter __set()

      post 类中定义private $comments = array();。为方便起见,对$comments 的第一个下标使用文本键:在这里,整数 0 变为“零”。然后按如下方式调用 setter:

      $post->zero = ['uid', 3];
      

      这会调用魔法设置器,因为在类post 中没有公开声明的属性$zero:“当与尚未声明或在当前范围内不可见的属性或方法交互时,将调用重载方法。 " (Overloading 上的 PHP 5 手册页。)

      setter 也可以是setComments(),这很方便,因为您不必区分传入的属性来识别那些用于数组comments 的属性,但调用语法变得不那么自然了。

      您的重载、自动魔法函数 __set 接收两个参数:一个属性和一个值:

      public function __set($property, $value) {
      

      让人想起 Crockford 的 JSON 协议。用这些术语来思考会很有帮助。

      由于您发送的属性“零”在classpost中不存在,因此需要将其捕获,而我的首选方法是,因为属性comments中的第一个下标可能有多个值,因此定义post 中支持的下标值的私有数组:

      private $indices = [
           "zero"  => 0,
           "one"   => 1,
           "two"   => 2,
           "three" => 3 
      ];
      

      comments 的索引作为$property 到达__set() 时,验证它存在于$indices 中。现在您只需遍历$value 中提供的数组,提取 uid 及其对应的值,然后赋值给$comments,如下:

      public function __set($property, $value) {
          if (array_key_exists($property, $this->indices) && is_array($value))
              foreach ($value as $uid => $uid_value)  
                  $this->comments[$this->indices[property]][$uid] = $uid_value;
          else
              ...
      }
      

      $this->indices[property] 用于提取整数值 0 用于 索引comments 的第一个维度,并将$uid_value 提取并分配值 int 3。

      此处概述的方法不是噱头、解决方法或巧妙的技巧。这是一种直接的设计技术,旨在与 SPL 的一个工具一起使用,并且原则上可以扩展到任意维度的数组。我已经在生产系统中实现了设计,因此,如果您仍然遇到困难,请在此处发布,我将帮助您调试您的应用程序。祝你好运!

      【讨论】:

        【解决方案4】:

        我相信重载某些属性最接近的方法是使用此处定义的魔术方法 __set():http://us.php.net/__set

        我不确定您是否可以在 PHP 编译器获取 [0] 之前处理它...

        所以您的其他解决方案是将 cmets 转换为方法

        public function comments($id) {
           return $this->obj[$id]; // Obj
        }
        

        而且你返回的对象有 __set 属性

        class Obj {
           private $id;
           public function __set($key, $value) {
               if($key === 'uid') {
                   $_cache = $GLOBALS['_cache'];
                   $_cache['comments'][$this->id]->uid = $value;
               }
           }
        }
        

        这里缺少很多代码,但是你可以用这个__set方法()弄清楚怎么做

        【讨论】:

        • 我更新了我的原始帖子,也许可以解释我想要做得更好的事情。我已经在使用 __set 重载。它适用于对象的一维数组($this->cmets[0]->uid),但不适用于多维数组。
        • 嗯,问题是你想让 $cmets[0] 返回一个值。所以在这种情况下,如果你可以用 __get() 来做,那就完美了。然后,一旦您返回了一个 Obj 值(如我的类示例),这次您可以使用 __set() 魔术引用来使 uid = 3 工作。
        【解决方案5】:

        创建一个函数,而不是试图在根本不适合的东西之上破解它。

        public function setCommentUid($commentId, $uid) {
            $this->_cache['comments'][$commentId]->uid = $uid;
        }
        
        //then...
        $post->setCommentUid(0, 3);
        

        这使得使用类变得更加简单,并且更容易看到它的作用。

        【讨论】:

        • 不幸的是,我正在寻找可以为每个实现扩展的基类的解决方案。我正在使用 Mongo,并且希望能够将我的文档模型扔给它,让一切“自动”发生。我敢肯定那里有一些解决方案......
        • 根据您需要自动执行的功能类型,使用__call 可能会起作用。您可以使用它来允许调用不存在的函数,并动态处理它,以便将值放入您想要的数组中。我确实有一种感觉,你正在做的事情可能不是最好的方法,但很难用可用的信息来判断,而且它可能超出了这个问题的范围:)
        • 在基类中,为什么不set($cachevar, $id, $basevar, $baseval) 调用$this->_cache[$cachevar][$id]->$basevar = $baseval
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-10
        • 1970-01-01
        • 2011-10-30
        • 1970-01-01
        • 1970-01-01
        • 2013-09-30
        相关资源
        最近更新 更多