Who is this guy?
Long time contributor to open source
Equip, League, Kohana, DOMPDF, etc …
4587 contributions to 270 repositories and counting
Tea drinker
It's not just for the British!
What is immutability?
An immutable object is an object whose state cannot be modified after it is created.
An immutable object cannot be modified by accident.
- Clearly states intent to modify.
- Easier reason to reason about.
- Reduces cognitive load.
<?php
final class LoginRequest
{
private $username;
private $password;
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
public function __get($key)
{
return $this->$key;
}
}
But isn't using magic methods slow?!
Relatively speaking, yes.
In the real world, don't worry about it.
When should I use immutability?
Best fit for value objects and entities.
<?php
final class LoginRequest
{
private $username;
private $password;
public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}
public function __get($key)
{
return $this->$key;
}
}
<?php
$login = new LoginRequest(
$input['username'],
$input['password']
);
if (!$this->authorization->isValid($login)) {
throw AuthorizationException::invalidLogin($login);
}
// Carry on, lovely person!
Easy to create
<?php
function isValid(LoginRequest $login)
{
if (!$login->username || !$login->password) {
return false;
}
$user = $this->users->findBy([
'username' => $login->username,
]);
if (!$user) {
return false;
}
return password_verify($login->password, $user->password);
}
Easy to use
Still need to be convinced?
Given this code…
$widget = new Widget([
'price' => 5.00,
'color' => 'yellow',
'quantity' => 3,
]);
$cart->add($widget);
What is the state of $widget
?
Unless the $widget
is immutable we have to make assumptions.
What if the cart did this?
function add($item) {
$this->items[$item->id] = $item;
$item->in_cart = true;
}
A silent modification just happened.
This is relatively safe but what if there was a bug?
A junior developer might come along and do something silly.
A bug could be something trivial…
function add($item) {
$this->items[$item->id] = $item;
$item->quantity = 1;
}
The quantity just became corrupted!
These problems can be eliminated with immutability.
If we want to allow the cart to modify items…
function add($item) {
$item = $item->withInCart();
$this->items[$item->id] = $item;
return $item;
}
When should I not use it?
Classes that require heavy configuration.
Classes that hold shared state.
What packages use immutability?
- PSR-7 implementations
- Immutable primitives
- Data structures
PSR-7 with Zend Diactoros
use Zend\Diactoros\ServerRequestFactory;
$request = ServerRequestFactory::fromGlobals();
PSR-7 and REST
if ($request->getMethod() === 'GET') {
return $this->fetchResource($request);
}
if ($request->getMethod() === 'POST') {
return $this->createResource($request);
}
if ($request->getMethod() === 'PUT') {
return $this->updateResource($request);
}
PSR-7 and Query Strings
$query = $request->getUri()->getQuery();
// the query is a string, NOT an array, so we need to parse it
parse_str($query, $params);
PSR-7 with League URI
use League\Uri\Components\Query;
$query = new Query($request->getUri()->getQuery());
// easier to access things
if ($query->hasKey('q')) {
return $this->searchFor($query->getValue('q'));
}
PSR-7 and Request Bodies
$body = (string) $request->getBody();
// or read it as a stream
$body = $request->getBody();
while ($chunk = $body->read(1024)) {
$this->writeUpload($chunk);
}
PSR-7 and Responses
use Zend\Diactoros\Response;
use Zend\Diactoros\Stream;
$body = new Stream(json_encode($output));
$response = new Response;
$response = $response->withHeader('Content-Type', 'application/json');
$response = $response->withBody($body);
Primitives
A number of different packages in this space.
nicolopignatelli/valueobjects
use ValueObjects\Web\EmailAddress;
$email = new EmailAddress('me@example.com');
echo $email->toNative(); // me@example.com
// throws InvalidNativeArgumentException
$email = new EmailAddress('blah');
use ValueObjects\Person\Age;
$age = new Age(29);
echo $age->toNative(); // 29
// throws InvalidNativeArgumentException
$age = new Age(-1);
sebastian/money
use SebastianBergmann\Money\Currency;
use SebastianBergmann\Money\Money;
$currency = new Currency('USD');
$price = new Money(100, $currency);
$discount = new Money(5, $currency);
$discounted = $price->subtract($discount);
var_dump($price === $discounted); // false
echo $discounted->getAmount(); // 90
DataValues/DataValues
use DataValues\QuantityValue;
use DataValues\Geo\Values\LatLongValue;
$quantity = QuantityValue::newFromNumber(5);
$coords = new LatLongValue(32.962747, -96.827570); // you are here!
Quality package covering a number of interesting types.
Data Structures
A number of different packages in this space, too.
equip/structure
use Equip\Structure\Dictionary;
$dict = new Dictionary([
'username' => 'shadowhand',
'password' => 'too-secure-for-you',
]);
echo $dict['username']; // shadowhand
$dict = $dict->withValue('username', 'tron');
echo $dict['username']; // tron
// If unsure if something exists?
var_dump($dict->getValue('famous')); // null
use Equip\Structure\Set;
use Equip\Structure\OrderedList;
$set = new Set([1, 5, 100, 20, 1]);
var_dump($set->toArray()); // [1, 5, 100, 20]
$set = $set->withValues([5, 100]);
var_dump($set->hasValue(5)); // true
var_dump($set->hasValue(20)); // false
$list = new OrderedList($set->toArray());
Other notable packages?
Guzzle 6
league/period
ramsey/uuid
estvoyage/value
DateTimeImmutable
Are immutable data structures useful?
Definitely!
And I hope you think so too!
Thinking With Immutable Objects
By Woody Gilk
Thinking With Immutable Objects
- 3,079