I have the following two entities address and user . and in one of my controllers I have this function :
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
...}
My question is why the first dump prints the user object with an empty array in the addresses field :
#collection: Doctrine\Common\Collections\ArrayCollection {#9487 ▼
-elements: []`
WHEREAS the second dump prints the user object with a non empty array collection in the addresses field (there is actually one address in this array):
#collection: Doctrine\Common\Collections\ArrayCollection {#9487 ▼
-elements: array:1 [▼
0 => App\Entity\address{#81625 ▼`
User :
/**
* @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);
}
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;
}
I've generated a User and an Address class via the Symfony maker bundle.
User:
<?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;
}
}
Address:
<?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;
}
}
Example that fails:
Try this (in a controller for example):
// get address 1 (this is attached to user 1 in the database)
$address1 = $addressRepo->findOneBy(['id' => 1]);
dump($address1);
This is Address 1, its User object has not been initalized yet, but that's not important for this example.
// get user 2 (user 2 only has address 2 in the database)
$user2 = $userRepo->findOneBy(['id' => 2]);
dump($user2);
This is User 2, notice that the Address array is not initialized yet (this is important!).
// set user 2 onto address 1
$address1->setUserField($user2);
dump($address1);
Now Address 1 has User 2.
dump($user2);
But User 2 does not have Address 1.
// load user 2's addresses from the database
$user2->getAddresses()->toArray();
dump($user2);
User 2 now has it's Address 2 loaded from the database (still no Address 1 though).
With $userField->addAddress($this)
If you add the $userField->addAddress($this);
line:
public function setUserField(?User $userField): self
{
$this->userField = $userField;
$userField->addAddress($this);
return $this;
}
And execute the same steps:
// 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);
User 2 now does have Address 1. Adding the Address to the User also triggers the loading of the existing Adresses from the database, so it now also has Address 2.
// load user 2's addresses from the database
$user2->getAddresses()->toArray();
dump($user2);
(The Addresses were already loaded, no changes.)
With persist & flush
And if you do not add the $userField->addAddress($this);
line, but add 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);
User 2 does not have Address 1.
// persist address 1
$em->persist($address1);
$em->flush($address1);
// load user 2's addresses from the database
$user2->getAddresses()->toArray();
dump($user2);
Now User 2 has both Address 1 and Address 2 loaded from the database.