Anthony Chambers

Engineer 81

Mutable vs Immutable

When is it OK to change your object state, and when is it not?

Written by Anthony Chambers , and read 1,279 times

Consider this code:

<?php
class User {
    public $name;

    public function setName($name)
    {
        $this->name = $name;
    }
}

I have a public property, $name, and I have a setter, setName(). Both of these allow me to change the state of my object.

Directly accessing the property allows me to set it to anything that I like, whereas the setter function can have validation etc added so that I'm not just putting any old data into the property.

Using either to change existing property values means that your object is mutable. This doesn't mean that you're muting it (ie silencing it), but rather that you are mutating it; changing it.

This is sometimes desirable, but imagine a case where you are looking at a log record. Logs should never be changed, so why would you need to mutate it? You should create a log record but you shouldn't be trying to change it afterwards.

If you've passed me a log object, I expect that the log data will be exactly the same as it was when it was retrieved from your logs themselves.

This would be bad:

<?php
$log = new LogRecord(time(), 'ALERT', 'User [ANTHONY] broke into the system');
$log->setSeverity('INFORMATION');
$log->setMessage('The weather is lovely outside');

new ReceivingLogObject($log);

My ReceivingLogObject has just received a LogRecord object that was instantiated in an alert state, and then modified to be a simple information record, and I no longer know that this user broke into the system.

What's the benefit of allowing this to be mutated?

Other issues that can be caused by mutable objects includes where you may be in a loop and are only performing certain actions under specific criteria. Consider this example:

<?php
$userObject = new User;
foreach ($arrayOfUsers as $user) {
    if ($user['firstName']) {
        $userObject->setFirstName($user['firstName']);
    }
    if ($user['lastName']) {
        $userObject->setLastName($user['lastName']);
    }
    $db->saveUser($userObject);
}

Seems safe enough, right? We're reusing the $userObject variable because we're trying to save creating loads of User objects, right?

So now imagine if $arrayOfUsers looks like this:

<?php
$arrayOfUsers = [
    ['firstName'=>'Anthony', 'lastName'=>'Chambers'],
    ['firstName'=>'John'],
    ['lastName'=>'Smith']
];

So when we save these three users, we'll save them as:

Well that's not at all what I meant to do, is it? But these kinds of things crop up a LOT when you're mutating existing objects. Think carefully about whether you actually need a mutable object or not. And if not, consider an immutable solution more like this:

<?php
foreach ($arrayOfUsers as $user) {
    $userObject = new User($user['firstName'], $user['lastName']);
    $db->saveUser($userObject);
}

In this case I wouldn't have any setters, because I don't want to allow someone to change some of the properties and leave others untouched, and I would have all properties set in the constructor which guarantees consistency.

In reality we would probably actually do something more like pass a database connection to the user object and an ID, and it would populate all of its properties itself, rather than us specify every property in the constructor.

The point is not about which solution is best, but rather that you should consider very carefully what the best way is for your specific use case.