rfc:immutability
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:immutability [2016/08/16 17:26] – marijic.silvio | rfc:immutability [2018/02/20 11:19] (current) – marijic.silvio | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Immutable classes and properties ====== | ====== PHP RFC: Immutable classes and properties ====== | ||
- | * Version: 0.1 | + | * Version: 0.2 |
- | * Date: 2016-08-12 | + | * Date: 2018-02-19 |
* Author: Michal Brzuchalski < | * Author: Michal Brzuchalski < | ||
* Author: Silvio Marijic < | * Author: Silvio Marijic < | ||
- | * Status: In Draft | + | * Status: In Discussion |
* First Published at: [[rfc: | * First Published at: [[rfc: | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | This RFC proposes introduction of immutable classes and properties. Currently | + | This RFC proposes |
+ | If this mechanism is introduced, developers can be sure that they are programming with no side effects, meaning that state of the object can not be changed without developers being made aware. This is especially useful when dealing with concurrency, | ||
**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. | ||
- | - Properties can be public which removes need for getters without allowing state modification. | + | - Properties can be public which removes |
- (Please point it out more advantages) | - (Please point it out more advantages) | ||
**Cons** | **Cons** | ||
- | - Cloning has to be disabled for immutable | + | - Currently arrays on immutable |
- | - (Please point it out more disadvantages) | + | |
- | **Before** | + | ===== Proposal ===== |
+ | |||
+ | ==== Immutable Class ==== | ||
+ | |||
+ | A class defined as immutable will imply immutability across all of its properties by default. After the object is constructed, | ||
<code php> | <code php> | ||
- | <?php | ||
- | class Email { | + | immutable |
- | | + | |
public function __construct ($email) { | public function __construct ($email) { | ||
- | | + | $this->email = $email; |
- | + | ||
- | | + | |
- | } | + | |
- | + | ||
- | public function getValue() { | + | |
- | | + | |
} | } | ||
} | } | ||
+ | |||
+ | $email = new Email(" | ||
+ | $email-> | ||
+ | |||
</ | </ | ||
- | **After** | + | Changes to inheritance are made to add constraints when extending an immutable class - the child class must also be immutable in order to preserve immutability across the whole object. |
<code php> | <code php> | ||
- | <?php | + | immutable class Foo{} |
+ | class Bar extends Foo{} // Will result in Fatal Error | ||
+ | </code> | ||
- | immutable class Email { | ||
- | public $email; | ||
- | public function __construct ($email) { | + | ==== Immutable Properties ==== |
- | // validation | + | |
+ | Classes have the ability to enforce immutability to only a subset of properties if needed, in that case, immutability will be implied only on properties that are declared as immutable. | ||
+ | |||
+ | <code php> | ||
+ | class User { | ||
+ | private $id; | ||
+ | public immutable $email; | ||
+ | |||
+ | public function __construct ($id, $email) { | ||
+ | $this-> | ||
$this-> | $this-> | ||
} | } | ||
Line 59: | Line 71: | ||
+ | If an immutable property contains an object, to preserve immutability, | ||
+ | <code php> | ||
+ | immutable class Email{} | ||
+ | class User { | ||
+ | public immutable $email; | ||
+ | public function __construct (Email $email) { | ||
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | </ | ||
- | ===== Proposal ===== | + | 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; | ||
- | Class defined as immutable will imply immutability across all of it's properties by default. After constructor returns, it will not be possible to modify state of immutable properties from any scope. | + | public function __construct ($handle) { |
+ | $this-> | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $file = new File(fopen('file.txt' | ||
+ | </ | ||
+ | Arrays are not allowed to be assigned to immutable properties.. | ||
<code php> | <code php> | ||
+ | class A { | ||
+ | public immutable $x; | ||
+ | public function __construct ($x) { | ||
+ | $this->x = $x; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $a = new A([' | ||
+ | </ | ||
+ | |||
+ | If an immutable property of a parent class is overridden in a child class, it has to be declared as immutable. | ||
+ | Since the focus of this RFC is immutable objects, having static properties in immutable classes will result in a compile error. | ||
+ | |||
+ | |||
+ | ==== References ==== | ||
+ | |||
+ | Assigning by references to immutable properties will result in an error, otherwise the object loses control over properties, and immutability can be broken. | ||
+ | |||
+ | <code php> | ||
immutable class Email { | immutable class Email { | ||
public $email; | public $email; | ||
Line 79: | Line 130: | ||
$email = new Email(" | $email = new Email(" | ||
- | $email-> | + | $emailRef = &$email-> |
+ | $emailRef | ||
</ | </ | ||
- | Regular classes can define immutability per property. | + | ==== Comparison ==== |
+ | |||
+ | Identity of immutable object is based on its value. So two immutable are identical if they are of the same type and contain same value. | ||
<code php> | <code php> | ||
- | class Email { | + | immutable |
- | public | + | public $email; |
public function __construct ($email) { | public function __construct ($email) { | ||
Line 96: | Line 149: | ||
} | } | ||
- | $email = new Email(" | + | $email1 |
- | $email-> | + | $email2 |
+ | var_dump($email1 === $email2); | ||
</ | </ | ||
- | Any class that extends | + | ===== Examples ===== |
+ | Notice in above example, changing getters and setters methods to public properties is optional. They simply don't need to be protected anymore, in fact, immutable class objects are deeply frozen with exceptions on write. | ||
- | <code php> | + | Every example shows where internal object state is important. Any references to objects passed into an immutable class constructor cannot be references to scalars or may be immutable |
- | immutable class Foo{} | + | |
- | class Bar extends Foo{} // Will result in Fatal Error | + | |
- | </ | + | |
+ | ---- | ||
+ | |||
+ | |||
+ | ==== Money ==== | ||
+ | |||
+ | Money Pattern, defined by Martin Fowler and published in Patterns of Enterprise Application Architecture, | ||
- | It will not be possible to assign value by reference to immutable property. | ||
<code php> | <code php> | ||
- | immutable | + | class Currency |
- | public $email; | + | |
- | public function __construct ($email) { | + | private |
- | | + | |
- | $this->email = $email; | + | |
- | } | + | |
+ | $this-> | ||
+ | } | ||
+ | |||
+ | public function getCentFactor() : int { | ||
+ | return $this-> | ||
+ | } | ||
+ | |||
+ | public function getStringRepresentation() : string { | ||
+ | return $this-> | ||
+ | } | ||
+ | |||
+ | public static function USD() : Currency { | ||
+ | return new self(100, ' | ||
+ | } | ||
+ | |||
+ | public static function EUR() : Currency { | ||
+ | return new self(100, ' | ||
+ | } | ||
} | } | ||
- | $email = new Email(" | + | class Money { |
- | $emailRef | + | |
- | $emailRef | + | private |
+ | private $currency; | ||
+ | |||
+ | public function __construct($amount, | ||
+ | $this-> | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | public function getAmount() : float { | ||
+ | return $this-> | ||
+ | } | ||
+ | |||
+ | public function getCurrency() : Currency { | ||
+ | return $this-> | ||
+ | } | ||
+ | |||
+ | public function add(Money $other) : Money { | ||
+ | $this-> | ||
+ | return | ||
+ | } | ||
+ | |||
+ | public function subtract(Money | ||
+ | $this-> | ||
+ | return new Money($this-> | ||
+ | } | ||
+ | |||
+ | public function multiplyBy($multiplier, | ||
+ | $product = round($this->amount * $multiplier, | ||
+ | | ||
+ | } | ||
+ | |||
+ | private function ensureSameCurrencyWith(Money $other) { | ||
+ | if ($this-> | ||
+ | throw new \Exception("Both Moneys must be of same currency"); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $oneThousand = new Money(1000, Currency:: | ||
</ | </ | ||
- | If immutable | + | After refactoring classes to immutable |
<code php> | <code php> | ||
+ | immutable class Currency { | ||
- | class Bar{} | + | /** @var int */ |
+ | public $centFactor; | ||
+ | /** @var string */ | ||
+ | public $stringRepresentation; | ||
- | immutable class Foo { | + | private function __construct(int $centFactor, |
- | | + | $this-> |
+ | $this-> | ||
+ | } | ||
- | | + | |
- | | + | return new self(100, ' |
+ | | ||
- | | + | |
- | } | + | return new self(100, ' |
+ | } | ||
} | } | ||
- | $foo = new Foo(new Bar()); // Will result in a error because Bar is not instance | + | immutable class Money { |
+ | |||
+ | /** @var float */ | ||
+ | public $amount; | ||
+ | /** @var Currency */ | ||
+ | public $currency; | ||
+ | |||
+ | public function __construct(float $amount, Currency $currency) { | ||
+ | | ||
+ | $this-> | ||
+ | } | ||
+ | |||
+ | public function add(Money $other) : Money { | ||
+ | $this-> | ||
+ | return | ||
+ | } | ||
+ | |||
+ | public function subtract(Money $other) { | ||
+ | $this-> | ||
+ | return | ||
+ | } | ||
+ | |||
+ | public function multiplyBy($multiplier, | ||
+ | $product = round($this-> | ||
+ | return new Money($product, | ||
+ | } | ||
+ | |||
+ | private function ensureSameCurrencyWith(Money $other) { | ||
+ | if ($this-> | ||
+ | throw new \Exception(" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | $oneThousand = new Money(1000, Currency:: | ||
</ | </ | ||
+ | |||
+ | There is no need for getters because this internally immutable object is deeply frozen, and none of his properties cannot be written to anymore. All properties accept scalar values or objects which implement the immutable class, so there is high guarantee such Money object will keep its internal state untouched. | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | No backward | + | No backwardly |
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
- | - PHP 7.2 | + | To be discussed. |
===== RFC Impact ===== | ===== RFC Impact ===== | ||
Line 161: | Line 317: | ||
==== To Existing Extensions ==== | ==== To Existing Extensions ==== | ||
- | - Reflection. | + | - Reflection |
==== To Opcache ==== | ==== To Opcache ==== | ||
Line 183: | Line 339: | ||
===== Future Scope ===== | ===== Future Scope ===== | ||
+ | Add support for arrays on immutable properties. | ||
+ | Expand immutability to regular variables also. | ||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
Line 190: | Line 348: | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | + | [[https:// | |
Line 201: | Line 358: | ||
===== References ===== | ===== References ===== | ||
- | |||
===== Rejected Features ===== | ===== Rejected Features ===== | ||
+ | - Immutable interfaces |
rfc/immutability.txt · Last modified: 2018/02/20 11:19 by marijic.silvio