简体   繁体   English

PHP:变量变量中的类属性链接

[英]PHP: Class property chaining in variable variables

So, I have a object with structure similar to below, all of which are returned to me as stdClass objects 所以,我有一个类似于下面结构的对象,所有这些都作为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;

etc... (note these examples are not linked together). 等...(注意这些例子没有链接在一起)。

Is it possible to use variable variables to call contact->phone as a direct property of $person ? 是否可以使用变量来调用contact->phone作为$person的直接属性?

For example: 例如:

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

This will not work as is and throws a E_NOTICE so I am trying to work out an alternative method to achieve this. 这将无法正常工作并抛出一个E_NOTICE所以我试图找出一种替代方法来实现这一目标。

Any ideas? 有任何想法吗?

In response to answers relating to proxy methods: 回答有关代理方法的答案:

And I would except this object is from a library and am using it to populate a new object with an array map as follows: 我想除了这个对象来自一个库,并使用它来填充一个带有数组映射的新对象,如下所示:

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

and then foreaching through the map to populate the new object. 然后通过地图预先填充新对象。 I guess I could envole the mapper instead... 我想我可以改为使用映射器......

If i was you I would create a simple method ->property(); 如果我是你,我会创建一个简单的方法->property(); that returns $this->contact->phone 返回$this->contact->phone

Is it possible to use variable variables to call contact->phone as a direct property of $person? 是否可以使用变量来调用contact-> phone作为$ person的直接属性?

It's not possible to use expressions as variable variable names. 不能将表达式用作变量变量名。

But you can always cheat: 但你总是可以作弊:

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

try this code 试试这段代码

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

I think this is a bad thing to to as it leads to unreadable code is is plain wrong on other levels too, but in general if you need to include variables in the object syntax you should wrap it in braces so that it gets parsed first. 我认为这是一件坏事,因为它导致无法读取的代码在其他级别上也是完全错误的,但是通常如果你需要在对象语法中包含变量,你应该将它包装在大括号中以便首先解析它。

For example: 例如:

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

The same applies if you need to access an object that has disalowed characters in the name which can happen with SimpleXML objects regularly. 如果您需要访问名称中包含不允许的字符的对象,这种情况同样适用于SimpleXML对象会定期发生的情况。

$xml->{a-disallowed-field}

If it is legal it does not mean it is also moral. 如果它是合法的,那并不意味着它也是道德的。 And this is the main issue with PHP, yes, you can do almost whatever you can think of, but that does not make it right. 这是PHP的主要问题,是的,你可以做任何你能想到的事情,但这并没有使它正确。 Take a look at the law of demeter: 看一下demeter法则:

Law of Demeter 得墨忒耳定律

try this if you really really want to: json_decode(json_encode($person),true); 如果你真的想要尝试这个:json_decode(json_encode($ person),true);

you will be able to parse it as an array not an object but it does your job for the getting not for the setting. 你将能够将它解析为一个数组而不是一个对象,但它完成了你的工作,而不是为了设置。

EDIT: 编辑:

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;

  }

} }

If you know the two function's names, could you do this? 如果您知道这两个函数的名称,可以这样做吗? (not tested) (未测试)

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

If it's not always two functions, you could hack it more to make it work. 如果它不总是两个功能,你可以更多地破解它以使其工作。 Point is, you can call chained functions using variable variables, as long as you use the bracket format. 要点是,只要使用括号格式,就可以使用变量变量调用链式函数。

OOP is much about shielding the object's internals from the outside world. OOP非常关注物体内部与外界的隔离。 What you try to do here is provide a way to publicize the innards of the phone through the person interface. 你在这里尝试做的是提供一种通过person界面宣传phone内部的方法。 That's not nice. 那不好。

If you want a convenient way to get "all" the properties, you may want to write an explicit set of convenience functions for that, maybe wrapped in another class if you like. 如果你想要一个方便的方法来获取“all”属性,你可能想要为它编写一组明确的便利函数,如果你愿意,也可以包装在另一个类中。 That way you can evolve the supported utilities without having to touch (and possibly break) the core data structures: 这样,您可以发展支持的实用程序,而无需触摸(并可能破坏)核心数据结构:

class conv {
 static function phone( $person ) {
   return $person->contact->phone;
 }

}

// imagine getting a Person from db
$person = getpersonfromDB();

print conv::phone( $p );

If ever you need a more specialized function, you add it to the utilities. 如果您需要更专业的功能,可以将其添加到实用程序中。 This is imho the nices solution: separate the convenience from the core to decrease complexity, and increase maintainability/understandability. 这是一个很好的解决方案:将便利性从核心中分离出来以降低复杂性,并提高可维护性/可理解性。

Another way is to 'extend' the Person class with conveniences, built around the core class' innards: 另一种方法是使用便利来“扩展” 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();

You could use type casting to change the object to an array. 您可以使用类型转换将对象更改为数组。

$person = (array) $person;

echo $person['contact']['phone'];

In most cases where you have nested internal objects, it might be a good time to re-evaluate your data structures. 在大多数嵌套内部对象的情况下,可能是重新评估数据结构的好时机。

In the example above, person has contact and dob . 在上面的例子中, 联系dob The contact also contains address . 联系人还包含地址 Trying to access the data from the uppermost level is not uncommon when writing complex database applications. 在编写复杂的数据库应用程序时,尝试从最高级别访问数据并不罕见。 However, you might find your the best solution to this is to consolidate data up into the person class instead of trying to essentially "mine" into the internal objects. 但是,您可能会发现最好的解决方案是将数据合并到person类中,而不是尝试将其“挖掘”到内部对象中。

As much as I hate saying it, you could do an eval : 尽管我讨厌说,你可以做一个评估:

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

Besides making function getPhone(){return $this->contact->phone;} you could make a magic method that would look through internal objects for requested field. 除了使function getPhone(){return $this->contact->phone;}您还可以制作一个神奇的方法,通过内部对象查看请求的字段。 Do remember that magic methods are somewhat slow though. 请记住,魔术方法有点慢。

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

I have decided to scrap this whole approach and go with a more long-winded but cleaner and most probably more efficient. 我决定废弃这整个方法,并采用更加冗长但更清洁,更有效的方式。 I wasn't too keen on this idea in the first place, and the majority has spoken on here to make my mind up for me. 我一开始并不是太热衷于这个想法,而且大多数人都在这里说过我的想法。 Thank for you for your answers. 谢谢你的回答。

Edit: 编辑:

If you are interested: 如果你感兴趣:

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

In case you use your object in a struct-like way, you can model a 'path' to the requested node explicitly. 如果您以类似结构的方式使用对象,则可以显式地为请求的节点建模“路径”。 You can then 'decorate' your objects with the same retrieval code. 然后,您可以使用相同的检索代码“装饰”您的对象。

An example of 'retrieval only' decoration code: “仅检索”装饰代码的示例:

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;

(find it on codepad ) (在键盘上找到它)

Simplest and cleanest way I know of. 我所知道的最简单,最干净的方式。

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

Usage 用法

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM