2013-08-21 4 views
10

Это немного философски, но я думаю, что многие люди столкнулись с этой проблемой. Целью является доступ к различным (динамически объявленным) свойствам в PHP и избавление от уведомлений, когда они не установлены.Как решить недостающие свойства объекта в PHP?

Почему не до __get?
Это хороший вариант, если вы можете объявить свой собственный класс, но не в случае stdClass, SimpleXML или аналогичного. Расширение их не является и вариантом, поскольку вы обычно не создаете экземпляр этих классов напрямую, они возвращаются в результате разбора JSON/XML.

Пример:

$data = '{"name": "Pavel", "job": "programmer"}'; 
$object = json_decode($data); 

Мы имеем простой stdClass объект. Проблемы очевидны:

$b = $data->birthday; 

Свойства не определены и, следовательно, уведомление поднимается:

PHP Notice: Undefined property: stdClass::$birthday 

Это может произойти очень часто, если вы считаете, что вы получите этот объект из анализа некоторых JSON. Наивное решение очевиден:

$b = isset($data->birthday) ? $data->birthday : null; 

Однако, очень быстро устаете, когда обматываете каждый аксессуар этим. Особенно при соединении объектов, таких как $data->people[0]->birthday->year. Проверьте, установлен ли people. Проверьте, установлен ли первый элемент. Проверьте, установлено ли значение birthday. Проверьте, установлен ли year. Я чувствую себя немного перепроверенным ...

Вопрос: Наконец, мой вопрос здесь.
Каков наилучший подход к этой проблеме? Замечания о замораживании, по-видимому, не лучшая идея. И проверять каждое свойство сложно. Я видел некоторые решения, такие как Symfony property access, но я думаю, что это еще слишком много шаблонов. Есть ли более простой способ? Или сторонняя библиотека, настройка PHP, расширение C, мне все равно, насколько это работает ... И каковы возможные подводные камни?

+2

* «Тем не менее, очень быстро устаешь, когда обматываете каждый аксессуар этим. Особенно, когда цепочки объектов, такие как $ data-> people [0] -> birthday-> year." * Я не Понимаю. Вы можете безопасно выполнять 'isset ($ data-> people [0] -> birthday-> year)', даже если '$ data' равно null без каких-либо побочных эффектов. – netcoder

+0

Это очень хороший момент, я не знал об этом. Благодаря! (И все же существует ли более короткая версия '$ y = isset ($ x)? $ X: null;' –

+0

Нет. Если вы посмотрите на другие языки, у PHP на самом деле есть кратчайшая форма. , вам придется проверять каждый подэлемент на null вручную. В Python вам придется окружить его блоком 'try..except'. Если вы действительно хотите короче, вы всегда можете написать небольшую функцию обертки. – netcoder

ответ

3

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

Вы можете использовать свой собственный валидатор или один с помощью фреймворков. Обычный способ - написать набор правил, которым ваши данные должны подчиняться, чтобы быть действительными.

Внутри вашего валидатора, когда правило не соблюдается, вы принимаете throw Исключение, описывающее ошибку, и добавление свойств исключения, которые несут информацию, которую вы хотите использовать. Позже, когда вы вызываете свой валидатор где-то в своей логике, вы помещаете его внутри блока try {...}, и вы получаете catch() свои Исключения и обрабатываете их, то есть записываете специальную логику, зарезервированную для этих исключений. Как общая практика, если ваша логика становится слишком большой в блоке, вы хотите «перенаправить» ее в качестве функции. Цитируя большую книгу Роберта Мартина «Чистый код», настоятельно рекомендуется для любого разработчика:

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

Я понимаю ваше разочарование дело с вечными isset с и увидеть, как причину проблемы здесь, что каждый раз, когда вам нужно написать обработчик дело с этой технической проблемы того или иного имущества нет. Эта техническая проблема имеет очень низкий уровень в вашей иерархии абстракции, и для того, чтобы правильно ее обрабатывать, вы должны пройти всю свою цепочку абстракции, чтобы достичь более высокого шага, который имеет значение для вашей логики. Всегда трудно прыгать между различными уровнями абстракции, особенно далеко друг от друга. Это также делает ваш код сложным для обслуживания и рекомендуется избегать. В идеале вся ваша архитектура спроектирована как дерево, где контроллеры, сидящие на своих узлах, знают только о том, что ребра уходят от них.

Например, возвращаясь к вашему примеру, вопрос -

  • Q - Что означает для вашего приложения ситуации, что $data->birthday не хватает?

Значение будет зависеть от того, что хочет выполнить текущая функция, бросающая Exception. Это удобное место для обработки вашего Исключения.

Надеется, что это помогает :)

