【问题标题】:doctrine - single table inheritance and native query fails教义 - 单表继承和本机查询失败
【发布时间】:2019-02-18 21:36:18
【问题描述】:

在 symfony 2.8 应用程序中,我将以下类层次结构配置为单表继承:

parent: App\Entity\AbstractUser 
child1: App\Entity\UserClient
child2: App\Entity\UserGuest

此外,上面的每个类都与类 App\Entity\Profile 具有 oneToOne 关系。

基于以上内容,我对 profiles 表加入了 users 表的原生查询。 目标是在一次查询中获取个人资料数据和相关的用户数据。

它确实工作了一段时间,但在某个时候停止工作。可能在学说包更新之后。

现在我下面的代码抛出以下异常:The provided class "App\Entity\AbstractUser" is abstract, and can not be instantiated

我该怎么做才能让它再次工作?

这是我的代码:

$rsm = new ResultSetMapping;
$rsm->addEntityResult('App\Entity\AbstractUser', 'br');
$rsm->addFieldResult('br', 'user_id', 'id');

$sql = "
    SELECT
        rsa.id AS profile_id,
        br.id AS user_id
    FROM profiles rsa
    LEFT OUTER JOIN users br ON rsa.id = br.profile_id
";

try {
    $query = $this->_em->createNativeQuery($sql, $rsm);
    $results = $query->getResult();
} catch (\Exception $e) {
    throw $e;
}

以下是教义定义:

// Resources/config/doctrine/AbstractUser.orm.yml
App\Entity\AbstractUser:
  type: entity
  table: users
  inheritanceType: SINGLE_TABLE
  discriminatorColumn:
    name: type
    type: string
    length: 30
    nullable: false
  discriminatorMap:
    client: App\Entity\UserClient
    guest: App\Entity\UserGuest
  fields:
    id:
      id: true
      type: integer
      generator:
        strategy: AUTO

  # oneToOne association
  oneToOne:
    profile:
      targetEntity: App\Entity\Profile
      cascade: ['persist','remove']
      joinColumn:
        name: profile_id
        referencedColumnName: id
        nullable: false
        unique: false

// Resources/config/doctrine/UserClient.orm.yml
App\Entity\UserClient:
  type: entity

// Resources/config/doctrine/UserGuest.orm.yml
App\Entity\UserGuest:
  type: entity

// Resources/config/doctrine/Profile.orm.yml
App\Entity\Profile:
  type: entity
  table: profiles
  fields:
    id:
      id: true
      type: integer
      generator:
        strategy: AUTO

【问题讨论】:

  • $rsm->addEntityResult('App\Entity\AbstractUser', 'br'); 此行不会导致结果映射器尝试使用查询结果创建AbstractUser 的实例吗?为什么不将 AbstractUser 更改为 User,让它成为一个实体。结果映射器应将其识别为可区分类型(基于配置和列)并自动加入映射实体。 (如果ResultSetMapping 不支持,请查看\Doctrine\ORM\QueryBuilder)。
  • 另外,see here,最后引用的要点:If no discriminator map is provided, then the map is generated automatically. The automatically generated discriminator map contains the lowercase short name of each class as key. - 所以你可以删除 discriminatorMap 配置,让 Doctrine 在内部处理它的创建。
  • 谢谢@rkeet。 “为什么不将 AbstractUser 更改为 User”是什么意思?儿童班之一?我已经尝试过,例如$rsm->addEntityResult('App\Entity\UserClient', 'br'); 查询有效,但结果集仅包含 UserClient 实体,无论记录是什么鉴别器类型。
  • 使User 成为一个实体。不是abstract,不是MappedSuperclass,而是一个可实例化的实体。如果您随后$rsm->addEntityResult('App\Entity\User', 'br'),您应该获得UserUserClientUserGuest 实体的结果集。为了防止创建普通的User,只需确保通过不创建user/add URI/endpoint 就无法创建它。
  • @rkeet 如果我这样做会出现后续问题:Entity 'App\Entity\AbstractUser' has to be part of the discriminator map of 'App\Entity\AbstractUser' to be properly mapped in the inheritance hierarchy. Alternatively you can make 'App\Entity\AbstractUser' an abstract class to avoid this exception from occurring

标签: doctrine-orm


【解决方案1】:

您应该使用 其中一个

  • DiscriminatorMap(单表或类表继​​承)
  • 映射超类

here 的信息


映射超类

这类似于 PHP 的抽象类。你不能实例化这个类。因此,它是一个必须扩展为适当的类。因此,您可以:

  • 抽象用户
  • Guest 扩展 AbstractUser
  • Admin 扩展 AbstractUser

这会给你一些东西:

  • 某些属性和函数对两者都可用(保存重复代码 - DRY)
  • 提供与实例相同的“父”类

设置和用例

/**
 * @ORM\MappedSuperclass
 */
