Thinking with Immutable Objects

by Woody Gilk

Lone Star PHP 2016

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.

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.


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;

$login = new LoginRequest(

if (!$this->authorization->isValid($login)) {
    throw AuthorizationException::invalidLogin($login);

// Carry on, lovely person!

Easy to create


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,


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)) {

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);


A number of different packages in this space.


use ValueObjects\Web\EmailAddress;

$email = new EmailAddress('');

echo $email->toNative(); //

// throws InvalidNativeArgumentException
$email = new EmailAddress('blah');
use ValueObjects\Person\Age;

$age = new Age(29);

echo $age->toNative(); // 29

// throws InvalidNativeArgumentException
$age = new Age(-1);


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


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.


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?


And I hope you think so too!


@shadowhand on Twitter, Github, etc.

Thinking With Immutable Objects

By Woody Gilk

Thinking With Immutable Objects

  • 2,609