Symfony 4 Doctrine 2实体映射报错:表无主键,只读权限无法添加
嘿,我太懂这种卡壳的挫败感了——碰到这种只读还没主键的表,Doctrine确实会闹脾气,毕竟它默认认定实体必须有主键来跟踪数据状态。别着急,我给你几个实际能用的方案,都是之前帮同行解决过类似问题的思路:
如果你的表虽然没有单独的主键,但有一组字段的组合是全局唯一的(比如user_id+order_no这种不会重复的组合),那可以把这组字段设为Doctrine的复合主键,完美绕过报错。
举个实体类的例子:
<?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; use Serializable; /** * @ORM\Entity * @ORM\Table(name="your_target_table") */ class YourEntity implements Serializable { /** * @ORM\Id * @ORM\Column(type="integer") */ private $userId; /** * @ORM\Id * @ORM\Column(type="string", length=50) */ private $orderNo; // 其他字段定义... // 必须实现Serializable接口,复合主键要求 public function serialize(): string { return serialize([$this->userId, $this->orderNo]); } public function unserialize($serialized): void { [$this->userId, $this->orderNo] = unserialize($serialized); } // 对应的getter方法(只读表可以不用setter) public function getUserId(): ?int { return $this->userId; } public function getOrderNo(): ?string { return $this->orderNo; } }
Doctrine会把这两个字段的组合当作主键来用,只要它们的组合在表里是唯一的,就能正常完成映射和查询操作。
如果你的表连复合唯一字段都没有,那可以把实体标记为只读,然后用原生SQL查询+结果集映射的方式,彻底绕过Doctrine的主键校验。
首先定义只读实体:
<?php namespace App\Entity; use Doctrine\ORM\Mapping as ORM; /** * @ORM\Entity(readOnly=true) * @ORM\Table(name="your_target_table") */ class YourEntity { /** * @ORM\Column(type="string", length=100) */ private $columnA; /** * @ORM\Column(type="integer") */ private $columnB; // 其他字段... // 只需要getter方法 public function getColumnA(): ?string { return $this->columnA; } public function getColumnB(): ?int { return $this->columnB; } }
然后创建一个自定义Repository来处理原生查询:
<?php namespace App\Repository; use App\Entity\YourEntity; use Doctrine\ORM\EntityRepository; use Doctrine\ORM\Query\ResultSetMapping; class YourEntityRepository extends EntityRepository { public function fetchAllData(): array { // 定义结果集映射,把SQL字段和实体属性对应起来 $rsm = new ResultSetMapping(); $rsm->addEntityResult(YourEntity::class, 'e'); $rsm->addFieldResult('e', 'column_a', 'columnA'); $rsm->addFieldResult('e', 'column_b', 'columnB'); // 继续映射其他字段... // 写原生SQL查询 $sql = 'SELECT column_a, column_b, other_columns FROM your_target_table'; // 创建原生查询并执行 $query = $this->getEntityManager()->createNativeQuery($sql, $rsm); return $query->getResult(); } }
这种方式完全绕开了Doctrine的ORM主键检查,直接把SQL结果映射到实体类,适合完全没有唯一标识的只读表。
如果上面两种方法都不适用,可以试试这个hack方案:通过Doctrine事件监听器,在Schema生成后给目标表手动添加一个伪主键(仅存在于Doctrine映射中,数据库里不会实际创建)。
首先创建事件监听器:
<?php namespace App\EventListener; use Doctrine\ORM\Tools\Event\GenerateSchemaEventArgs; class IgnorePrimaryKeyListener { public function postGenerateSchema(GenerateSchemaEventArgs $args): void { $schema = $args->getSchema(); $targetTableName = 'your_target_table'; if ($schema->hasTable($targetTableName)) { $table = $schema->getTable($targetTableName); // 如果表没有主键,随便选一个字段当伪主键 if (!$table->hasPrimaryKey()) { // 这里选column_a作为伪主键,你可以换成表中存在的任意字段 $column = $table->getColumn('column_a'); $table->addPrimaryKey([$column->getName()]); } } } }
然后在config/services.yaml里注册这个监听器:
services: App\EventListener\IgnorePrimaryKeyListener: tags: - { name: doctrine.event_listener, event: postGenerateSchema }
这个方法能让Doctrine不再抛出“表无主键”的错误,但要注意:这个伪主键只是Doctrine的“自欺欺人”,不能用来做更新/删除操作(刚好你只有只读权限,不影响),而且尽量只针对目标表处理,避免影响其他表的映射。
内容的提问来源于stack exchange,提问作者ShanjayG

