PHP:变量中的类属性链接

PHP: Class property chaining in variable variables

提问人:Matt Humphrey 提问时间:3/21/2011 最后编辑:Matt Humphrey 更新时间:8/17/2016 访问量:4201

问:

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

$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 方法链接 变量

评论

0赞 dynamic 3/21/2011
第二个问题完全不清楚,它与第一个问题无关,您可以打开一个新问题

答:

4赞 dynamic 3/21/2011 #1

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

评论

0赞 Clement Herreman 3/21/2011
这被称为“代理方法”,这是正确的使用方式。
0赞 Matt Humphrey 3/21/2011
除了这个对象来自一个库,并且使用它来填充一个带有数组映射的新对象,如下所示: array('contactPhone' => 'contact->phone', 'contactEmail' => 'contact->email')
0赞 dynamic 3/21/2011
@matt:如果这还不够,这似乎是另一个问题。(在xD之前标记为答案)
0赞 xtofl 3/21/2011 #2

OOP 主要是将物体的内部与外界隔离开来。您在这里尝试做的是提供一种通过接口公开内部的方法。这可不好。phoneperson

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

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();

评论

0赞 Matt Humphrey 3/21/2011
我想我应该更清楚。在我的示例中,我使用 $person,但还有其他对象也使用这个“系统”,我以这种方式访问的每个对象都是一个 stdClass,因此没有明确的“Person”类,否则,我可能不会问这个问题。
0赞 Neddy 3/21/2011 #3

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

$person = (array) $person;

echo $person['contact']['phone'];
2赞 Poonam Bhatt 3/21/2011 #4

试试这段代码

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

评论

0赞 Matt Humphrey 3/21/2011
@felix没错,但$contact = $person->接触;回声 $person->$contact->电话;愿意。。我真的没想过要这样做......
0赞 Felix Kling 3/21/2011
@Matt Humphrey:这是不正确的。如果有的话,它应该是 .$contact = $person->contact; echo $contact->phone
0赞 Felix Kling 3/21/2011
为了进一步澄清我对这个答案的评论:这将尝试访问其名称为 的属性。这在给定的上下文中是错误的,让我认为你不明白你实际上在那里做了什么。$person$contact->phone
0赞 Matt Humphrey 3/21/2011
对不起,我打得太快了,这就是我的意思。
0赞 Felix Kling 3/21/2011
@Matt Humphrey:我明白了。尽管如此,它与这个答案中提出的完全不同。
2赞 Adam Pointer 3/21/2011 #5

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

例如:

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

如果需要访问名称中包含已取消字符的对象,这同样适用,这通常发生在 SimpleXML 对象中。

$xml->{a-disallowed-field}

评论

0赞 Matt Humphrey 3/21/2011
这在理论上是行不通的,因为在这种情况下,“接触”不会作为任何事物的实例存在。
0赞 Adam Pointer 3/21/2011
这是真的,也是它完全错误的另一个原因,但它仍然是一件有用的事情。
3赞 mario 3/21/2011 #6

是否可以使用变量变量将 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;
    }
}

评论

0赞 Matt Humphrey 3/21/2011
这是我已经计划好的,但只是检查没有其他方法。谢谢。
1赞 mario 3/21/2011
我们之前有过一次这个话题,我找不到链接。但结论是,没有更好/更合适的本地方式。无论如何,如果您需要此功能(通常或为了可读性),那么这是一种可以接受的方法。
0赞 CoR 11/19/2014
__set() 方法对于这个类来说是什么样子的?
0赞 plasmid87 3/21/2011 #7

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

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

2赞 Catalin Marin 3/21/2011 #8

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

得墨忒耳定律

如果您真的非常想要,请尝试以下操作: 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;

  }

}

评论

0赞 Matt Humphrey 3/21/2011
谢谢你的努力,但我决定走另一条路。
0赞 ianaré 3/21/2011 #9

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

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

评论

1赞 Matt Humphrey 3/21/2011
我也想过这一点,但后来想......错误编号。
0赞 Slava 3/21/2011 #10

除了制作之外,您还可以制作一个神奇的方法,该方法可以查看请求字段的内部对象。请记住,魔法方法有点慢。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
    }
}
0赞 Matt Humphrey 3/21/2011 #11

我决定放弃这整个方法,转而采用更啰嗦但更干净、很可能更有效率的方法。我一开始并不太热衷于这个想法,大多数人都在这里发言,为我下定决心。感谢您的回答。

编辑:

如果您有兴趣:

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;
}
0赞 xtofl 3/21/2011 #12

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

“仅检索”装饰代码示例:

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 上找到它)

-1赞 Josh S. 2/24/2016 #13

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

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

用法

echo getValueByPath($person,'contact->email');
// Returns the value of that object path
0赞 Katrina 8/17/2016 #14

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

$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
}

如果它并不总是两个功能,你可以更多地破解它以使其工作。关键是,只要使用括号格式,就可以使用变量变量调用链式函数。