ahmedzidan/php-core

Disable default behavior of PHP objects.

1.0.1 2022-03-20 21:05 UTC

This package is auto-updated.

Last update: 2024-11-21 03:40:16 UTC


README

Latest Stable Version License Travis CI build status AppVeyor CI build status

Coveralls branch Scrutinizer Code Climate: GPA Total Downloads

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