abstract class AbstractEntity
{
    /**
     * @var int
     * @ORM\Id
     * @ORM\Column(name="id", type="integer", options={"unsigned":true})
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    protected $id;

    // getter / setter
}

这个用例展示了我自己项目中所有实体的通用标识符的用法。它还提供了一个简单的入口点来验证特定实体是在我自己的项目中还是由供应商提供,这样做:

if ($entity instanceof AbstractEntity) { ... }

单/类表继承

现在,让我们在表继承中使用 MappedSuperclass。在此示例中,我们将使用 JOINED 策略,但请随意使用最适合您需求的策略。

你有 3 个类,其中 2 个是可实例化的:

  • 用户客户端
  • 用户访客

两者均来自:AbstractUser

现在,假设“Guest”是最受限的用户,我们将首先创建一个:

/**
 * @ORM\Entity
 * @ORM\Table(name="user_guests")
 */
class Guest extends AbstractEntity
{
    /**
     * @var string
     * @ORM\Column(name="display_name", type="string", length=255, nullable=false)
     */
    protected $displayName;

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

    /**
     * @var string
     * @ORM\Column(name="password", type="text", nullable=false)
     */
    protected $password;

    // getters / setters
}

到目前为止一切顺利。

现在,我们需要创建“客户端”。客户应该:

  • 显示名称
  • 电子邮件
  • 密码
  • clientId

前 3 个与访客相同。因此,扩展Guest 可能是明智之举。对于任一用户类型,其他事情(例如功能)是否相同?如:

  • 登录/注销方式
  • 作为电子邮件收件人/发件人

如果这些用例中有很多是相同的,那么我们就有了使用表继承的可靠案例。因此,更新 Guest 的注释,如下所示:

/**
 * @ORM\Entity
 * @ORM\Table(name="user_guests")
 * 
 * @InheritanceType("JOINED")
 * @DiscriminatorColumn(name="discr", type="string")
 */
class Guest extends AbstractEntity { ... }

注意:我们不声明DiscriminatorMap。教义会为我们处理这件事。 Per the docs

如果没有提供鉴别器映射,则自动生成映射。自动生成的鉴别器映射包含每个类的小写短名称作为键。

接下来,创建客户端:

/**
 * @ORM\Entity
 * @ORM\Table(name="user_clients")
 */
class Client extends Guest
{
    /**
     * @var string
     * @ORM\Column(name="client_id", type="string", length=50, nullable=false)
     */
    protected $clientId;

    // getters / setters
}

Doctrine 为这两个生成的SQL,例如,是这样的:

CREATE TABLE user_guests
(
  id           INT UNSIGNED AUTO_INCREMENT NOT NULL,
  display_name VARCHAR(255)                NOT NULL,
  email        VARCHAR(255)                NOT NULL,
  password     LONGTEXT                    NOT NULL,
  discr        VARCHAR(255)                NOT NULL,
  UNIQUE INDEX UNIQ_2843A78FE7927C74 (email),
  PRIMARY KEY (id)
) DEFAULT CHARACTER SET utf8
  COLLATE utf8_unicode_ci
  ENGINE = InnoDB;

CREATE TABLE user_clients
(
  id        INT UNSIGNED NOT NULL,
  client_id VARCHAR(50)  NOT NULL,
  PRIMARY KEY (id)
) DEFAULT CHARACTER SET utf8
  COLLATE utf8_unicode_ci
  ENGINE = InnoDB;

ALTER TABLE user_clients
  ADD CONSTRAINT FK_58C5307EBF396750 FOREIGN KEY (id) REFERENCES user_guests (id) ON DELETE CASCADE;

直观地显示,即:


现在您还可以非常轻松地添加个人资料实体。您只需使用 OneToOne bi-directional 关系将其添加到您的访客用户。

所以,添加到您的客人:

// other properties

/**
 * @var Profile
 * @ORM\OneToOne(targetEntity="Profile", cascade={"persist", "remove"}, fetch="EAGER", inversedBy="Guest")
 * @ORM\JoinColumn(name="profile_id", referencedColumnName="id")
 */
protected $profile;

// getter / setter

此时,您可能希望考虑将“Guest”重命名为普通的“User”,因为将来使用它作为名称可能会减少混乱。 “用户”通常已经是一个非常有限的成员,类似于访客(区别通常是“访客”不是注册用户而用户)。

现在你可以简单地做:

$profile->getGuest() // or ->getUser() if you renamed
// Returns instance of Guest (might be child instance: Client)

$guest->getProfile() // returns Profile
$client->getProfile() // returns Profile

$user = new Guest();
$user instanceof Guest ? true : false; // true
$user instanceof Client ? true : false; // false

$user = new Client();
$user instanceof Guest ? true : false; // true
$user instanceof Client ? true : false; // true

【讨论】:

  • 非常感谢您提供如此详细的说明。很高兴看到这么多的承诺。不要误会我的意思,但我猜我很好地掌握了继承基础知识。我明确要求 SINGLE_TABLE 继承。最重要的是,我在我的 yaml 定义中看不到任何错误。自定义 discriminatorMap 不是必需的,但我认为支持和正确定义(顺便说一句。我删除了它 - 没有效果)。我怀疑我的问题不在查询逻辑代码内的 Native Query Builder 选项中的继承定义中。如果您能专注于此,我们将不胜感激。
猜你喜欢
  • 2016-04-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-12
  • 1970-01-01
  • 1970-01-01
  • 2015-12-21
相关资源
最近更新 更多