【问题标题】:Casting object to array - any magic method being called?将对象转换为数组 - 调用任何魔术方法?
【发布时间】:2012-06-22 17:03:54
【问题描述】:

我有一个 Foo 类的对象:

class Foo extends Bar {
    protected $a;
    protected $b;
}

$obj = new Foo();

我想要(并且必须)做的是将此对象转换为数组,如下所示:

$arr = (array)$obj;

此时是否有任何魔法(或不是魔法:))方法被调用?或者有没有其他方法可以拦截?我知道我可以写一个简单的方法,例如。 asArray() 在 Foo 中,但我正在寻找一些更“原生”的 PHP 方式。

【问题讨论】:

    标签: php arrays object casting


    【解决方案1】:

    没有

    PHP 中没有__toArray 魔术方法。 2006 中的enhancement proposal has been rejected,答案如下:

    [2006-08-20 11:12 UTC] helly@php.net

    为什么不简单地拥有一个方法 asArray() 甚至可以作为 界面:

    接口 ArrayConversion { function asArray(); }

    看,我们有 __toString,因为它在语言结构中受支持,例如 echo、print 等内部函数。但我们决定反对 数组的自动转换已经。所以它永远不会被支持 语言结构。那就是说没有必要,也没有 你会战胜上面的界面。事实上你会成功的 php 更复杂,因为您只需要添加一个神奇的功能。

    因此,它不太可能在未来的任何版本中实现(如果你问我,这很遗憾)。

    【讨论】:

    • 但是,嘿,我们至少没有得到 goto 构造吗? 颤抖
    • 他这么说很奇怪,因为实际上php有一个对象到数组的转换:php.net/manual/en/…
    • 问题实际上是关于能够实现自定义转换逻辑。
    • 请注意:2019 年,PHP 正在讨论 __toArray() :wiki.php.net/rfc/to-array
    【解决方案2】:

    您可以让该类实现ArrayAccess 接口。这将允许您将对象视为数组而不进行强制转换,并且您可以完全控制成员的使用方式。

    【讨论】:

    • 虽然很好,但这个答案并没有真正解决这个问题。您至少应该在某处包含“不”一词:)
    • 好点,但在答案的开头某处说明实现ArrayAccess 接口不足以将对象转换为数组。
    • 还应该注意的是,为了使用foreach遍历扩展ArrayAccess的对象中包含的元素,您还必须实现\IteratorAggregate\Iterator。通常,那些希望在对象中重现数组功能的人使用implements \Countable, \IteratorAggregate, \ArrayAccess 请参阅\ArrayObject
    【解决方案3】:

    遗憾的是,转换为数组不会触发任何魔术方法,就像它完成的那样:

    $s = (string)$obj;
    

    触发__toString() 方法并且您可以覆盖。

    但是,您可以编写一个自定义的toArray() 方法。

    您可能还对Serializable 接口感兴趣,该接口允许您编写自定义序列化器策略。

    【讨论】:

      【解决方案4】:

      不确定这个问题是否仍然相关,但 php 已内置 ArrayObject 类,允许将对象视为数组,并且在用作数据库记录或集合的容器时很方便。

      就严格类型而言,这可能不是最佳实践,但它允许将对象视为数组,并且两个语句都有效。

      $obj = new ArrayObject(['a' => 'alpha']);
      var_dump($obj['a']); //alpha
      var_dump($obj->getOffset('a'));//alpha
      

      但是你需要记住ArrayObject的行为

      $obj = new ArrayObject(['a' => 'alpha']);
      //Access Property
      var_dump($obj['a']); //alpha
      var_dump($obj->offsetGet('a'));//alpha
      var_dump($obj->a); //null Notice: Undefined property: ArrayObject::$a
      
      //Serialization
      var_dump(serialize($obj));// string 'C:11:"ArrayObject":41:{x:i:0;a:1:{s:1:"a";s:5:"alpha";};m:a:0:{}}' (length=65)
      var_dump($obj->serialize());// string 'x:i:0;a:1:{s:1:"a";s:5:"alpha";};m:a:0:{}'
      var_dump(serialize($obj) === $obj->serialize());// false !!!
      
      //Setting Properties
      $obj['b'] = 'beta'; //OK
      $obj->c = 'gamma'; //value becomes object property!!!
      var_dump($obj);
      /* OBJECT DUMP
      object(ArrayObject)[13]
        public 'c' => string 'gamma' (length=5)
        private 'storage' =>
          array (size=2)
            'a' => string 'alpha' (length=5)
            'b' => string 'beta' (length=4)
       */
      
      //Property validation as array
      var_dump(isset($obj['a']));//true
      var_dump(isset($obj['b']));//true
      var_dump(isset($obj['c']));//false
      //Property validation as object
      var_dump(isset($obj->a));//false
      var_dump(isset($obj->b));//false
      var_dump(isset($obj->c));//true
      
      //Typecasting
      var_dump((array)$obj);
      /*
      array (size=2)
        'a' => string 'alpha' (length=5)
        'b' => string 'beta' (length=4)
       */
      
      //var_dump((string)$obj);// Catchable fatal error: Object of class ArrayObject could not be converted to string
      

      ArrayObject 接受两个标志ArrayObject::STD_PROP_LIST 作为默认和ArrayObject::ARRAY_AS_PROPS 作为替代。

      这会改变读取值的行为,但不支持以这种方式设置新属性,示例如下:

      $obj = new ArrayObject(['a' => 'alpha'], ArrayObject::ARRAY_AS_PROPS);
      //Access Property
      var_dump($obj['a']); //alpha
      var_dump($obj->offsetGet('a'));//alpha
      var_dump($obj->a);//alpha
      
      //Serialization
      var_dump(serialize($obj));// string 'C:11:"ArrayObject":41:{x:i:0;a:1:{s:1:"a";s:5:"alpha";};m:a:0:{}}' (length=65)
      var_dump($obj->serialize());// string 'x:i:0;a:1:{s:1:"a";s:5:"alpha";};m:a:0:{}'
      var_dump(serialize($obj) === $obj->serialize());// false !!!
      
      //Setting Properties
      $obj['b'] = 'beta'; //OK
      $obj->c = 'gamma'; //OK
      var_dump($obj);
      /* OBJECT DUMP
      object(ArrayObject)[14]
        private 'storage' =>
          array (size=3)
            'a' => string 'alpha' (length=5)
            'b' => string 'beta' (length=4)
            'c' => string 'gamma' (length=5)
       */
      
      //Property validation as array
      var_dump(isset($obj['a']));//true
      var_dump(isset($obj['b']));//true
      var_dump(isset($obj['c']));//false !!!
      //Property validation as object
      var_dump(isset($obj->a));//true
      var_dump(isset($obj->b));//true
      var_dump(isset($obj->c));//true
      
      //Typecasting
      var_dump((array)$obj);
      /*
      array (size=2)
        'a' => string 'alpha' (length=5)
        'b' => string 'beta' (length=4)
       */
      

      要使这种行为更加一致,您必须扩展此类并实现魔术方法__get()__set()__isset()__unset()

      另一个棘手的部分是序列化,默认方法serialize 将返回一个副本序列化$storage 变量而不是对象本身,作为返回实例的序列化副本的解决方法,您可以在__toString 方法中实现默认序列化,这种方式它的行为正确。

      class FooObject extends ArrayObject
      {
          public function __get($index)
          {
              if ($this->offsetExists($index)) {
                  return $this->offsetGet($index);
              } else {
                  throw new UnexpectedValueException('Undefined key ' . $index);
              }
          }
      
          public function __set($index, $value)
          {
              $this->offsetSet($index, $value);
              return $this;
          }
      
          public function __isset($index)
          {
              return $this->offsetExists($index);
          }
      
          public function __unset($index)
          {
              return $this->offsetUnset($index);
          }
      
          public function __toString()
          {
              return serialize($this);
          }
      }
      

      使用示例

      $obj2 = new FooObject(['a' => 'alpha']);
      //Access Property
      var_dump($obj2['a']); //alpha
      var_dump($obj2->offsetGet('a'));//alpha
      var_dump($obj2->a); //alpha
      
      //Serialization
      var_dump(serialize($obj));// string 'C:11:"ArrayObject":41:{x:i:0;a:1:{s:1:"a";s:5:"alpha";};m:a:0:{}}' (length=65)
      var_dump($obj->serialize());// string 'x:i:0;a:1:{s:1:"a";s:5:"alpha";};m:a:0:{}'
      var_dump(serialize($obj) === $obj->serialize());// false !!!
      
      //Setting Properties
      $obj2['b'] = 'beta'; //OK
      $obj2->c = 'gamma'; //OK
      var_dump($obj2);
      /* OBJECT DUMP
      object(FooObject)[14]
        private 'storage' (ArrayObject) =>
          array (size=3)
            'a' => string 'alpha' (length=5)
            'b' => string 'beta' (length=4)
            'c' => string 'gamma' (length=5)
       */
      
      //Property validation as array
      var_dump(isset($obj2['a']));//true
      var_dump(isset($obj2['b']));//true
      var_dump(isset($obj2['c']));//true
      //Property validation as object
      var_dump(isset($obj2->a));//true
      var_dump(isset($obj2->b));//true
      var_dump(isset($obj2->c));//true
      
      //Typecasting
      var_dump((array)$obj2);
      /*
      array (size=3)
        'a' => string 'alpha' (length=5)
        'b' => string 'beta' (length=4)
        'c' => string 'gamma' (length=5)
       */
      

      【讨论】:

        【解决方案5】:

        在不更改原始类定义的情况下,一种方法是使用反射。这允许您在运行时检查类的属性。

        取自手册:http://www.php.net/manual/en/reflectionclass.getproperties.php

        <?php
        class Foo {
            public    $foo  = 1;
            protected $bar  = 2;
            private   $baz  = 3;
        }
        
        $foo = new Foo();
        
        $reflect = new ReflectionClass($foo);
        $props   = $reflect->getProperties(ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PROTECTED);
        
        foreach ($props as $prop) {
            print $prop->getName() . "\n";
        }
        
        var_dump($props);
        
        ?>
        
        The above example will output something similar to:
        foo
        bar
        array(2) {
          [0]=>
          object(ReflectionProperty)#3 (2) {
            ["name"]=>
            string(3) "foo"
            ["class"]=>
            string(3) "Foo"
          }
          [1]=>
          object(ReflectionProperty)#4 (2) {
            ["name"]=>
            string(3) "bar"
            ["class"]=>
            string(3) "Foo"
          }
        }
        

        【讨论】:

          【解决方案6】:

          您可以使用 get_object_vars($yourObject) 返回一个关联数组,其中包含可从上下文访问的所有属性名称/值。

          http://php.net/manual/en/function.get-object-vars.php

          如果您想访问受保护或私有属性,我的建议是扩展 ArrayObject,它实现方法 getArrayCopy()

          【讨论】:

            猜你喜欢
            • 2019-10-11
            • 1970-01-01
            • 1970-01-01
            • 2013-05-27
            • 1970-01-01
            • 1970-01-01
            • 2018-05-02
            • 2012-01-21
            相关资源
            最近更新 更多