【问题标题】:Doctrine inverse side's collection field is randomly filledDoctrine inverse side 的集合字段是随机填充的
【发布时间】:2021-12-28 17:59:59
【问题描述】:

我有以下两个实体 address 和 user 。 在我的一个控制器中,我有这个功能:

public function initAddressAction($idUser)
{
    $em = $this->getDoctrine()->getManager();
    $address = new Address();

    /** @var User $user*/
    $user= $em->getRepository('AppBundle:User' )->find($idUser);
    if ($user!== null) {
        $address->setUser($user); 
            dump($user); // #1
            $addresses = $user->getAddresses()->toArray();
            dump($user);die; // #2
            ...} 

我的问题是为什么第一个转储会在地址字段中打印带有空数组的用户对象:

#collection: Doctrine\Common\Collections\ArrayCollection {#9487 ▼

  -elements: []`

而第二个转储在地址字段中打印带有非空数组集合的用户对象(该数组中实际上有一个地址):

#collection: Doctrine\Common\Collections\ArrayCollection {#9487 ▼

  -elements: array:1 [▼

    0 => App\Entity\address{#81625 ▼`

用户:

/**
 * @ORM\OneToMany(targetEntity="App\Entity\Address", mappedBy="user")
 */
private $addresses;

/**
 * Set addresses
 *
 * @param Collection $addresses
 */
public function setAddresses($addresses)
{
    $this->addresses= $addresses;
}

/**
 * Get addresses
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getAddresses()
{
    return $this->addresses;
}

/**
 * Add address
 *
 * @param Address $address
 * @return User
 */
public function addAddress(Address$address)
{
    if (!$this->addresses->contains($address)) {
        $this->addresses[] = $address;
    }

    return $this;
}

/**
 * Remove address
 *
 * @param Address $address
 */
public function removeAddress(Address $address)
{
    $this->addresses->removeElement($address);
}

地址:

/**
 * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="adresses")
 * @ORM\JoinColumn(name="id_user", referencedColumnName="id_user", nullable=false)
 */
private $user

/**
 * @return User
 */
public function getUser()
{
    return $this->user;
}

/**
 * @param User $user
 */
public function setUser($user)
{
    $this->user= $user;
}

【问题讨论】:

  • 这是延迟加载:Doctrine 不会在需要时加载集合中的对象。当调用集合上的方法时加载对象。所以,dump($user) 转储没有地址的用户,在调用getAddresses()->toArray() 后,用户的地址从数据库中加载,添加到用户并在第二个转储中可见。
  • 没错,这种情况下不会填写addresses属性。如果稍后在您的代码中,您确实使用了addresses 属性(例如,使用foreach 打印地址)地址将在代码中的该点加载。如果您稍后在代码中不使用该属性,该属性将保持为空,但地址仍在数据库中,因此它们不会丢失。基本上,您应该使用 User 对象,就好像它已经完全加载了它的地址一样,而不必担心何时以及是否加载了地址。
  • 延迟加载只加载保存在数据库中的地址。如果要添加一个尚未保存到数据库中的新地址到用户的地址数组中,则需要使用 addAddress()。
  • 只有用户的 $addresses 属性上的地址才会被延迟加载。要添加新地址,请将用户添加到具有$address->setUser($user) 的地址,这将设置关系的拥有方。然后用persist($address)flush() 保存地址。如果您在保存新地址后调用$addresses = $user->getAddresses()->toArray(),则$addresses 还将包含新添加的用户地址。
  • (你也可以调用$user->addAddress($address)添加新地址,但是在addAddress()方法中你仍然需要$address->setUser($this),因为你总是需要设置关系。)

标签: symfony4


【解决方案1】:

我已经通过 Symfony maker bundle 生成了一个 User 和一个 Address 类。

用户:

<?php

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=UserRepository::class)
 * @ORM\Table(name="`user`")
 */
class User
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

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

    /**
     * @ORM\OneToMany(targetEntity=Address::class, mappedBy="userField")
     */
    private $addresses;

    public function __construct()
    {
        $this->addresses = new ArrayCollection();
    }

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    /**
     * @return Collection<int, Address>
     */
    public function getAddresses(): Collection
    {
        return $this->addresses;
    }

    public function addAddress(Address $address): self
    {
        if (!$this->addresses->contains($address)) {
            $this->addresses[] = $address;
            $address->setUserField($this);
        }

        return $this;
    }

    public function removeAddress(Address $address): self
    {
        if ($this->addresses->removeElement($address)) {
            // set the owning side to null (unless already changed)
            if ($address->getUserField() === $this) {
                $address->setUserField(null);
            }
        }

        return $this;
    }
}

