原则-单表继承和本机查询失败

时间:2019-02-18 21:36:18

标签: doctrine-orm

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

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

从上到下的每个类还具有与App \ Entity \ Profile类的oneToOne关系。

基于上述情况,我在连接用户表的个人资料表上进行了本机查询。 目的是在一次查询中获得个人资料数据和相关的用户数据。

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

现在,我下面的代码引发以下异常: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

1 个答案:

答案 0 :(得分:0)

您应该使用以下任一

  • DiscriminatorMap(单个或类表继承)
  • MappedSuperclass

两个here上的信息


MappedSuperclass

这类似于PHP的抽象类。您无法实例化此类。因此,这是必须扩展为适当内容的一类。因此,您可以:

  • AbstractUser
  • 来宾扩展了AbstractUser
  • 管理员扩展了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个是可实例化的:

  • UserClient
  • UserGuest

两者都来自:AbstractUser

现在,假设“来宾”是最受限制的用户,我们将首先创建该用户:

/**
 * @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
}

到目前为止很好。

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

  • displayName
  • 电子邮件
  • 密码
  • 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为这2个生成的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;

直观显示,即:

Tables for use-case shown visually


现在,您还可以非常轻松地添加个人资料实体。您只需通过OneToOne bi-directional关系将其添加到Guest用户即可。

因此,添加到您的访客:

// 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

但是,在这一点上,您可能希望考虑将“来宾”重命名为普通的“用户”,因为将来将其用于名称可能会减少混乱。 “用户”通常已经是一个非常有限的成员,类似于来宾(区别通常是“来宾” 不是注册用户,而用户)。

现在您可以轻松地做到:

$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