Doctrine 水合物关系错误,但直接应用 SQL 后的结果集是正确的

Doctrine hydrates relations wrong, but result set after applying SQL directly is right

基本上,我正在尝试 select 所有具有 CurrentCompany 过滤权限的用户,因此如果我们切换 CurrentCompany,我们应该只有具有该 CurrentCompany 权限的用户。

出于测试目的,我添加了两家公司、两个用户(f.i.A 和 B),并将用户 A 分配给两家公司,而用户 B 仅分配给一家公司。另外,用户A对每个公司设置了不同的权限,而用户B只对他添加的一个公司有权限。

虽然使用生成的 SQL 直接查询 MySQL 显示了正确的结果集,但是 User::permissions() 的结果集合具有分配给用户的所有公司的所有权限,但应该只是与 CurrentCompany 相关的权限。

希望我描述的很好理解。 因此,用两个词来说,问题是水化的 User->permissions() 关系为用户分配了他分配给的所有公司的所有权限,但是查询 SQL 直接显示正确的结果。

QueryBuilder + 结果 DQL/SQL:

$this->createQueryBuilder('u')
    ->join('u.companies', 'c', Join::WITH, 'c.company = :company')
    ->leftJoin('u.permissions', 'p', Join::WITH, 'p.company = :company')
//            ->andWhere('c.company = :company')
//            ->andWhere('p.company = :company')
    ->setParameter('company', $this->tenant->company()->id()->toBinary())
    ->getQuery()
    ->getResult()
);
DQL:
SELECT u FROM App\Identity\Domain\User\User u INNER JOIN u.companies c WITH c.company = :company LEFT JOIN u.permissions p WITH p.company = :company

SQL:
SELECT u0_.id AS id_0, u0_.email AS email_1, u0_.username AS username_2, u0_.password AS password_3, u0_.created AS created_4, u0_.deleted AS deleted_5, u0_.active_company_id AS active_company_id_6 FROM user u0_ INNER JOIN user_company u1_ ON u0_.id = u1_.user_id AND (u1_.company_id = 0x3DF17103A3E14FD09D1CEF98D8318230) LEFT JOIN user_permission u2_ ON u0_.id = u2_.user_id AND (u2_.company_id = 0x3DF17103A3E14FD09D1CEF98D8318230) WHERE ((u0_.deleted IS NULL OR u0_.deleted > CURRENT_TIMESTAMP));

数据库结构:

CREATE TABLE `user` (
  `id` binary(16) NOT NULL,
  `email` varchar(180) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
  `created` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `deleted` datetime DEFAULT NULL,
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
  `active_company_id` binary(16) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UNIQ_8D93D649E7927C74` (`email`),
  KEY `FK_USER_ACTIVE_COMPANY` (`active_company_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `user_company` (
  `user_id` binary(16) NOT NULL,
  `company_id` binary(16) NOT NULL,
  `email` varchar(180) COLLATE utf8mb4_unicode_ci NOT NULL,
  `id` binary(16) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UNIQ_USER_COMPANY` (`user_id`,`company_id`),
  KEY `FK_17B21745979B1AD6` (`company_id`),
  CONSTRAINT `FK_17B21745979B1AD6` FOREIGN KEY (`company_id`) REFERENCES `Company` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

CREATE TABLE `user_permission` (
  `user_id` binary(16) NOT NULL,
  `permission_id` binary(16) NOT NULL,
  `id` binary(16) NOT NULL,
  `company_id` binary(16) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `FK_USER_PERMISSION` (`user_id`),
  KEY `FK_USER_PERMISSION_PERMISSION` (`permission_id`),
  KEY `FK_USER_PERMISSION_COMPANY` (`company_id`),
  CONSTRAINT `FK_USER_PERMISSION_COMPANY` FOREIGN KEY (`company_id`) REFERENCES `company` (`id`) ON DELETE CASCADE,
  CONSTRAINT `FK_USER_PERMISSION_PERMISSION` FOREIGN KEY (`permission_id`) REFERENCES `permission` (`id`) ON DELETE CASCADE,
  CONSTRAINT `FK_USER_PERMISSION_USER` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

学说映射:

App\Identity\Domain\User\User:
  type: entity
  table: user
  ...
  oneToMany:
    permissions:
      targetEntity: App\Identity\Domain\User\UserPermission
      orphanRemoval: true
      mappedBy: user
      joinTable:
        name: user_permission
      cascade: [ "all" ]
    companies:
      targetEntity: App\Identity\Domain\UserCompany\UserCompany
      mappedBy: user
      joinTable:
        name: user_company
      cascade: [ "all" ]

App\Identity\Domain\User\UserPermission:
  type: entity
  table: user_permission
  ...
  manyToOne:
    user:
      targetEntity: App\Identity\Domain\User\User
      inversedBy: permissions
      joinTable:
        name: user_permission

App\Identity\Domain\UserCompany\UserCompany:
  type: entity
  table: user_company
  ...
  manyToOne:
    company:
      targetEntity: App\Identity\Domain\Company\Company
      inversedBy: userCompanies
    user:
      targetEntity: App\Identity\Domain\User\User
      inversedBy: companies

好的,解决方案是使用Query::HINT_REFRESH$em->clear(),因为我发现在中间件中还有另一个对User实体的查询,而Doctrine使用IdentityMap的缓存结果,如果实体是已经在那里了。