rfc:immutability

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Next revisionBoth sides next revision
rfc:immutability [2016/08/12 15:16] – Sematics marijic.silviorfc:immutability [2016/12/02 14:54] marijic.silvio
Line 2: Line 2:
   * Version: 0.1   * Version: 0.1
   * Date: 2016-08-12   * Date: 2016-08-12
-  * Authors: Michal Brzuchalski <michal@brzuchalski.com>Silvio Marijic <marijic.silvio@gmail.com>+  * Author: Michal Brzuchalski <michal@brzuchalski.com> 
 +  * Author: Silvio Marijic <marijic.silvio@gmail.com>
   * Status: In Draft   * Status: In Draft
-  * First Published at: 2016-08-12+  * First Published at: [[rfc:immutability|https://wiki.php.net/rfc/immutability]]
  
 ===== Introduction ===== ===== Introduction =====
  
-This RFC proposes introduction of immutable classes and properties. Currently PHP lacks native support for immutability. Because of that user-land applications are using third party libraries or resort to custom implementations and still there is no easy enforcement of immutability. Introducing this feature would help bring one unified solution to this problem, and also it would remove unnecessary logic from user-land applications.+This RFC proposes introduction of immutable classes and properties. Currently the only way of achieving immutability is trough encapsulation. Because of that user-land applications are using third party libraries or resort to custom implementations and still there is no easy enforcement of immutability. Introducing this feature would help bring one unified solution to this problem, and also it would remove unnecessary logic from user-land applications
 +If those mechanizsm is utilized, developer can be sure that he is programming with no side effects, meaning that state of the object can not be changed with developer beeing unaware of that. This especialy comes hand in hand with concurrency when we can guarantee that threads will always read the same value.
  
  
 **Pros** **Pros**
   - Immutability guaranteed by language instead of user-land implementation.   - Immutability guaranteed by language instead of user-land implementation.
 +  - Programming with no side effects.
   - Safe for concurrency.    - Safe for concurrency. 
   - Value objects, DTO's etc. can be easily created.   - Value objects, DTO's etc. can be easily created.
Line 22: Line 25:
  
  
-**Before**+===== Proposal ===== 
 + 
 +==== Immutable Class ==== 
 + 
 +Class defined as immutable will imply immutability across all of it's properties by default. After object is constructor, it is not possible to modify state of object from any scope. 
 <code php> <code php>
-<?php 
  
-class Email { +immutable class Email { 
-  private $_email;+  public $email;
  
   public function __construct ($email) {   public function __construct ($email) {
-    // validation +    $this->email = $email;
- +
-    $this->_email = $email;+
   }   }
 +}
  
-  public function getValue() { +$email = new Email("foo@php.net"); 
-     return $this->_email;+$email->email = "bar@php.net" // Call will result in Fatal Error 
 + 
 +</code> 
 + 
 +Changes to inheritance are made to add constraints when extending a immutable class, child class must also be immutable in order to to preserve immutability across whole object. 
 + 
 +<code php> 
 +immutable class Foo{} 
 +class Bar extends Foo{} // Will result in Fatal Error 
 +</code> 
 + 
 + 
 +==== Immutable Properties ==== 
 + 
 +Classes have ability to enforce immutability to only subset of properties if needed, in that case immutability will be implied only on properties that are delared immutable. 
 + 
 +<code php> 
 +class User { 
 +  private $id; 
 +  public immutable $email; 
 + 
 +  public function __construct ($id, $email) { 
 +    $this->id = $id; 
 +    $this->email = $email;
   }   }
 } }
 </code> </code>
  
-**After**+ 
 +If immutable property contains object, to preserve immutability, object that is beeing assigned to immutable property must also immutable.
 <code php> <code php>
-<?php+immutable class Email{}
  
-immutable class Email +class User 
-  public $email; +  public immutable $email;
- +
-  public function __construct ($email) { +
-    // validation+
  
 +  public function __construct (Email $email) {
     $this->email = $email;     $this->email = $email;
   }   }
Line 56: Line 84:
 </code> </code>
  
 +Resources are not allowed to be assigned to immutable properties because of fact that resources by nature are not immutable.
 +<code php>
 +class File {
 +  public immutable $handle;
  
 +  public function __construct ($handle) {
 +    $this->handle = $handle;
 +  }
 +}
  
 +$file = new File(fopen('file.txt'));
 +</code>
  
 +If immutable property of parent class is overridden in child class, it has to be declared as immutable.
 +Since focus of this RFC is on immutable objects, having static properties in immutable classes will result in compile error.
  
-===== Proposal ===== 
  
-Class defined as immutable will imply immutability across all of it's properties by default. Regular classes can define immutability per property. After constructor returns, it will not be possible to modify state of immutable properties from any scope. Any class that extends immutable class must be also declared as immutable. It will not be possible to assign value by reference to immutable property. If immutable property contains object, object must also be an instance of immutable class.+==== References ====
  
-<code php>+Assigning by references to immutable properties will result with error, otherwise object looses controll over properties and immutability can be broken.
  
 +<code php>
 immutable class Email { immutable class Email {
   public $email;   public $email;
Line 77: Line 117:
  
 $email = new Email("foo@php.net"); $email = new Email("foo@php.net");
-$email->email = "bar@php.net" // Call will result in Fatal Error+$emailRef = &$email->email
 +$emailRef = "bar@php.net" // Call will result in Fatal Error 
 +</code>
  
 +
 +===== Examples =====
 +Notice in above examples removing getters and setting properties to public is optional. They simply doesn't need to be protected anymore in fact that immutable class objects are deeply frozen with eceptions on write.
 +
 +Every example shows where internal object state is important. Any references to objects passed into immutable class constructor cannot be references to scalars or may be immutable class instances. That gives high guarantee you will keep internal state unchanged for all the time.
 +
 +----
 +
 +
 +==== Money ====
 +
 +Money Pattern, defined by Martin Fowler and published in Patterns of Enterprise Application Architecture, is a great way to represent value-unit pairs. It is called Money Pattern because it emerged in a financial context.
 +
 +<code php>
 +class Currency {
 +
 +    private $centFactor;
 +    private $stringRepresentation;
 +
 +    private function __construct(int $centFactor, string $stringRepresentation) {
 +        $this->centFactor = $centFactor;
 +        $this->stringRepresentation = $stringRepresentation;
 +    }
 +
 +    public function getCentFactor() : int {
 +        return $this->centFactor;
 +    }
 +
 +    public function getStringRepresentation() : string {
 +        return $this->stringRepresentation;
 +    }
 +
 +    public static function USD() : Currency {
 +        return new self(100, 'USD');
 +    }
 +
 +    public static function EUR() : Currency {
 +        return new self(100, 'EUR');
 +    }
 +}
 +
 +class Money {
 +
 +    private $amount;
 +    private $currency;
 +
 +    public function __construct($amount, Currency $currency) {
 +        $this->amount = $amount;
 +        $this->currency = $currency;
 +    }
 +
 +    public function getAmount() : float {
 +        return $this->amount;
 +    }
 +
 +    public function getCurrency() : Currency {
 +        return $this->currency;
 +    }
 +
 +    public function add(Money $other) : Money {
 +        $this->ensureSameCurrencyWith($other);
 +        return new Money($this->amount + $other->getAmount(), $this->currency);
 +    }
 +
 +    public function subtract(Money $other) {
 +        $this->ensureSameCurrencyWith($other);
 +        return new Money($this->amount - $other->getAmount(), $this->currency);
 +    }
 +
 +    public function multiplyBy($multiplier, $roundMethod = PHP_ROUND_HALF_UP) {
 +        $product = round($this->amount * $multiplier, 0, $roundMethod);
 +        return new Money($product, $this->currency);
 +    }
 +
 +    private function ensureSameCurrencyWith(Money $other) {
 +        if ($this->currency != $other->getCurrency()) {
 +            throw new \Exception("Both Moneys must be of same currency");
 +        }
 +    }
 +}
 +
 +$oneThousand = new Money(1000, Currency::USD());
 </code> </code>
 +
 +After refactoring classes to immutable this example will look like this:
 +
 +<code php>
 +immutable class Currency {
 +
 +    /** @var int */
 +    public $centFactor;
 +    /** @var string */
 +    public $stringRepresentation;
 +
 +    private function __construct(int $centFactor, string $stringRepresentation) {
 +        $this->centFactor = $centFactor;
 +        $this->stringRepresentation = $stringRepresentation;
 +    }
 +
 +    public static function USD() : Currency {
 +        return new self(100, 'USD');
 +    }
 +
 +    public static function EUR() : Currency {
 +        return new self(100, 'EUR');
 +    }
 +}
 +
 +immutable class Money {
 +
 +    /** @var float */
 +    public $amount;
 +    /** @var Currency */
 +    public $currency;
 +
 +    public function __construct(float $amount, Currency $currency) {
 +        $this->amount = $amount;
 +        $this->currency = $currency;
 +    }
 +
 +    public function add(Money $other) : Money {
 +        $this->ensureSameCurrencyWith($other);
 +        return new Money($this->amount + $other->amount, $this->currency);
 +    }
 +
 +    public function subtract(Money $other) {
 +        $this->ensureSameCurrencyWith($other);
 +        return new Money($this->amount - $other->amount, $this->currency);
 +    }
 +
 +    public function multiplyBy($multiplier, $roundMethod = PHP_ROUND_HALF_UP) {
 +        $product = round($this->amount * $multiplier, 0, $roundMethod);
 +        return new Money($product, $this->currency);
 +    }
 +
 +    private function ensureSameCurrencyWith(Money $other) {
 +        if ($this->currency != $other->currency) {
 +            throw new \Exception("Both Moneys must be of same currency");
 +        }
 +    }
 +}
 +
 +$oneThousand = new Money(1000, Currency::USD());
 +</code>
 +
 +There is no need for getters because internally immutable object if deeply frozen and none of his properties cannot be written anymore. All properties accepts scalar values or objects which implements immutable classes so there is high guarantee such Money object will keep his internal state untouched.
  
  
Line 98: Line 285:
  
 ==== To Existing Extensions ==== ==== To Existing Extensions ====
- - Reflection.+ - Reflection is patched.
  
 ==== To Opcache ==== ==== To Opcache ====
Line 127: Line 314:
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
- +[[https://github.com/php/php-src/compare/master...brzuchal:immutable]]
  
  
Line 138: Line 324:
  
 ===== References ===== ===== References =====
- 
  
  
 ===== Rejected Features ===== ===== Rejected Features =====
 + - Immutable interfaces
rfc/immutability.txt · Last modified: 2018/02/20 11:19 by marijic.silvio