Orm 3.1 – Property containers

Today we are releasing Nextras Orm 3.1 – a release without much features, but with plenty small fixes and enhancements. Let’s examine the major enhancement of property containers. Also, see full release notes.

Property containers

Until now, Orm property containers were quite limited to pretty simple functionality, such as converting encapsulating object as a JSON. (You may have read an article about implementing such container.) Some advanced transformations to class-backed enums were possible, but limited just to the entity interface. The conversion may have failed you just minute later. ICollection required non-converted values for Dbal queries and converted values for in-memory queries. In 3.1 this is fixed. Property containers may define the reverse deserialize function, which will be used in ICollection for proper comparison/query building. Let’s see an example:

We define an enum with marc-mabe/php-enum:

class GeometryType extends MabeEnum\Enum 
{
    const PLACE = 1;
    const CITY = 2;
    const COUNTRY = 3;
    const CONTINENT = 4;
}

Then we define generic reusable enum property container for enums. To this, we inherit from abstract helper class ImmutableValuePropertyContainer that already implements a lot of IPropertyContainer interface.

The definition of EnumContainer is reusable, e.g. you may use it multiple times in different properties with different enum classes. Of your you may write just one-time-purpose property containers.

use MabeEnum\Enum;
use Nextras\Orm\Entity\ImmutableValuePropertyContainer;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;

class EnumContainer extends ImmutableValuePropertyContainer
{
    /** @var string */
    private $enumClass;

    public function __construct(PropertyMetadata $propertyMetadata)
    {
        parent::__construct($propertyMetadata);
        // check the property has one valid type
        assert(count($propertyMetadata->types) === 1);
        $this->enumClass = key($propertyMetadata->types);
        // check the enum class exists
        assert(class_exists($this->enumClass));
    }

    public function convertToRawValue($value)
    {
        assert($value instanceof Enum);
        return $value->getValue();
    }

    public function convertFromRawValue($value)
    {
        $enumClass = $this->enumClass;
        return $enumClass::byValue($value);
    }
}

By default ImmutableValuePropertyContainer allows null values, if you want the null to be part of the enum, you have to override setRawValue, getRawValue respectively.

    public function setRawValue($value)
    {
        $this->value = $this->convertFromRawValue($value);
    }

    public function getRawValue()
    {
        return $this->convertToRawValue($this->value);
    }

The usage then is pretty simple:

/**
 * @property int|null          $id   {primary}
 * @property GeometryType|null $type {container EnumContainer}
 */
final class Geometry extends Entity
{
}

$geometry = new Geometry();
$geometry->type = GeometryType::PLACE();

Filtering requires using proper enum instances:

$geometriesRepository->findBy([
    'type' => GeometryType::CONTINENT()
]);

Property containers may be uses to other data encapsulation use-cases. This enum examples nicely adds type-safety to your code without much effort.