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'
);
, А затем переход по карте для заполнения нового объекта. Думаю, я мог бы вместо этого включить картограф...
14 answers
На вашем месте я бы создал простой метод ->property();
, который возвращает $this->contact->phone
Можно ли использовать переменные переменные для вызова 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;
}
}
Попробуйте этот код
$property = $contact->phone;
echo $person->$property;
Я думаю, что это плохо, так как это приводит к нечитабельному коду, это просто неправильно и на других уровнях, но в целом, если вам нужно включить переменные в синтаксис объекта, вы должны заключить его в фигурные скобки, чтобы он был проанализирован первым.
Например:
$property = 'contact->phone';
echo $person->{$property};
То же самое применимо, если вам нужно получить доступ к объекту, в имени которого запрещены символы, что может регулярно происходить с объектами SimpleXML.
$xml->{a-disallowed-field}
Если это законно, это не значит, что это также морально. И это главная проблема с 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;
}
}
ООП во многом заключается в защите внутренних частей объекта от внешнего мира. То, что вы пытаетесь здесь сделать, - это предоставить способ опубликовать внутренности 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();
Вы можете использовать приведение типов для преобразования объекта в массив.
$person = (array) $person;
echo $person['contact']['phone'];
В большинстве случаев, когда у вас есть вложенные внутренние объекты, возможно, настало время пересмотреть ваши структуры данных.
В приведенном выше примере у человека есть контакт и dob. контакт также содержит адрес . Попытка доступа к данным с самого верхнего уровня не является редкостью при написании сложных приложений для баз данных. Тем не менее, вы можете найти лучшее решение для этого - объединить данные в класс person вместо того, чтобы пытаться по существу "заминировать" внутренние объекты.
Как бы мне ни было неприятно это говорить, вы могли бы провести оценку:
foreach ($properties as $property) {
echo eval("return \$person->$property;");
}
Помимо создания 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
}
}
Я решил отказаться от всего этого подхода и перейти к более многословному, но более чистому и, скорее всего, более эффективному. Я не был слишком увлечен этой идеей с самого начала, и большинство высказалось здесь, чтобы принять решение за меня. Спасибо вам за ваши ответы.
Изменить:
Если вам интересно:
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;
}
В случае, если вы используете свой объект структурным способом, вы можете явно смоделировать "путь" к запрошенному узлу. Затем вы можете "украсить" свои объекты одним и тем же кодом поиска.
Пример кода оформления "только для извлечения":
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;
(найдите его на кодовом контроллере)
Если вы знаете имена двух функций, могли бы вы это сделать? (не проверено)
$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
}
Если это не всегда две функции, вы могли бы взломать их больше, чтобы заставить их работать. Дело в том, что вы можете вызывать цепные функции с использованием переменных переменных, если вы используете формат скобок.
Самый простой и чистый способ, который я знаю.
function getValueByPath($obj,$path) {
return eval('return $obj->'.$path.';');
}
Использование
echo getValueByPath($person,'contact->email');
// Returns the value of that object path