【问题标题】:PHP: Class property chaining in variable variablesPHP:变量变量中的类属性链接
【发布时间】:2011-07-19 13:16:00
【问题描述】:

所以,我有一个结构类似于下面的对象,所有这些都作为stdClass objects 返回给我

$person->contact->phone;
$person->contact->email;
$person->contact->address->line_1;
$person->contact->address->line_2;
$person->dob->day;
$person->dob->month;
$person->dob->year;
$album->name;
$album->image->height;
$album->image->width;
$album->artist->name;
$album->artist->id;

等等...(注意这些例子没有联系在一起)。

是否可以使用变量变量来调用contact->phone 作为$person 的直接属性?

例如:

$property = 'contact->phone';
echo $person->$property;

这不会按原样工作并引发E_NOTICE,因此我正在尝试寻找一种替代方法来实现这一目标。

有什么想法吗?

针对与代理方法相关的答案:

除了这个对象来自一个库之外,我会使用它来使用数组映射填充一个新对象,如下所示:

array(
  'contactPhone' => 'contact->phone', 
  'contactEmail' => 'contact->email'
);

然后遍历地图以填充新对象。我想我可以改为使用映射器......

【问题讨论】:

  • 第二个问题完全不清楚,和第一个问题无关,你可以开一个新问题

标签: php oop method-chaining variable-variables


【解决方案1】:

如果你知道这两个函数的名字,你能做到吗? (未测试)

$a = [
    'contactPhone' => 'contact->phone', 
    'contactEmail' => 'contact->email'
];

foreach ($a as $name => $chain) {
    $std = new stdClass();
    list($f1, $f2) = explode('->', $chain);
    echo $std->{$f1}()->{$f2}(); // This works
}

如果它不总是两个功能,您可以对其进行更多修改以使其正常工作。重点是,可以使用变量变量调用链式函数,只要使用括号格式即可。

