Download the PHP package lisachenko/immutable-object without Composer
On this page you can find all versions of the php package lisachenko/immutable-object. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package immutable-object
Immutable objects in PHP
This library provides native immutable objects for PHP>=7.4.2
Rationale
How many times have you thought it would be nice to have immutable objects in PHP? How many errors could be avoided if the objects could warn about attempts to change them outside the constructor? Unfortunately, Immutability RFC has never been implemented.
What to do? Of course, there is psalm-immutable annotation which can help us find errors when running static analysis. But during the development of the code itself, we will not see any errors when trying to change a property in such an object.
However, with the advent of FFI and the Z-Engine library, it became possible to use PHP to expand the capabilities of the PHP itself.
Pre-requisites and initialization
As this library depends on FFI
, it requires PHP>=7.4 and FFI
extension to be enabled.
To install this library, simply add it via composer
:
To enable immutability, you should activate FFI
bindings for PHP first by initializing the Z-Engine
library with
short call to the Core::init()
. And you also need to activate immutability handler for development mode (or do not
call it for production mode to follow Design-by-Contract logic and optimize performance and stability of application)
Probably, Z-Engine
will provide an automatic self-registration later, but for now it's ok to perform initialization
manually.
Applying immutability
In order to make your object immutable, you just need to implement the ImmutableInterface
interface marker in your
class and this library automatically convert this class to immutable. Please note, that this interface should be added
to every class (it isn't guaranteed that it will work child with parent classes that was declared as immutable)
Now you can test it with following example:
Low-level details (for geeks)
Every PHP class is represented by zend_class_entry
structure in the engine:
You can notice that this structure is pretty big and contains a lot of interesting information. But we are interested in
the interface_gets_implemented
callback which is called when some class trying to implement concrete interface. Do you
remember about Throwable
class that throws an error when you are trying to add this interface to your class? This is
because Throwable
class has such handler installed that prevents implementation of this interface in user-land.
We are going to use this hook for our ImmutableInterface
interface to adjust original class behaviour. Z-Engine
provides a method called ReflectionClass->setInterfaceGetsImplementedHandler()
that is used for installing custom
interface_gets_implemented
callback.
But how we will make existing class and objects immutable? Ok, let's have a look at one more structure, called
zend_object
. This structure represents an object in PHP.
You can see that there is handle of object (almost not used), there is a link to class entry (zend_class_entry *ce
),
properties table and strange const zend_object_handlers *handlers
field. This handlers
field points to the list of
object handlers hooks that can be used for object casting, operator overloading and much more:
But there is one important fact. This field is declared as const
, this means that it cannot be changed in runtime, we
need to initialize it only once during object creation. We can not hook into default object creation process without
writing a C extension, but we have an access to the zend_class_entry->create_object
callback. We can replace it with
our own implementation that could allocate our custom object handlers list for this class and save a pointer to it in
memory, providing API to modify object handlers in runtime, as they will point to one single place.
We will override low-level write_property
handler to prevent changes of properties for every instance of class. But
we should preserve original logic in order to allow initialization in class constructor, otherwise properties will be
immutable from the beginning. Also we should throw an exception for attempts to unset property in unset_property
hook.
And we don't want to allow getting a reference to properties to prevent indirect modification like $obj->field++
or
$byRef = &$obj->field; $byRef++;
.
This is how immutable objects in PHP are implemented. Hope that this information will give you some food for thoughts )
Code of Conduct
This project adheres to the Contributor Covenant code of conduct. By participating, you are expected to uphold this code. Please report any unacceptable behavior.
License
This library is distributed under MIT-license and it uses Z-Engine
library distributed under
RPL-1.5 with additional premium license.