地址:

<?php

namespace App\Entity;

use App\Repository\AddressRepository;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass=AddressRepository::class)
 */
class Address
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity=User::class, inversedBy="addresses")
     * @ORM\JoinColumn(nullable=false)
     */
    private $userField;

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

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getUserField(): ?User
    {
        return $this->userField;
    }

    public function setUserField(?User $userField): self
    {
        $this->userField = $userField;

        return $this;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }
}

失败的例子:

试试这个(例如在控制器中):

    // get address 1 (this is attached to user 1 in the database)
    $address1 = $addressRepo->findOneBy(['id' => 1]);
    dump($address1);

这是地址 1,它的用户对象还没有被初始化,但这对于这个例子并不重要。

    // get user 2 (user 2 only has address 2 in the database)
    $user2 = $userRepo->findOneBy(['id' => 2]);
    dump($user2);

这是用户 2,请注意地址数组尚未初始化(这很重要!)。

    // set user 2 onto address 1
    $address1->setUserField($user2);
    dump($address1);

现在地址 1 有用户 2。

    dump($user2);

但用户 2 没有地址 1。

    // load user 2's addresses from the database
    $user2->getAddresses()->toArray();
    dump($user2);

用户 2 现在从数据库中加载了地址 2(但仍然没有地址 1)。

使用 $userField->addAddress($this)

如果添加$userField-&gt;addAddress($this); 行:

public function setUserField(?User $userField): self
{
    $this->userField = $userField;
    $userField->addAddress($this);

    return $this;
}

并执行相同的步骤:

    // get address 1 (this is attached to user 1 in the database)
    $address1 = $addressRepo->findOneBy(['id' => 1]);
    dump($address1);

    // get user 2  (user 2 only has address 2 in the database)
    $user2 = $userRepo->findOneBy(['id' => 2]);
    dump($user2);

    // set user 2 onto address 1
    $address1->setUserField($user2);
    dump($address1);
    dump($user2);

用户 2 现在确实有地址 1。将地址添加到用户也会触发从数据库中加载现有地址,因此它现在也有地址 2。

    // load user 2's addresses from the database
    $user2->getAddresses()->toArray();
    dump($user2);

(地址已经加载,没有变化。)

使用持久化和刷新

如果您添加$userField-&gt;addAddress($this); 行,但添加persist & flush:

    // get address 1 (this is attached to user 1 in the database)
    $address1 = $addressRepo->findOneBy(['id' => 1]);
    dump($address1);

    // get user 2  (user 2 only has address 2 in the database)
    $user2 = $userRepo->findOneBy(['id' => 2]);
    dump($user2);

    // set user 2 onto address 1
    $address1->setUserField($user2);
    dump($address1);
    dump($user2);

用户 2 没有地址 1。

    // persist address 1
    $em->persist($address1);
    $em->flush($address1);

    // load user 2's addresses from the database
    $user2->getAddresses()->toArray();
    dump($user2);

现在用户 2 已经从数据库中加载了地址 1 和地址 2。

【讨论】:

  • 非常感谢您的清晰解释!谢谢@Marleen!实际上你写的一切都是正确的!
  • 他假设 Customer 和 Company 对象都已完全加载。如果您随后在没有 $company-&gt;setCustomer($this) 行的情况下调用 setCompany 方法,然后执行 $customer = $company-&gt;getCustomer(),那么您还将获得“旧”客户。
  • 但是如果公司的客户还没有从数据库中加载,并且你在没有$company-&gt;setCustomer($this) 行的情况下调用setCompany 方法,然后持久化和刷新,然后调用$customer = $company-&gt;getCustomer() 那么你会确实从数据库中获取“新”客户。
  • 啊,我看到了@Marleen,所以这与您上次的截图有点不同:-) 再次感谢 :) 现在我们已经解决了这个问题 :D
  • 在谈到面向对象设计时,您总是假设所有对象引用都存在,就好像没有数据库一样。对于数据库,情况可能与我上一个示例中的情况相同,或者已经加载了所有或部分引用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-20
  • 1970-01-01
  • 2021-07-06
  • 2014-08-23
  • 1970-01-01
  • 2021-04-01
  • 1970-01-01
相关资源
最近更新 更多