Let's say you had a class that you wanted to create, but there was some sort of error in creating the object. This might be that the wrong parameters were passed, or the third party service (eg. a database) wasn't available at the time of creation.
If this happens you'll obviously want to handle the error correctly, but the question is would you throw an exception in the constructor or handle the error condition elsewhere?
Note that this article will talk about PHP, but the same discussion applies to other languages that implement OOP principles.
I did some reading on this subject and it appears that there's roughly two camps of thought with regards to this, these are:
- If there was a problem the object should exist, but shouldn't be usable.
- If there was a problem the object shouldn't exist if it is not usable.
Let's look into these two camps in detail.
The Object Should Exist
If there was an issue creating the object then it shouldn't matter, the object should still exist in some form. This sort of approach lends itself to minimalist constructors that just assign input parameters to properties and do little else.
For example, the following class has a single property called "number" that can be created in the constructor.
class ObjectWillExist {
public int $number;
public function __construct($number) {
if ($number !== 0) {
$this->number = $number;
}
}
}
If the number is set to the value "0" then the property won't be set.
$object = new ObjectWillExist(0);
var_dump($object);
This produces the following output.
object(ObjectWillExist)#1 (0) {
["number"]=>
uninitialized(int)
}
Detecting the number "0" in the above example is an arbitrary condition, but if this property contained a database connection or curl handle then the overall effect would be similar. The object would exist but essentially be in a broken state.
In order for this approach to work the class should also have helper methods that can be used to inspect the current state of the object. Without these methods in place there's a possibility that we would call the resource that didn't exist and produce an error.
The alternative approach to this is to remove the constructor entirely. This does lean towards the use of "init" or "connect" functions that gets things ready to use. These methods, however, are a bit of a coding smell. You either force developers to call a method before they can use your actual methods, or you need to copy/paste the same check into all of your class methods.
I should mention that since PHP 8.0 it has been possible to use constructor property promotion to automatically inject object properties via the constructor. The syntax to inject the $number property in this way would be as follows.
class ObjectWillExist {
public function __construct(protected int $number) {}
}
However we set things up, the object would be created and be usable in some way. Although it should ideally have some code to prevent it from causing errors in upstream code.
The Object Shouldn't Exist
In this case, the constructor tends to do more than just assigning variables. It will connect to databases or create curl handles to get the object into a state that can be used.
Crucially, if any of these steps happens to fail then the constructor will throw an error and not create the object.
Let's change the above example so that if the number "0" was sent to the constructor an exception is thrown.
class ObjectMayExist {
public int $number;
public function __construct($number) {
if ($number === 0) {
throw new Exception('Invalid value');
}
$this->number = $number;
}
}
In order for this to work the upstream code needs to be resilient to the error that the constructor might throw.
try {
$object = new ObjectMayExist(0);
} catch (Exception $e) {
// Object was not created.
}
In the example above, the $object variable would never have been set as the constructor throws an error before the value can be set.
This approach is more clear cut as you either get an object you can immediately use, or you get an error. You therefore don't need to add any helper code to the class to detect if the object has been created correctly. If the object exists then it will work as expected.
The only issue with this is that you do need to be a little bit more careful in larger applications, especially with dependency injection mechanisms in play. If the object was part of a injected dependency then there's a chance that the dependency will also fail to be created and potentially cause a cascade of errors.
Conclusion
I have always been in the camp of "the object should exist" but I've been thinking about this problem recently and have started to think that the "the object shouldn't exist" approach makes more sense. The code needs to be protected against exceptions, but it means less code involved in initializing or checking for the current state of things before using them.
I've started to think about some of the code I've written over the years and about how I have used initialization and check methods to check on the internal state of the object. Doing that sort of thing always felt like a code smell, but at the time I hadn't thought of the possibility of throwing an error in the constructor. Now I know of this technique I'll be seriously using it in the future.
What do you think the best approach is here? Is there a third camp that I haven't thought about here? Let me know in the comments!
Add new comment