PHP: Цепочка свойств класса в переменных переменных


Итак, у меня есть объект со структурой, подобной приведенной ниже, все они возвращаются мне как 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'
);

, А затем переход по карте для заполнения нового объекта. Думаю, я мог бы вместо этого включить картограф...

Author: Matt Humphrey, 2011-03-21

14 answers

На вашем месте я бы создал простой метод ->property();, который возвращает $this->contact->phone

 3
Author: dynamic, 2011-03-21 09:41:39

Можно ли использовать переменные переменные для вызова contact->телефон как прямое свойство $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;
    }
}
 3
Author: mario, 2011-03-21 10:04:41

Попробуйте этот код

$property = $contact->phone;
echo $person->$property;
 2
Author: Poonam Bhatt, 2011-03-21 09:59:15

Я думаю, что это плохо, так как это приводит к нечитабельному коду, это просто неправильно и на других уровнях, но в целом, если вам нужно включить переменные в синтаксис объекта, вы должны заключить его в фигурные скобки, чтобы он был проанализирован первым.

Например:

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

То же самое применимо, если вам нужно получить доступ к объекту, в имени которого запрещены символы, что может регулярно происходить с объектами SimpleXML.

$xml->{a-disallowed-field}
 2
Author: Adam Pointer, 2011-03-21 10:04:11

Если это законно, это не значит, что это также морально. И это главная проблема с PHP, да, вы можете делать почти все, что вам приходит в голову, но это не делает его правильным. Взгляните на закон Деметры:

Закон Деметры

Попробуйте это, если вы действительно действительно хотите: json_decode(json_encode($человек), истина);

Вы сможете проанализировать его как массив, а не как объект, но он выполняет вашу работу для получения, а не для установка.

ИЗМЕНИТЬ:

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;

  }

}

 2
Author: Catalin Marin, 2011-03-21 10:29:10

ООП во многом заключается в защите внутренних частей объекта от внешнего мира. То, что вы пытаетесь здесь сделать, - это предоставить способ опубликовать внутренности phone через интерфейс person. Это нехорошо.

Если вам нужен удобный способ получить "все" свойства, вы можете написать явный набор удобных функций для этого, возможно, завернутый в другой класс, если хотите. Таким образом, вы можете развивать поддерживаемые утилиты без необходимости прикасаться (и, возможно, ломать) основные структуры данных:

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

}

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

print conv::phone( $p );

Если вам когда-нибудь понадобится более специализированная функция, вы добавите ее в утилиты. Это имхо решение nices: отделите удобство от ядра, чтобы уменьшить сложность и повысить ремонтопригодность/понятность.

Другой способ - "расширить" класс 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
Author: xtofl, 2011-03-21 09:54:24

Вы можете использовать приведение типов для преобразования объекта в массив.

$person = (array) $person;

echo $person['contact']['phone'];
 0
Author: Neddy, 2011-03-21 09:57:39

В большинстве случаев, когда у вас есть вложенные внутренние объекты, возможно, настало время пересмотреть ваши структуры данных.

В приведенном выше примере у человека есть контакт и dob. контакт также содержит адрес . Попытка доступа к данным с самого верхнего уровня не является редкостью при написании сложных приложений для баз данных. Тем не менее, вы можете найти лучшее решение для этого - объединить данные в класс person вместо того, чтобы пытаться по существу "заминировать" внутренние объекты.

 0
Author: plasmid87, 2011-03-21 10:12:59

Как бы мне ни было неприятно это говорить, вы могли бы провести оценку:

foreach ($properties as $property) {
     echo eval("return \$person->$property;");
}
 0
Author: ianaré, 2011-03-21 10:19:25

Помимо создания 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
Author: Slava, 2011-03-21 10:19:26

Я решил отказаться от всего этого подхода и перейти к более многословному, но более чистому и, скорее всего, более эффективному. Я не был слишком увлечен этой идеей с самого начала, и большинство высказалось здесь, чтобы принять решение за меня. Спасибо вам за ваши ответы.

Изменить:

Если вам интересно:

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
Author: Matt Humphrey, 2011-03-21 10:44:04

В случае, если вы используете свой объект структурным способом, вы можете явно смоделировать "путь" к запрошенному узлу. Затем вы можете "украсить" свои объекты одним и тем же кодом поиска.

Пример кода оформления "только для извлечения":

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;

(найдите его на кодовом контроллере)

 0
Author: xtofl, 2011-03-21 10:46:16

Если вы знаете имена двух функций, могли бы вы это сделать? (не проверено)

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

Если это не всегда две функции, вы могли бы взломать их больше, чтобы заставить их работать. Дело в том, что вы можете вызывать цепные функции с использованием переменных переменных, если вы используете формат скобок.

 0
Author: GreeKatrina, 2016-08-16 20:49:28

Самый простой и чистый способ, который я знаю.

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

Использование

echo getValueByPath($person,'contact->email');
// Returns the value of that object path
 -1
Author: Josh S., 2016-02-23 22:25:58