Reputation: 1863
Is it a good idea to create objects that cannot be changed in PHP?
For example a date object which has setter methods, but they will always return a new instance of the object (with the modified date).
Would these objects be confusing to other people that use the class, because in PHP you usually expect the object to change?
Example
$obj = new Object(2);
$x = $obj->add(5); // 7
$y = $obj->add(2); // 4
Upvotes: 19
Views: 31549
Reputation: 7810
Making object immutable in PHP is pretty easy. Here is an elegant and convenient approach.
All you need to do is to create the base abstract class with the specific __get()
and __set()
magic methods and extend this base class in the child object.
This is quite applicable if you use value objects (e.g. for DDD).
Here is the base class:
abstract class BaseValueObject
{
public function __get(string $propertyName)
{
return $this->$propertyName;
}
public function __set(string $propertyName, $value): void
{
throw new \Exception("Cannot set property {$propertyName}. The object is immutable.");
}
}
Now a child object (well, its class).
class CategoryVO extends BaseValueObject
{
public $id;
public $name;
public function __construct(array $data)
{
$this->id = $data['id'];
$this->name = $data['name'];
}
}
It would throw an exception at attempt to set some value. Basically it is immutable.
This is it.
Make as many immutable objects as you need. Create the new objects via constructor. Dispose them and re-create the new ones when needed (add a specific creator method if required, a static or an instance one, to the base class or to the extended one).
Yet such an object would conveniently expose all its properties as read-only (for some kind of serialization or the like), unlike if we would have made them private (but even though we could use JsonSerializable interface to make the serialization as flexible as we need with private properties or even more drastic transformations).
Finally one cannot mistakenly instantiate BaseValueObject
as it is an abstract class. From all standpoints nice elegant solution.
Upvotes: 1
Reputation: 342
An immutable object cannot be changed after its initial creation so having setter methods makes no sense as it goes against that base principle.
You could implement some workarounds to simulate immutability in PHP by manipulating class member visibility and overriding the magic __set()
method but its not guaranteed immutable as immutability is not a feature of the PHP language.
I believe someone once wrote an extension to provide an immutable value type in PHP though so you could google for that.
Upvotes: 6
Reputation: 4783
If you want setters on a class and object this is perfectly fine, we do this all of the time as we need to set object data. Just simply don't call it immutable.
Many things in the dev world are subjective - our approaches, methodology etc - but "immutable" is a pretty solid definition:
"Immutable":
- Unchanging over time or unable to be changed.
If you want an immutable object it means it cannot be changed after instantiation. This is good for things such as data from a DB that needs to remain set in stone for the duration of the cycle.
If you need to call the object and set or change data on it after instantiation, this is not an immutable object.
Would you take 2 wheels off a car and calling it a motorbike?
There is some talk about methods on an "immutable" class being named without the word "set", but this doesn't stop the functionality of them being a method that sets data. You could call it thisDoesNotSetAnything(int $id)
and allow data to be passed in which changes the object. It'll be a setter, and thus the object is mutable.
Upvotes: 0
Reputation: 2011
From an immutable object, you can get its values but there is no way to modify them. Here you can see an example of an immutable class:
<?php
declare(strict_types=1);
final class Immutable
{
/** @var string */
private $value;
public static function withValue(string $value): self
{
return new self($value);
}
public function __construct(string $value)
{
$this->value = $value;
}
public function value(): string
{
return $this->value;
}
}
// Example of usage:
$immutable = Immutable::withValue("my value");
$immutable->value();
Upvotes: 0
Reputation: 1461
I made a little trait avoiding using Reflection to ease the implementation of immutability: https://github.com/jclaveau/php-immutable-trait
Obviously, as it's not a language feature, it won't impeach mutation by magic but lighten the code of the mutators that must clone the current instance before being applied. Applied to Massimiliano's example it would produce
class ImmutableValueObject
{
use JClaveau\Traits\Immutable;
private $val1;
private $val2;
public function __construct($val1, $val2)
{
$this->val1 = $val1;
$this->val2 = $val2;
}
public function getVal1()
{
return $this->val1;
}
public function withVal1($val1)
{
// Just add these lines at the really beginning of methods supporting
// immutability ("setters" mostly)
if ($this->callOnCloneIfImmutable($result))
return $result;
// Write your method's body as if you weren't in an Immutable class
$this->val1 = $val1;
return $this;
}
public function getVal2()
{
return $this->val2;
}
public function withVal2($val2)
{
if ($this->callOnCloneIfImmutable($result))
return $result;
$this->val2 = $val2;
return $this;
}
}
Hoping it can help some people here!
PS: Suggestions on this little feature are really welcome :) : https://github.com/jclaveau/php-immutable-trait/issues
Upvotes: 0
Reputation: 656
In my opinion objects should be immutable for value objects. Other than that it does not have much benefits unless you're sharing your object across your whole application.
There is some wrong answers here, an immutable object can have setters. Here's some implementation of immutable objects in PHP.
Example #1.
class ImmutableValueObject
{
private $val1;
private $val2;
public function __construct($val1, $val2)
{
$this->val1 = $val1;
$this->val2 = $val2;
}
public function getVal1()
{
return $this->val1;
}
public function getVal2()
{
return $this->val2;
}
}
As you can see once instantiated you cannot changed any value.
Example 2: with setters
:
class ImmutableValueObject
{
private $val1;
private $val2;
public function __construct($val1, $val2)
{
$this->val1 = $val1;
$this->val2 = $val2;
}
public function getVal1()
{
return $this->val1;
}
public function withVal1($val1)
{
$copy = clone $this;
$copy->val1 = $val1;
return $copy; // here's the trick: you return a new instance!
}
public function getVal2()
{
return $this->val2;
}
public function withVal2($val2)
{
$copy = clone $this;
$copy->val2 = $val2;
return $copy;
}
}
There is several implementation possible and this is by no means an exclusive list. And remember that with Reflection there is always a way to get around that in PHP, so immutability is all in your head in the end!
It is also often good practice to put immutable objects as final.
EDIT:
Upvotes: 16
Reputation: 318518
Immutable objects don't have setter methods. Period.
Everyone will expect a setXyz()
method to have a void return type (or return nothing in loosely typed languages). If you do add setter methods to your immutable object it will confuse the hell out of people and lead to ugly bugs.
Upvotes: 32