Doctrine Attributes instead of annotations

Does anyone know how to tell Concrete/Doctrine to use attributes for the entities in my package instead of annotations?

You need ConcreteCMS v9.2.0 or later (and of course PHP 8.0 or later).

Your package controller must implement the Concrete\Core\Database\EntityManager\Provider\ProviderInterface interface, and you have to add a getDrivers() method that provides a Doctrine\ORM\Mapping\Driver\AttributeDriver driver.

Here’s a minimal example of a package controller whose handle is test_attributes (you need to save it as packages/test_attributes/controller.php):

<?php

declare(strict_types=1);

namespace Concrete\Package\TestAttributes;

use Concrete\Core\Database\EntityManager\Driver\DriverInterface;
use Concrete\Core\Database\EntityManager\Provider\ProviderInterface;
use Concrete\Core\Package\Package;
use Doctrine\ORM\Mapping\Driver\AttributeDriver;
use Doctrine\Persistence\Mapping\Driver\MappingDriver;

defined('C5_EXECUTE') or die('Access Denied.');

final class Controller extends Package implements ProviderInterface
{
    /**
     * The package handle.
     *
     * @var string
     */
    protected $pkgHandle = 'test_attributes';

    /**
     * The package version.
     *
     * @var string
     */
    protected $pkgVersion = '1.0.0';

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Package\Package::$appVersionRequired
     */
    protected $appVersionRequired = '9.2.0';

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Package\Package::$phpVersionRequired
     */
    protected $phpVersionRequired = '8.0';

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Package\Package::getPackageName()
     */
    public function getPackageName(): string
    {
        return t('Test Attributes');
    }

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Package\Package::getPackageDescription()
     */
    public function getPackageDescription(): string
    {
        return t('A package that implements Doctrine ORM Entities with attributes instead of notations.');
    }

    /**
     * {@inheritdoc}
     *
     * @see \Concrete\Core\Database\EntityManager\Provider\ProviderInterface::getDrivers()
     */
    public function getDrivers(): array
    {
        $driver = new class implements DriverInterface {
            public function getNamespace(): string
            {
                return __NAMESPACE__ . '\\Entity';
            }
            public function getDriver(): MappingDriver
            {
                return new AttributeDriver([
                    __DIR__ . '/src/Entity',
                ]);
            }
        };

        return [$driver];
    }
}

Then you can write your entities in the src/Entity folder under your package controller.

For example, you may have this entity stored in packages/test_attributes/src/Entity/TestEntity.php:

<?php

declare(strict_types=1);

namespace Concrete\Package\TestAttributes\Entity;

use Doctrine\ORM\Mapping;

defined('C5_EXECUTE') or die('Access Denied.');

#[
    Mapping\Entity(),
    Mapping\Table(options: ['comment' => 'This is my test table']),
]
class TestEntity
{
    #[
        Mapping\Id,
        Mapping\Column(type: 'integer', options: ['unsigned' => true, 'comment' => 'The record identifier']),
        Mapping\GeneratedValue(strategy: 'IDENTITY'),
    ]
    protected ?int $id;

    #[Mapping\Column(type: 'string', length: 50, nullable: false, options: ['comment' => 'The name of the entity'])]
    protected string $name;

    public function __construct()
    {
        $this->id = null;
        $this->name = '';
    }

    /**
     * Get the record identifier (NULL if and only if not yet persisted).
     */
    public function getID(): ?int
    {
        return $this->id;
    }

    /**
     * Get the name of the entity.
     */
    public function getName(): string
    {
        return $this->id;
    }

    /**
     * Set the name of the entity.
     *
     * @return $this
     */
    public function setName(string $value): self
    {
        $this->name = $value;

        return $this;
    }
}