2

Форматы данных, которые имеют необязательные поля, с которыми сложно справиться. Они особенно проблематичны, если у вас есть сторонние лица, которые обращаются к данным или предоставляют данные, поскольку редко достаточно документации для всестороннего изучения всех причин появления или исчезновения полей. И, конечно, перестановки, как правило, сложнее тестировать, потому что кодеры не будут инстинктивно понимать, что поля могут быть там.

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

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

Однако, если вы это сделаете, вы терпите неудачу, разве что? (Это еще одна философия программирования, чтобы принять в сердце.) Я полагаю, можно было бы утверждать, что предоставление безопасных параметров по умолчанию удовлетворяет проверке данных для любых отсутствующих полей. Но особенно при работе с данными третьих сторон вы должны использовать высокий уровень паранойи для любого поля, на котором вы штукатурки со значениями по умолчанию. Слишком просто просто установить его на нуль и - в процессе - не понять , почему его не хватало в первую очередь.

Вы также должны спросить, чего вы пытаетесь достичь? Ясность? Безопасность? Стабильность? Минимальное дублирование кода? Это все действительные цели. Устали? В меньшей степени. Это говорит о недостатке дисциплины, и хороший программист всегда является дисциплинированным. Конечно, я соглашусь с тем, что люди с меньшей вероятностью что-то делают, если они рассматривают это как тяжесть.

Мое мнение, ответ на ваш вопрос может отличаться в зависимости от почему его спрашивают. Решение с нулевым усилием, вероятно, не будет доступно, поэтому, если вы только обмениваетесь одной задачей программирования на другое, решаете ли вы что-нибудь?

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

+0

Спасибо за это. И +1 для значений по умолчанию и шаблонов Я буду рад услышать и другие подходы. –

1

Лучших ответов были даны, но вот ленивая:

$data = '{"name": "Pavel", "job": "programmer"}'; 
$object = json_decode($data); 
if(
    //...check mandatory properties: !isset($object->...)&& 
    ){ 
    //error 
} 
error_reporting(E_ALL^E_NOTICE);//Yes you're right, not the best idea... 
$b = $object->birthday?:'0000-00-00';//thanks Elvis (php>5.3) 
//Notice that if your default value is "null", you can just do $b = $object->birthday; 
//assign other vars here 
error_reporting(E_ALL); 
//Your code 
3

Одно решения (я не знаю, если это лучшее решение, но один Возможное решение) заключается в создании функции, как это:

function from_obj(&$type,$default = "") { 
    return isset($type)? $type : $default; 
} 

затем

$data = '{"name": "Pavel", "job": "programmer"}'; 
$object = json_decode($data); 

$name = from_obj($object->name  , "unknown"); 
$job = from_obj($object->job  , "unknown"); 
$skill = from_obj($object->skills[0] , "unknown"); 
$skills = from_obj($object->skills , Array()); 

echo "Your name is $name. You are a $job and your main skill is $skill"; 

if(count($skills) > 0) { 
    echo "\n\nYour skills: " . implode(",",$skills); 
} 

Я думаю, что это convienent, потому что у вас есть в верхней части вашего сценария, что вы хотите, и что она должна быть (массив, строка, и т.д.)

EDIT:

Другим решением. Вы можете создать класс Bridge, который расширяет ArrayObject:

class ObjectBridge extends ArrayObject{ 
    private $obj; 
    public function __construct(&$obj) { 
     $this->obj = $obj; 
    } 

