Как решить недостающие свойства объекта в 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, но я думаю, что это еще слишком много шаблонов. Есть ли более простой способ? Или сторонняя библиотека, настройка PHP, расширение C, мне все равно, насколько это работает... И каковы возможные подводные камни?

Ответ 1

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

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

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

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

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

Например, вернемся к вашему примеру, вопрос:

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

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

Надеюсь, это поможет:)

Ответ 2

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

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

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

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

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

Ответ 3

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

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

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

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

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

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

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

Ответ 4

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

$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

Ответ 5

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

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;

Ответ 6

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

else
return null;
}

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

Ответ 7

Я столкнулся с этой проблемой, главным образом, из-за получения json-данных из поддерживаемой nosql api, которая по дизайну имеет несогласованные структуры, например, если у пользователя есть адрес, вы получите $user- > address, иначе адресный ключ просто isn ' там. Вместо того, чтобы ставить тонны 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- > что-то

Ответ 8

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

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