How to solve the missing object properties in PHP?

If I understand correctly, you want to deal with 3rd party Objects, where you have no control, but your logic requires certain properties that may not be present on the Object. That means, the data you accepting are invalid (or should be declared invalid) for your logic. Then the burden of checking the validity goes into your validator. Which I hope you already have following best practices to deal with 3rd party data. :)

You can use your own validator or one by frameworks. A common way is to write a set of Rules that your data needs to obey in order to be valid.

Now inside your validator, whenever a rule is not obeyed, you throw an Exception describing the error and attaching Exception properties that carry the information you want to use. Later when you call your validator somewhere in your logic, you place it inside try {...} block and you catch() your Exceptions and deal with them, that is, write your special logic reserved for those exceptions. As general practice, if your logic becomes too large in a block, you want to "outsource" it as function. Quoting the great book by Robert Martin "Clean Code", highly recommended for any developer:

The first rule of function is that they should be small. The second is that they should be smaller than that.

I understand your frustration dealing with eternal issets and see as cause of the problem here that each time you need to write a handler dealing with that technical issue of this or that property not present. That technical issue is of very low level in your abstraction hierarchy, and in order to handle it properly, you have to go all the way up your abstraction chain to reach a higher step that has a meaning for your logic. It is always hard to jump between different levels of abstraction, especially far apart. It is also what makes your code hard to maintain and is recommended to avoid. Ideally your whole architecture is designed as a tree where Controllers sitting at its nodes only know about the edges going down from them.

For instance, coming back to your example, the question is -

  • Q - What is the meaning for your app of the situation that $data->birthday is missing?

The meaning will depend on what the current function throwing the Exception wants to achieve. That is a convenient place to handle your Exception.

Hope it helps :)


One solution (I don't know if it's the better solution, but one possible solution) is to create a function like this:

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

then

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

I think it's convienent because you have at the top of your script what you want and what it should be (array, string, etc)

EDIT:

Another solution. You could create a Bridge class that extends 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

Personally I would prefer the first solution. It's more clear and more safe.