How to solve the missing object properties in PHP?

Pavel S. picture Pavel S. · Aug 21, 2013 · Viewed 7.3k times · Source

This is a bit philosophical but I think many people encountered this problem. The goal is to access various (dynamically declared) properties in PHP and get rid of notices when they are not set.

Why not to __get?
That's good option if you can declare your own class, but not in case of stdClass, SimpleXML or similar. Extending them is not and option since you usually do not instantiate these classes directly, they are returned as a result of JSON/XML parsing.

Example:

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

We have simple stdClass object. The problems is obvious:

$b = $data->birthday;

The property is not defined and therefore a notice is raised:

PHP Notice:  Undefined property: stdClass::$birthday

This can happen very often if you consider that you get that object from parsing some JSON. The naive solution is obvious:

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

However, one gets tired very soon when wrapping every accessor into this. Especially when chaining the objects, such as $data->people[0]->birthday->year. Check whether people is set. Check if the first element is set. Check if birthday is set. Check if year is set. I feel a bit overchecked...

Question: Finally, my question is here.
What is the best approach to this issue? Silencing notices does not seem to be the best idea. And checking every property is difficult. I have seen some solutions such as Symfony property access but I think it is still too much boilerplate. Is there any simpler way? Either third party library, PHP setting, C extension, I don't care as far as it works... And what are the possible pitfalls?

Answer

Dmitri Zaitsev picture Dmitri Zaitsev · Aug 31, 2013

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 :)