You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Symfony Doctrine实现类似Laravel的Has One Through关联问题

在Symfony Doctrine中实现类似Laravel Has One Through的关联方案

针对你的locked_users表(含user_id和user_type字段)关联members、customers表的需求,先分析你之前方案的问题,再给出两种可行的实现方式:

问题根源分析

  1. 多关联共享外键方案:你在LockedUser实体中同时定义Customer和Member关联,两者都绑定user_id字段,Doctrine无法区分关联逻辑,且单条locked_users记录仅对应一种用户类型,这种设计本身不符合业务逻辑。
  2. 继承映射方案:父类LockedUser同时标注@MappedSuperclass和@Entity(二者互斥);@UniqueEntity配置的字段在父类中不存在;子类重复定义@Table注解(单表继承中子类无需指定表名),这些错误导致映射失效。

方案一:正确配置单表继承

利用Doctrine单表继承,将不同用户类型的锁定记录映射为不同子类,既符合业务逻辑,又能正确关联对应表。

1. 父类LockedUser(基础实体)

/**
 * @ORM\Entity(repositoryClass="Customize\Repository\LockedUserRepository")
 * @ORM\Table(name="mtb_locked_user", uniqueConstraints={@ORM\UniqueConstraint(name="user_type_unique", columns={"user_id", "user_type"})})
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="user_type", type="string", length=255)
 */
class LockedUser extends \Eccube\Entity\AbstractEntity
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @ORM\Column(type="integer")
     */
    protected $user_id;

    // 其他公共字段(如锁定时间、原因等)
}

2. 子类LockedCustomer(关联Customer)

/**
 * @ORM\Entity
 * @ORM\DiscriminatorValue("customer")
 */
class LockedCustomer extends LockedUser
{
    /**
     * @var Customer|null
     *
     * @ORM\ManyToOne(targetEntity="Eccube\Entity\Customer")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
     */
    private ?Customer $customer = null;

    public function getCustomer(): ?Customer
    {
        return $this->customer;
    }

    public function setCustomer(?Customer $customer): self
    {
        $this->customer = $customer;
        $this->user_id = $customer?->getId();
        return $this;
    }
}

3. 子类LockedMember(关联Member)

/**
 * @ORM\Entity
 * @ORM\DiscriminatorValue("member")
 */
class LockedMember extends LockedUser
{
    /**
     * @var Member|null
     *
     * @ORM\ManyToOne(targetEntity="Eccube\Entity\Member")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
     */
    private ?Member $member = null;

    public function getMember(): ?Member
    {
        return $this->member;
    }

    public function setMember(?Member $member): self
    {
        $this->member = $member;
        $this->user_id = $member?->getId();
        return $this;
    }
}

使用方式:创建对应子类实例并设置关联对象,Doctrine会自动填充user_id和user_type字段:

// 锁定客户
$lockedCustomer = new LockedCustomer();
$lockedCustomer->setCustomer($customerEntity);
$entityManager->persist($lockedCustomer);

// 锁定会员
$lockedMember = new LockedMember();
$lockedMember->setMember($memberEntity);
$entityManager->persist($lockedMember);

$entityManager->flush();

方案二:条件关联(无需继承)

若不想使用继承,可在单个LockedUser实体中为关联添加WHERE条件,根据user_type区分关联对象。

/**
 * @ORM\Entity(repositoryClass="Customize\Repository\LockedUserRepository")
 * @ORM\Table(name="mtb_locked_user", uniqueConstraints={@ORM\UniqueConstraint(name="user_type_unique", columns={"user_id", "user_type"})})
 */
class LockedUser extends \Eccube\Entity\AbstractEntity
{
    /**
     * @ORM\Column(type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    private $id;

    /**
     * @ORM\Column(type="integer")
     */
    private $user_id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $user_type;

    /**
     * @var Customer|null
     *
     * @ORM\ManyToOne(targetEntity="Eccube\Entity\Customer")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     * @ORM\Where("user_type = 'customer'")
     */
    private ?Customer $customer = null;

    /**
     * @var Member|null
     *
     * @ORM\ManyToOne(targetEntity="Eccube\Entity\Member")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id")
     * @ORM\Where("user_type = 'member'")
     */
    private ?Member $member = null;

    public function setCustomer(?Customer $customer): self
    {
        $this->customer = $customer;
        $this->user_id = $customer?->getId();
        $this->user_type = 'customer';
        return $this;
    }

    public function setMember(?Member $member): self
    {
        $this->member = $member;
        $this->user_id = $member?->getId();
        $this->user_type = 'member';
        return $this;
    }

    // 其他getter/setter
}

使用方式:设置关联对象时需同步指定user_type,Doctrine会根据条件加载对应关联。


方案对比

  • 单表继承:面向对象设计更清晰,不同用户类型的业务逻辑可拆分到子类,适合后续扩展不同的锁定规则。
  • 条件关联:实体结构单一,配置简单,适合业务逻辑差异较小的场景。

内容的提问来源于stack exchange,提问作者Dương Việt Hoà

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 15:10:26