Objects are divided into mutable and immutable depending on the possibility to change. Objects that don’t change their internal state after creating are immutable. Otherwise, they are mutable.
Why should we think about it? This is an incomplete list of advantages for immutability:
- easier support in the future
- immutable objects are simpler to use
- immutable objects are simpler to test
- truly immutable objects side-effects free
- they are much easier to cache
From mutable to immutable objects
As a developer who slightly prefers product development, the most crucial part is work with objects for a long-term period. The immutable objects help to avoid inconvenience in the future.
Let’s dive into an example:
class Author {
private $first_name;
private $last_name;
public function __construct( string $first_name, string $last_name ) {
$this->first_name = $first_name;
$this->last_name = $last_name;
}
public function brief_name() {
return sprintf( '%s. %s', $this->first_name[0], $this->last_name );
}
}
class Document {
public $title;
private $author;
public function __construct( Author $author ) {
$this->author = $author;
}
public function information() {
return sprintf( '%s, %s', $this->title, $this->author->brief_name() );
}
}
$document = new Document( new Author( 'WP', 'Punk' ) );
echo $document->information() . PHP_EOL;
$document->title = 'How to write a beautiful code?';
echo $document->information() . PHP_EOL;
$document->title = 'Just avoid mutable objects.';
echo $document->information() . PHP_EOL;
Whenever we run the information method, we have various results. The first time the document had not the title, and it’s a side effect.
Logically, they are two different documents, but technically, we just renamed one document. We couldn’t be sure that the author for the second document still as for the first. So after creating our document, it’s possible to modify it. Anyone can change the document’s internal state, and it makes the support of this object more complicated.
Public properties are the awful enemy of immutable objects.
Imagine that you’ve read smartbooks, got cautious about the access modifiers, and started to use setters.
<?php
class Document {
private $title;
private $author;
public function __construct( Author $author ) {
$this->author = $author;
}
public function set_title( string $title ) {
$this->title = $title;
}
public function information() {
return sprintf( '%s, %s', $this->title, $this->author->brief_name() );
}
}
$document = new Document( new Author( 'WP', 'Punk' ) );
echo $document->information() . PHP_EOL;
$document->set_title( 'How to write a beautiful code?' );
echo $document->information() . PHP_EOL;
$document->set_title( 'Just avoid mutable objects.' );
echo $document->information() . PHP_EOL;
What has changed? Nothing, it’s just syntax sugar. This object still mutable and changed its own state. So, the second enemy of immutable objects is setters.
Imagine that you’ve read the awesome article about dependency injection and decided that you need to avoid setters.
<?php
class Document {
private $title;
private $author;
public function __construct( string $title, Author $author ) {
$this->title = $title;
$this->author = $author;
}
public function information() {
return sprintf( '%s, %s', $this->title, $this->author->brief_name() );
}
}
$document = new Document( 'How to write a beautiful code?', new Author( 'WP', 'Punk' ) );
echo $document->information() . PHP_EOL;
$document = new Document( 'Just avoid mutable objects.', new Author( 'WP', 'Punk' ) );
echo $document->information() . PHP_EOL;
And after these changes, we can’t reuse the created object and create only a new instance. Wait, this object immutable? Yep, absolutely. And again, we can be sure that dependency injection is a great pattern.
WordPress Hooks and mutability
As you can guess, WordPress hooks also can influence immutability.
<?php
class Document {
private $title;
private $author;
public function __construct( Author $author, string $title ) {
$this->author = apply_filters( 'document_author', $author, $title );
$this->title = apply_filters( 'document_title', $title, $author );
}
public function information() {
return sprintf( '%s, %s', $this->title, $this->author->brief_name() );
}
}
add_filter( 'document_author', function() {
return new Author( 'Rock', 'Star' );
} );
add_filter( 'document_title', function() {
return 'Just avoid mutable objects.';
} );
$document = new Document( 'How to write a beautiful code?', new Author( 'WP', 'Punk' ) );
echo $document->information() . PHP_EOL;
Hooks also can make your object mutable. Avoid these cases. The right way to allow users to change your public methods’ output, but don’t change the object’s internal state.
class Document {
private $title;
private $author;
public function __construct( Author $author, string $title ) {
$this->author = $author;
$this->title = $title;
}
public function information() {
return apply_filters(
'document_information',
sprintf(
'%s, %s',
$this->title,
$this->author->brief_name()
),
$this->title,
$this->author,
$this
);
}
}
add_filter( 'document_information', function( string $information, string $title, Author $author ) {
return sprintf( '%s %s', $author->brief_name(), $title );
}, 10, 3 );
$document = new Document( 'How to write a beautiful code?', new Author( 'WP', 'Punk' ) );
echo $document->information() . PHP_EOL;
As you can mention, there is a tiny difference between mutable and immutable objects. But a deep understanding of one simple rule that objects after creation shouldn’t change their internal state will allow you.
I know that you have examples when the usage of immutable objects is convenient. That’s a short way, but in fact, that’s mean that your object takes more than one responsibility by SOLID, and you are trying to add a creator role to the object by GRASP. I don’t want to offend anyone, but most often, this is a sign of poor architecture.
Frankly speaking, the right OOP way is creating one more class that will resolve this problem. But if you have a good example of when mutable objects are really comfortable to use, you’re welcome to the comments, and let’s discuss.