New Release: Doctrine ORM 2.11 with Enums, Virtual Columns, Read-Only Properties, Nested Attributes and more

Posted on January 11, 2022 by Benjamin Eberlei


We have released a new minor version 2.11 of Doctrine ORM with several improvements and new features.

See all changes and contributors in the Changelog on Github.

This blog post gives an overview over all the new features and improvements that are user facing. Please see the changelog and UPGRADE notes for new deprecations.

PHP 8.1 Enum Support

With PHP 8.1 the language has first class support for enumerations and Doctrine ORM 2.11 supports the mapping of database values to Backed Enums.

The support is not integrated on DBAL Type level, but using a new mapping option called enumType on column/field declaration level:

enum Suit: string {
    case Hearts = 'H';
    case Diamonds = 'D';
    case Clubs = 'C';
    case Spades = 'S';
}

#[Entity]
class Card
{
    /** ... */

    #[Column(type: 'string', enumType: Suit::class)]
    public $suit;
}

Virtual and Generated Columns

There has been constant demand for this feature for a long time, to add support for columns that are not insertable/updatable and might have their value updated on the database side.

We have worked along the lines of Java Persistence API support of insertable, updatable and generated options for field mappings.

There are two major use cases for this:

  1. Map a column several times, for example with join columns:
#[Entity]
class User
{
     #[ManyToOne(targetEntity: Country:class), JoinColumn(name: "country_code", referencedColumnName: "country_code")]
     public $country;

     #[Column(type: "string", name: "country_code", insertable: false, updatable: false)]
     public $countryCode;
}
  1. Columns updated by the database
#[Entity]
class Article
{
    #[Column(type: "datetime",
        columnDefinition: "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",
        insertable: false,
        updatable: false,
        generated: "ALWAYS")]
    public $created;
}

Support for Readonly Properties

Another PHP 8.1 feature is the new readonly keyword that prevents the value of a property to be written again after it has been initialized in the constructor of an object.

With ORM 2.11 the support now works as you would expect with no additional mapping options necessary:

#[Entity, Table(name: 'author')]
class Author
{
    #[Column, Id, GeneratedValue]
    private readonly int $id;

    #[Column]
    private readonly string $name;
}

AssociationOverrides and AttributeOverrides in Attribute Driver

The new AttributeDriver for PHP 8 did not support the equivalent mapping options for association and attribute overrides that are available for XML and Annotation mapping, because in PHP 8.0 it was not possible to nest complex attributes.

With the support now available in PHP 8.1 we have added these attributes.

<?php
use Doctrine\ORM\Mapping\AssociationOverride;
use Doctrine\ORM\Mapping\AssociationOverrides;
use Doctrine\ORM\Mapping\AttributeOverride;
use Doctrine\ORM\Mapping\AttributeOverrides;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;

#[AssociationOverrides([
    new AssociationOverride(
        name: "groups",
        joinTable: new JoinTable(
            name: "ddc964_users_admingroups",
        ),
        joinColumns: [new JoinColumn(name: "adminuser_id")],
        inverseJoinColumns: [new JoinColumn(name: "admingroup_id")]
    )
])]
#[AttributeOverrides([
    new AttributeOverride(
        name: "id",
        column: new Column(name: "guest_id", type: "integer", length: 140)
    )])]
class DDC964Admin extends DDC964User
{
}

For PHP 8.0 we have already moved Index and JoinColumn mappings to the top level to avoid nesting and decided not to allow nesting for these to mimic annotation support.

Allow arithmetic expressions within IN operator

It is now possible to use arithmetic expressions or functions inside the IN operator:

SELECT u FROM User u WHERE u.id IN (1 + 1, FOO(u.id))

Ignore entity classes in schema tool

You can now specify Entity FQCNs to ignore during schema tool creation and comparison. SchemaTool will then skip these (e.g. when comparing schemas).

<?php
$config->setSchemaIgnoreClasses([$fqcn]);
$config->getSchemaIgnoreClasses();