    public function __get($a) { 
     if(isset($this->obj->$a)) { 
      return $this->obj->$a; 
     }else { 
      // return an empty object in order to prevent errors with chain call 
      $tmp = new stdClass(); 
      return new ObjectBridge($tmp); 
     } 
    } 
    public function __set($key,$value) { 
     $this->obj->$key = $value; 
    } 
    public function __call($method,$args) { 
     call_user_func_array(Array($this->obj,$method),$args); 
    } 
    public function __toString() { 
     return ""; 
    } 
} 

$data = '{"name": "Pavel", "job": "programmer"}'; 
$object = json_decode($data); 

$bridge = new ObjectBridge($object); 

echo "My name is {$bridge->name}, I have " . count($bridge->skills). " skills and {$bridge->donald->duck->is->paperinik}<br/>"; 
// output: My name is Pavel, I have 0 skills and 
// (no notice, no warning) 

// we can set a property 
$bridge->skills = Array('php','javascript'); 

// output: My name is Pavel, my main skill is php 
echo "My name is {$bridge->name}, my main skill is {$bridge->skills[0]}<br/>"; 


// available also on original object 
echo $object->skills[0]; // output: php 

Лично я бы предпочел первое решение. Это более понятно и безопаснее.

0
function check($temp=null) { 
if(isset($temp)) 
return $temp; 

else 
return null; 
} 

$b = check($data->birthday); 
+0

Будьте осторожны при возврате null - он может создавать s и рекомендуется избегать дядей Бобом. –

+0

Это не сработает, потому что ошибка вызовет перед вызовом функции. –

1

Использовать объект прокси - он будет добавлять только один крошечный класс и одну строку для экземпляра объекта для его использования.

class ProxyObj { 
    protected $obj; 
    public function __construct($obj) { 
     $this->_obj = $obj; 
    } 
    public function __get($key) { 
    if (isset($this->_obj->$key)) { 
     return $this->_obj->$key; 
    } 
    return null; 
    } 
    public function __set($key, $value) { 
     $this->_obj->$key = $value; 
    } 
} 

$proxy = new ProxyObj(json_decode($data)); 
$b = $proxy->birthday; 
+2

Проблема заключается в вложенных объектах. __get() должен проверить, является ли '($ this -> _ obj -> $ key instanceof StdClass)' и возвращать 'return new self ($ this -> _ obj -> $ key);' if true. – popthestack

+1

Кроме того, возврат 'null' приведет к ошибке' $ obj-> user-> birthday', если 'user' не существует. Однако вы не можете вернуть «ProxyObj» на свое место, потому что тогда '$ obj-> user-> birthdayBlah' вернет объект. '__toString' может помочь здесь, но не полностью решить проблему. – popthestack

0

Я ударил эту проблему, главным образом, от получения данных JSon из NoSQL поддерживаемого API, что дизайн имеет несовместимые структуры, например, если пользователь имеет адрес, который вы получите $ user-> адрес в противном случае адрес ключа просто нет. Вместо того, чтобы ставить тонны issets в моих шаблонах я написал этот класс ...

class GracefulData 
{ 
    private $_path; 
    public function __construct($d=null,$p='') 
    { 
     $this->_path=$p; 
     if($d){ 
      foreach(get_object_vars($d) as $property => $value) { 
       if(is_object($d->$property)){ 
        $this->$property = new GracefulData($d->$property,$this->_path . '->' . $property); 
       }else{ 
        $this->$property = $value; 
       } 
      } 
     } 
    } 
    public function __get($property) { 
     return new GracefulData(null,$this->_path . '->' . $property); 
    } 
    public function __toString() { 
     Log::info('GracefulData: Invalid property accessed' . $this->_path); 
     return ''; 
    } 
} 

, а затем создать его экземпляр, как так

$user = new GracefulData($response->body); 

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

$ user-> firstName-> что-то

0

Вы можете декодировать объект JSON в массив:

$data = '{"name": "Pavel", "job": "programmer"}'; 
$jsonarray = json_decode($data, true); 
$b = $jsonarray["birthday"]; // NULL