ahmedzidan / php-core
Disable default behavior of PHP objects.
Requires
- php: ^8.0
Requires (Dev)
- codeclimate/php-test-reporter: ^0.4
- phpunit/phpunit: ^9.5
- satooshi/php-coveralls: ^1.0
- scrutinizer/ocular: ^1.3
Suggests
- fleshgrinder/comparable: For a consistent and type safe comparison contract for objects.
- fleshgrinder/equalable: For a consistent equals contract for objects.
This package is auto-updated.
Last update: 2024-10-21 03:25:16 UTC
README
Core
The core library provides the most basic functionality that is missing in PHP core (and most probably will never make it into it): helpers (in form of traits) to disable default PHP object behavior.
Installation
Open a terminal, enter your project directory and execute the following command to add this package to your dependencies:
composer require fleshgrinder/core
This command requires you to have Composer installed globally, as explained in the installation chapter of the Composer documentation.
Usage
Disenchant
The Disenchant trait can be used to disable support for
the magic __get
, __isset
, __set
, and __unset
methods. Every object in
PHP by default supports these methods for dynamic property management. However,
these are features that are actually undesired in almost all situations. While
support for them does not lead to serious bugs, it can result in in weird
behavior. Especially in the case of value objects when equality is determined
with PHP’s equality operator (==
).
Suppose we have a simple value object:
final class ValueObject { private $value; public function __construct($value) { $this->value = $value; } }
Using the equality operator to determine if two instances are equal or not is straight forward, the same is true for comparisons:
$v0 = new ValueObject(0); $v1 = new ValueObject(1); var_dump( $v0 < $v1, // true $v0 <= $v1, // true $v0 == $v1, // false $v0 >= $v1, // false $v0 > $v1 // false );
If we now add a property to $v0
the result will be different:
$v0 = new ValueObject(0); $v0->x = 42; $v1 = new ValueObject(1); var_dump( $v0 < $v1, // false $v0 <= $v1, // false $v0 == $v1, // false $v0 >= $v1, // true $v0 > $v1 // true );
Of course, these operators should not be used and instead specialized functionality like Equalable and Comparable should be used, but this trait adds another layer of safety to the whole program at almost no cost.
Other situations in which this trait is very handy is if your objects rely on
the properties they have. For instance if get_object_vars
is used somewhere.
However, note that not all possibly weird behavior is fixed through the
inclusion of this trait. It is, for instance, still possible to dynamically
add properties via specially crafted unserialize
strings. This is because
PHP does not call any of these magic methods in that case but directly adds
the properties to the object, validate your objects in your __wakeup
or
unserialize
methods instead.
Uncloneable
The Uncloneable trait can be used to disable support for
the clone
keyword in client code. This is a good idea for objects that
cannot be cloned for technical reasons, e.g. anything that encloses a
resource of some kind like a database connection, or should not be cloned
because it makes no sense, e.g. any kind of immutable implementation like
value objects.
The magic __clone
method is defined as final and protected in this trait,
this ensures that subclasses of the class that uses the trait are not able to
alter that contract. At the same time it allows the using class to use the
clone functionality internally to provide copy-on-write support without
breaking changes; as illustrated in the following example:
final class URI { use Fleshgrinder\Core\Uncloneable; // ... public function withFragment(string $fragment): URI { $clone = clone $this; $clone->fragment = $fragment; return $clone; } }
Another interesting use-case are friend classes paired with the builder pattern to provide immutable entities.
abstract class EntityFriend { use Fleshgrinder\Core\Uncloneable; protected $value; protected function setValue(T $value): void { $this->value = $value; } } final class Entity extends EntityFriend { public function getValue(): T { return $this->value; } } final class EntityBuilder extends EntityFriend { private $entity; public function __construct() { $this->entity = new Entity; } public function build(): Entity { return clone $this->entity; } public function setValue(T $value): void { $this->entity->setValue($value); } }
Unconstructable
The Unconstructable trait can be used to disable
support for the new
keyword in client code. This is almost always a good
idea to disable multiple constructor calls
and enforce invariance for actual constructor method arguments. Of course, the
class requires named constructors, otherwise construction would be impossible.
final class SomeClass { use Fleshgrinder\Core\Unconstructable; public static function new(): self { return new static; } }
Another use-case are final abstract classes, which are not available in PHP.
final class AbstractFinalClass { use Fleshgrinder\Core\Unconstructable; public static function f() { } public static function f′() { } // ... }
Immutable
The Immutable is a combination of all other traits, and is provided for convenience only. It is best used for any kind of immutable class, as the name already suggests.
Testing
Open a terminal, enter the project directory and execute the following commands to run the PHPUnit tests with your locally installed PHP executable.
make
You can also execute the following two commands, in case make
is not
available on our system:
composer install
composer test