首页 > 解决方案 > 使用迁移管理数据的 Symfony Doctrine 只读实体

问题描述

我正在寻找有关以下场景的最佳实践的建议。

设想

我有一些实体使用我所说的“数据作为架构的一部分”:它们的数据是软件的一部分,在正常操作期间不会由软件创建/编辑/删除。

我正在使用这些为其他实体提供固定的选择列表,而不使用枚举字段 - 因为它们实际上是列定义的一部分,如果我需要以任何其他方式更改列而不是附加一个新值,它会导致全表副本。

这样的实体是这样定义的——readOnly=true 参数、手动分配的 ID、唯一的 slug、唯一的用户可见名称和私有构造函数。这不是夹具的好候选,因为它不是测试数据(谈论 Doctrine Fixtures 如何删除我的“数据作为模式”是另一个 SO 问题)

/**
 * @ORM\Entity(readOnly=true)
 */
class SubmissionStatus
{
    /**
     * @var int
     * @ORM\Column(type="integer")
     * @ORM\Id()
     */
    protected $id;

    /**
     * @var string
     * @ORM\Column(type="string", length=191, unique=true)
     */
    protected $slug;

    /**
     * @var string
     * @ORM\Column(type="string", length=191, unique=true)
     */
    protected $name;

    private __construct() {}

    // ... other fields
}

我使用 Doctrine Migrations 管理价值观:

final class Version20181023132831 extends AbstractMigration
{
    public function up(Schema $schema) : void
    {
        $statuses = [
            ["id" => 1, "name" => "Pending", "slug" => "pending"],
            ["id" => 2, "name" => "Reported", "slug" => "reported"],
            ["id" => 3, "name" => "Rejected", "slug" => "rejected"],
            ["id" => 4, "name" => "Accepted", "slug" => "accepted"],
        ];

        foreach ($statuses as $status) {
            $this->addSql("INSERT INTO submission_status (id, name, slug) VALUES (:id, :name, :slug WHERE id = :id", [
                "id" => $status['id'],
                "name" => $status['name'],
                "slug" => $status['slug'],
            ]);
        }
    }
}

如果我需要更新这些值,我会在未来的迁移中这样做 - 如果需要,还会按摩引用这些行的现有外UPDATE键。DELETE

其他实体@ORM\ManyToOne与该实体有关系。

问题

在代码中引用这些的最佳方法是什么?

并非所有这些实体都将由用户ChoiceType在表单中的字段中选择或通过 API 调用进入。其中一些将仅在代码中使用,例如$otherEntity->setStatus(...)

当前解决方案

目前我们已经决定这样做:

我们将常量存储在 ID 和 slug 的实体类中。

class SubmissionStatus
{
    const PENDING_ID = 1;
    const PENDING_SLUG = 'pending';
    const REPORTED_ID = 2;
    const REPORTED_SLUG = 'reported';
    const REJECTED_ID = 3;
    const REJECTED_SLUG = 'rejected';
    const ACCEPTED_ID = 4;
    const ACCEPTED_SLUG = 'accepted';
}

当我们需要时setStatus,我们进行数据库查找:

$status = $em->getRepository(SubmissionStatus::class)->findOneBy(['slug' => SubmissionStatus::ACCEPTED_SLUG])
$otherEntity->setStatus($status);

问题

有没有更好的方法来做到这一点?

我不是一直编写所有样板文件的忠实粉丝getRepository->findOneBy,尤其是当我需要引用其中几个此类实体时。我想要一些简短、简单、快速的反复写的东西。

我也希望在更改值时不必更改太多地方。迁移中的一个和类常量中的一个就足够了。

到目前为止我的想法

我无法向实体本身(例如setStatusAccepted()setStatus(SubmissionStatus::ACCEPTED_SLUG))添加辅助函数,因为我无法从那里查找被引用的实体。

一个注入的服务,它在创建时查找并存储引用。它的用法是例如$entity->setStatus($statusRef->accepted), $entity->setStatus($statusRef->get(SubmissionStatus::ACCEPTED_SLUG))。第一个选项会中断 IDE 代码完成。第二个有点太长了,虽然短于$em->getRepository->find

标签: symfonydoctrine-orm

解决方案


有一个属性注释可以帮助您的 IDE 保持愉快。

/**
 * One entry for each status 
 * @property-read SubmissionStatus $accepted 
 */
class SubmissionStatusReference
{
    public function __get($slug) {
        switch($slug) {
            // blah blah blah
            case 'accepted': 
                return $this->entityManager->getReference(etc...

我认为这是实际回答您的问题的最干净的方法。

但话虽如此,我最终停止做这种事情,而不是制作实际的状态实体,我只使用 slug。最初我担心能够改变价值观,但实际上我几乎不需要这样做。我使用视图转换器将蛞蝓转换为您所称的名称。而且根本没有身份证。


推荐阅读