使用 PHP 8 个属性时,如何在子类中覆盖 Doctrine 的字段关联映射?

How to override Doctrine's field association mappings in subclasses when using PHP 8 attributes?

如何在父 class 中定义 Doctrine 属性 并覆盖扩展父 class 的 class 中的关联?使用注释时,这是通过使用 AssociationOverride 实现的,但是,我认为使用 PHP 8 个属性时它们不可用

我为什么要:

我有一个 class AbstractTenantEntity,其目的是将对数据的访问限制为拥有数据的给定 Tenant(即帐户、所有者等)和任何实体扩展此 class 将在创建时将 tenant_id 插入到数据库中,所有其他请求会将 tenant_id 添加到 WHERE 子句中。 Tenant 通常没有扩展 AbstractTenantEntity 的各种实体的集合,但少数有。在使用注释时,我通过将 Doctrine 的 AssociationOverride 注释应用到扩展的 classes 来处理它,它应该在 Tenant 中有一个集合,但是我不知道如何在使用 PHP 8个属性?


我在下面描述的尝试没有成功,因为我错误地认为 annotation class would magically work with attributes if modified appropriately, but now I see other code 必须能够根据属性应用适当的逻辑。因此,我放弃了这种方法,只是保护属性并在具体 class.

中复制它们

我的尝试:

租户实体

use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\OneToMany;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

#[Entity()]
class Tenant
{
    #[Id, Column(type: "integer")]
    #[GeneratedValue]
    private ?int $id = null;

    #[OneToMany(targetEntity: Asset::class, mappedBy: 'tenant')]
    private array|Collection|ArrayCollection $assets;

    // Other properties and typical getters and setters
}

AbstractTenantEntity 实体

use Doctrine\ORM\Mapping\ManyToOne;
use Doctrine\ORM\Mapping\JoinColumn;

abstract class AbstractTenantEntity implements TenantInterface
{
    /**
     * inversedBy performed in child where required
     */
    #[ManyToOne(targetEntity: Tenant::class)]
    #[JoinColumn(nullable: false)]
    protected ?Tenant $tenant = null;

    // Typical getters and setters
}

这是让我卡住的部分。使用注释时,我的代码如下:

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\AssociationOverrides({
 *     @ORM\AssociationOverride(name="tenant", inversedBy="assets")
 * })
 */
class Asset extends AbstractTenantEntity
{
    // Various properties and typical getters and setters
}

但是 AssociationOverrides 还没有被修改以使用属性,所以基于 official class,我创建了我自己的 class 类似于 Doctrine 更新的其他内容:

namespace App\Mapping;

use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Doctrine\ORM\Mapping\Annotation;

/**
 * This annotation is used to override association mapping of property for an entity relationship.
 *
 * @Annotation
 * @NamedArgumentConstructor()
 * @Target("ANNOTATION")
 */
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
final class AssociationOverride implements Annotation
{
    /**
     * The name of the relationship property whose mapping is being overridden.
     *
     * @var string
     */
    public $name;

    /**
     * The join column that is being mapped to the persistent attribute.
     *
     * @var array<\Doctrine\ORM\Mapping\JoinColumn>
     */
    public $joinColumns;

    /**
     * The join table that maps the relationship.
     *
     * @var \Doctrine\ORM\Mapping\JoinTable
     */
    public $joinTable;

    /**
     * The name of the association-field on the inverse-side.
     *
     * @var string
     */
    public $inversedBy;

    /**
     * The fetching strategy to use for the association.
     *
     * @var string
     * @Enum({"LAZY", "EAGER", "EXTRA_LAZY"})
     */
    public $fetch;

    public function __construct(
        ?string $name = null,
        ?array  $joinColumns = null,
        ?string $joinTable = null,
        ?string $inversedBy = null,
        ?string $fetch = null
    ) {
        $this->name    = $name;
        $this->joinColumns = $joinColumns;
        $this->joinTable = $joinTable;
        $this->inversedBy = $inversedBy;
        $this->fetch = $fetch;
        //$this->debug('__construct',);
    }

    private function debug(string $message, string $file='test.json', ?int $options = null)
    {
        $content = file_exists($file)?json_decode(file_get_contents($file), true):[];
        $content[] = ['message'=>$message, 'object_vars'=>get_object_vars($this), 'debug_backtrace'=>debug_backtrace($options)];
        file_put_contents($file, json_encode($content, JSON_PRETTY_PRINT));
    }
}

验证映射时,Doctrine 抱怨目标实体不包含所需的 inversedBy。我花了一些时间研究 Doctrine 源代码,但没有取得太大进展。

我目前的方法是否有价值,如果有,请填补空白。但是,如果没有,您建议如何满足这一需求?

覆盖子classes

中的字段关联映射

有时需要保留实体但覆盖全部或部分映射元数据。有时,要覆盖的映射也来自使用特征的实体,其中特征具有映射元数据。本教程解释了如何覆盖映射元数据,特别是属性和关联元数据。此处的示例显示了使用特征的 class 的覆盖,但在扩展基础 class 时类似,如本教程末尾所示。

假设我们有一个 class ExampleEntityWithOverride。 class 使用特征 ExampleTrait:

<?php
/**
 * @Entity
 *
 * @AttributeOverrides({
 *      @AttributeOverride(name="foo",
 *          column=@Column(
 *              name     = "foo_overridden",
 *              type     = "integer",
 *              length   = 140,
 *              nullable = false,
 *              unique   = false
 *          )
 *      )
 * })
 *
 * @AssociationOverrides({
 *      @AssociationOverride(name="bar",
 *          joinColumns=@JoinColumn(
 *              name="example_entity_overridden_bar_id", referencedColumnName="id"
 *          )
 *      )
 * })
 */
class ExampleEntityWithOverride
{
    use ExampleTrait;
}

/**
 * @Entity
 */
class Bar
{
    /** @Id @Column(type="string") */
    private $id;
}

文档块显示属性和关联类型的元数据覆盖。它基本上更改了为 属性 foo 映射的列的名称以及与上面显示的 Bar class 相关的关联栏的名称。这是具有被上面注释覆盖的映射元数据的特征:

<?php
/**
 * Trait class
 */
trait ExampleTrait
{
    /** @Id @Column(type="string") */
    private $id;

    /**
     * @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
     */
    protected $foo;

    /**
     * @OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
     * @JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
     */
    protected $bar;
}

扩展 class 的情况是一样的,但是:

<?php
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
{
    // ...
}

通过 XML 和 YAML(示例)也支持覆盖。

已通过此 pr 解决:https://github.com/doctrine/orm/pull/9241

ps:PHP 8.1 是必需的

#[AttributeOverrides([
new AttributeOverride(
    name: "id",
    column: new Column(name: "guest_id", type: "integer", length: 140)
),
new AttributeOverride(
    name: "name",
    column: new Column(name: "guest_name", nullable: false, unique: true, length: 240)
)]
)]