🎻
[Symfony][Doctrine] SELECT結果の件数が1件とは限らないリレーションをOneToOneにしても一応動くという話
知らなかったので共有です。タイトルを見て「何を今さら」と思った人はスルーしてください😅
よくあるOneToManyの例
以下のように、 Parent
エンティティが複数の Child
エンティティを所有している、という状況を考えます。
class Parent
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\OneToMany(targetEntity=Child::class, mappedBy="parent")
*/
private $children;
/**
* @return Collection|Child[]
*/
public function getChildren(): Collection
{
return $this->children;
}
public function addChild(Child $child): self
{
if (!$this->children->contains($child)) {
$this->children[] = $child;
$child->setParent($this);
}
return $this;
}
public function removeChild(Child $child): self
{
if ($this->children->contains($child)) {
$this->children->removeElement($child);
// set the owning side to null (unless already changed)
if ($child->getParent() === $this) {
$child->setParent(null);
}
}
return $this;
}
}
class Child
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity=Parent::class, inversedBy="children")
*/
private $parent;
public function getParent(): ?Parent
{
return $this->parent;
}
public function setParent(Parent $parent): self
{
$this->parent = $parent;
return $this;
}
}
よくあるOneToMany/ManyToOneのリレーションシップですね。
これを無造作にOneToOneにしちゃっても普通に動く
これを、何も考えずにリレーションの設定をOneToOne/OneToOneに変えてみましょう。
class Parent
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\OneToOne(targetEntity=Child::class, mappedBy="parent")
*/
private $child;
public function getChild(): ?Child
{
return $this->child;
}
public function setChild(Child $child): self
{
$this->child = $child;
return $this;
}
}
class Child
{
/**
* @ORM\Id()
* @ORM\GeneratedValue()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\OneToOne(targetEntity=Parent::class, inversedBy="child")
*/
private $parent;
public function getParent(): ?Parent
{
return $this->parent;
}
public function setParent(Parent $parent): self
{
$this->parent = $parent;
return $this;
}
}
実はこれ、普通に動くんです😳
$parent->getChild()
したときに実行されるSQLは
SELECT
t0.id AS id_1,
FROM
profile t0
WHERE
t0.user_id = ?
こんな感じで LIMIT 1
とかはついてませんが、複数レコード取得された場合でも、PHP側で1件だけが採用されてそのエンティティが返ってきます。知りませんでした。
複数レコードのうちどの1件が返ってくるかはたぶん保証されてないと思いますが、実験してみたところどうやらid昇順で最後の1件が返ってくるっぽいです。
プロファイラを見てみるとエラーは出てる
ただし、プロファイラを覗いてみると、以下のようなエラーが出ていました😓
The mappings App\Entity\Parent#child and App\Entity\Child#parent are inconsistent with each other.
多分 OneToOne
なのにDBの child.parent_id
にユニーク制約がないことが原因だと思うのですが、試せていないので正確なことは分かりません🙏(詳細知ってる方いたらぜひ DM お待ちしてます🙇)
Discussion