如何更改 Doctrine2 CTI 继承中的实体类型

Posted

技术标签:

【中文标题】如何更改 Doctrine2 CTI 继承中的实体类型【英文标题】:How to change and entity type in Doctrine2 CTI Inheritance 【发布时间】:2011-05-09 14:37:41 【问题描述】:

如何(如果可能的话)使用 Doctrine2 更改实体类型,使用它的类表继承?

假设我有一个Person 父类类型和两个继承类型EmployeClient。我的系统允许创建一个 Person 并指定它的类型 - 这很容易实现 - 但我也希望能够将这个人从 Employe 更改为 Client,同时维护 Person 级别的信息(它是 id和其他相关记录)。

有没有一种简单的方法可以用 Doctrine2 做到这一点?

【问题讨论】:

【参考方案1】:

我昨天也在寻找这种行为。

最后,在 freenode 上与#doctrine 中的人交谈后,我被告知这是不可能的。

如果你想这样做,那么你必须经历这个:

升级用户

    抓取 Person 实体。 更新鉴别器列,使其不再是“人员”并将其更改为“员工” 在Employee 表中为此继承创建相应的行。

删除继承

同样,如果你想删除继承,你必须..

    抓取 Person 实体。 更新鉴别器列,使其不再是“员工”并将其更改为“人员”。 删除Employee 表中的相应行。 (是的,你必须删除它,仅仅改变鉴别器是不够的)。

这可能晚了 7 个月,但它至少是其他任何希望支持此类功能的正确答案。

【讨论】:

太棒了。我最终没有使用 CTI 来解决这个特定问题,但实际上有时可能需要 =) 刷新实体呢?如果您已经加载了用户实体,则手动鉴别器值将不会是工作单元管理的已加载用户实体的一部分。因此用户实体仍将被视为旧类型。 $em->刷新($user);不起作用。那么如何处理这个问题呢?【参考方案2】:

php 不支持对象转换,因此 Doctrine 不支持它。为了解决这个问题,我将此静态方法写入父类:

public static function castToMe($obj) 

    $class = get_called_class();
    $newObj = New $class();

    foreach (get_class_vars(get_class($newObj)) as $property => $value) 
        if (method_exists($obj, 'get' . ucfirst($property)) && method_exists($newObj, 'set' . ucfirst($property))) 
            $newObj->'set' . ucfirst($property)($obj->'get' . ucfirst($property)());
        
    

    return $newObj;

您可以在 Person 类中创建此方法,并使用它从 Employe 转换为 Client,反之亦然:

$employe = New Employe();
$client = Client::castToMe($employe);

现在,如果需要,您可以删除 $employe 实体。

【讨论】:

【参考方案3】:

你可以这样做:

此 Trait 可用于您的 Repository 类:

namespace App\Doctrine\Repository;

trait DiscriminatorTrait

    abstract public function getClassMetadata();

    abstract public function getEntityManager();

    private function updateDiscriminatorColumn($id, $class)
    
        $classMetadata = $this->getClassMetadata();

        if (!in_array($class, $classMetadata->discriminatorMap)) 
            throw new \Exception("invalid discriminator class: " . $class);
        

        $identifier = $classMetadata->fieldMappings[$classMetadata->identifier[0]]["columnName"];

        $column = $classMetadata->discriminatorColumn["fieldName"];
        $value = array_search($class, $classMetadata->discriminatorMap);

        $connection = $this->getEntityManager()->getConnection();

        $connection->update(
            $classMetadata->table["name"],
            [$column => $value],
            [$identifier => $id]
        );
    

您可能还需要做一些额外的工作,例如清除仅存在于您的一个子类中的字段中的值

【讨论】:

【参考方案4】:

在 Doctrine2 中,当您拥有父实体类时,Person 设置为:

/**
 * @Entity
 * @InheritanceType("JOINED")
 * @DiscriminatorColumn(name="discr", type="string")
 * @DiscriminatorMap("person" = "Person", "employee" = "Employee", , "client" = "Client")
 */
class Person

    // ...

以及Client等子类设置为:

/** @Entity */
class Client extends Person

    // ...

当您将Person 实例化为:

$person = new Person();

Doctrine2 检查您的@DiscriminatorMap 语句(上面)是否有到Person 的对应映射,找到后,在上面@DiscriminatorColumn 中设置的表列中创建一个字符串值。

因此,当您决定将Client 的实例设为:

$client = new Client();

遵循这些原则,只要您在@DiscriminatorMap 中声明了参数,Doctrine2 就会为您创建一个实例。还将在 Person 表的 discr 列中创建一个条目,以反映刚刚实例化的实体类的类型。

希望对您有所帮助。不过,这一切都在documentation 中

【讨论】:

也许我的问题不够清楚,但我想 cast 将一种类型的实体转换为另一种类型(即: Client -> Employee ),同时保持一般与超类相关的数据(此处为 Person)。另外,我为此使用单表继承(不是多表)

以上是关于如何更改 Doctrine2 CTI 继承中的实体类型的主要内容,如果未能解决你的问题,请参考以下文章

Doctrine2 中的条件关联

doctrine2 映射覆盖从 MappedSuperclass 继承的 inversedBy 字段。

Doctrine2:任意连接和单表继承

Doctrine2 刷新单个删除的实体

Zend+Doctrine2:如何使用 ArrayCollections() 正确刷新实体?

从父实体映射中将可嵌入字段设置为主键 - Doctrine2