【讨论】:

    【解决方案2】:

    我所知道的最简单和最干净的方式。

    function getValueByPath($obj,$path) {
        return eval('return $obj->'.$path.';');
    }
    

    用法

    echo getValueByPath($person,'contact->email');
    // Returns the value of that object path
    

    【讨论】:

      【解决方案3】:

      如果您以类似结构的方式使用您的对象,您可以显式地为所请求节点的“路径”建模。然后,您可以使用相同的检索代码“装饰”您的对象。

      'retrieval only' 装饰代码示例:

      function retrieve( $obj, $path ) {
          $element=$obj;
          foreach( $path as $step ) { 
             $element=$element[$step];
          }
          return $element;
      }
      
      function decorate( $decos, &$object ) {
         foreach( $decos as $name=>$path ) {
            $object[$name]=retrieve($object,$path);
         }
      }
      
      $o=array( 
        "id"=>array("name"=>"Ben","surname"=>"Taylor"),
        "contact"=>array( "phone"=>"0101010" )
      );
      
      $decorations=array(
        "phone"=>array("contact","phone"),
        "name"=>array("id","name")  
      );
      
      // this is where the action is
      decorate( $decorations, &$o);
      print $o->name;
      print $o->phone;
      

      (在codepad上找到它)

      【讨论】:

        【解决方案4】:

        我决定放弃这整个方法,转而采用一种更冗长但更干净,而且很可能更高效的方法。一开始我并不太热衷于这个想法,大多数人都在这里发表了我的想法。感谢您的回答。

        编辑:

        如果你有兴趣:

        public function __construct($data)
        {
          $this->_raw = $data;
        }
        
        public function getContactPhone()
        {
          return $this->contact->phone;
        }
        
        public function __get($name) 
        {
          if (isset($this->$name)) {
            return $this->$name;
          }
        
          if (isset($this->_raw->$name)) {
            return $this->_raw->$name;
          }
        
          return null;
        }
        

        【讨论】:

          【解决方案5】:

          如果它是合法的,并不意味着它也是道德的。这是 PHP 的主要问题,是的,你几乎可以做任何你能想到的事情,但这并不能使它正确。看看得墨忒耳定律:

          Law of Demeter

          如果你真的想试试这个: json_decode(json_encode($person),true);

          您将能够将其解析为数组而不是对象,但它完成您的工作是为了获取而不是为了设置。

          编辑:

          class Adapter {
          
            public static function adapt($data,$type) {
          
            $vars = get_class_vars($type);
          
            if(class_exists($type)) {
               $adaptedData = new $type();
            } else {
              print_R($data);
              throw new Exception("Class ".$type." does not exist for data ".$data);
            }
          
            $vars = array_keys($vars);
          
            foreach($vars as $v) {
          
              if($v) {        
                if(is_object($data->$v)) {
                    // I store the $type inside the object
                    $adaptedData->$v = Adapter::adapt($data->$v,$data->$v->type);
                } else {
                    $adaptedData->$v =  $data->$v;
                }
              }
            }
            return $adaptedData;
          
            }
          

          }

          【讨论】:

          • 感谢您的努力,但我决定另辟蹊径。
          【解决方案6】:

          除了制作function getPhone(){return $this->contact->phone;} 之外,您还可以制作一个神奇的方法来查看请求字段的内部对象。请记住,魔法方法有点慢。

          class Person {
              private $fields = array();
          
              //...
          
              public function __get($name) {
                  if (empty($this->fields)) {
                      $this->fields = get_class_vars(__CLASS__);
                  }
                  //Cycle through properties and see if one of them contains requested field:
                  foreach ($this->fields as $propName => $default) {
                      if (is_object($this->$propName) && isset($this->$propName->$name)) {
                          return $this->$propName->$name;
                      }
                  }
                  return NULL;
                  //Or any other error handling
              }
          }
          

          【讨论】:

            【解决方案7】:

            尽管我讨厌这样说,但你可以做一个 eval :

            foreach ($properties as $property) {
                 echo eval("return \$person->$property;");
            }
            

            【讨论】:

            • 我也想过这个,但后来想... err no.
            【解决方案8】:

            在您嵌套了内部对象的大多数情况下,这可能是重新评估您的数据结构的好时机。

            在上面的例子中,personcontactdob联系人还包含地址。在编写复杂的数据库应用程序时,尝试从最高级别访问数据并不少见。但是,您可能会发现最好的解决方案是将数据整合到 person 类中,而不是试图从本质上“挖掘”到内部对象中。

            【讨论】:

              【解决方案9】:

              是否可以使用变量变量来调用contact->phone 作为$person 的直接属性?

              不能使用表达式作为变量名。

              但你总是可以作弊:

              class xyz {
              
                  function __get($name) {
              
                      if (strpos($name, "->")) {
                          foreach (explode("->", $name) as $name) {
                              $var = isset($var) ? $var->$name : $this->$name;
                          }
                          return $var;
                      }
                      else return $this->$name;
                  }
              }
              

              【讨论】:

              • 这是我已经计划好的,但只是检查没有其他方法。谢谢。
              • 我们之前有过这个话题,我找不到链接。但结论是没有更好/更合适的原生方式。无论如何,如果您需要此功能(通常或为了便于阅读),那么这是一种可以接受的方法。
              • 这个类的 __set() 方法会是什么样子?
              【解决方案10】:

              我认为这是一件坏事,因为它导致不可读的代码在其他级别上也是完全错误的,但一般来说,如果您需要在对象语法中包含变量,您应该将它包装在大括号中以便它得到先解析。

              例如:

              $property = 'contact->phone';
              echo $person->{$property};
              

              如果您需要访问名称中包含不允许字符的对象,这同样适用于 SimpleXML 对象经常发生的情况。

              $xml->{a-disallowed-field}
              

              【讨论】:

              • 这在理论上是行不通的,但在这种情况下,'contact' 不会作为任何事物的实例存在。
              • 这是真的,也是它完全错误的另一个原因,但它仍然是一件有用的事情。
              【解决方案11】:

              试试这个代码

              $property = $contact->phone;
              echo $person->$property;
              

              【讨论】:

              • @felix 完全正确,但 $contact = $person->contact;回声$person->$contact->电话;会..我真的没想过这样做...
              • @Matt Humphrey:这是不正确的。如果有的话,应该是$contact = $person->contact; echo $contact->phone
              • 为了进一步澄清我对此答案的评论:这将尝试访问名称为 $contact->phone$person 的属性。这在给定的上下文中是错误的,让我觉得你不明白你在那里实际做什么。
              • 对不起,我打字太快了,我就是这个意思。
              • @Matt Humphrey:我明白了。仍然与此答案中提出的完全不同。
              【解决方案12】:

              您可以使用类型转换将对象更改为数组。

              $person = (array) $person;
              
              echo $person['contact']['phone'];
              

              【讨论】:

                【解决方案13】:

                OOP 主要是为了保护对象的内部结构不受外界影响。您在这里尝试做的是提供一种通过person 接口公开phone 内部结构的方法。这不好。

                如果您想要一种方便的方法来获取“所有”属性,您可能需要为此编写一组显式的便捷函数,如果您愿意,可以将其包装在另一个类中。这样您就可以改进支持的实用程序,而无需触及(并且可能破坏)核心数据结构:

                class conv {
                 static function phone( $person ) {
                   return $person->contact->phone;
                 }
                
                }
                
                // imagine getting a Person from db
                $person = getpersonfromDB();
                
                print conv::phone( $p );
                

                如果您需要更专业的功能,请将其添加到实用程序中。这是很好的解决方案:将便利性与核心分开以降低复杂性,并提高可维护性/可理解性。

                另一种方法是“扩展”Person 类,方便,围绕核心类的内部构造构建:

                class ConvPerson extends Person {
                   function __construct( $person ) {
                     Person::__construct( $person->contact, $person->name, ... );
                   }
                   function phone() { return $this->contact->phone; }
                }
                
                // imagine getting a Person from db
                $person = getpersonfromDB();
                $p=new ConvPerson( $person );
                print $p->phone();
                

                【讨论】:

                • 我想我应该更清楚。在我的示例中,我使用 $person,但是还有其他对象也使用这个“系统”,我以这种方式访问​​的每个对象都是一个 stdClass,所以没有明确的“Person”类,否则,我可能不会问这个问题..
                【解决方案14】:

                如果我是你,我会创建一个返回 $this->contact->phone 的简单方法 ->property();

                【讨论】:

                • 这被称为“代理方法”,这是正确的使用方法。
                • 除了这个对象来自库之外,我会使用它来使用数组映射填充新对象,如下所示:array('contactPhone' => 'contact->phone', 'contactEmail ' => '联系方式->电子邮件')
                • @matt:如果这还不够的话,这似乎是另一个问题。 (标记为xD之前的答案)
                猜你喜欢
                • 2013-02-10
                • 2011-10-22
                • 2020-01-19
                • 1970-01-01
                • 2011-05-28
                • 1970-01-01
                • 2013-05-10
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多