symfony Doctrine -- Связанные сущности -- Пример One To Many связи (с использованием ArrayCollection)

Здесь описан пример, как настроить связь между таблицами House (Дома) и User (Жильцы) -- One To Many (один ко многим), и чтобы она работала. Можно будет обращаться к объекту Дома через объект Жильца, и наоборот.

Создаём связанные таблицы с помощью аннотаций в Entity :

Класс User (Жильцы):

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="`user`")
 */
class User 
{  
    /**
     * @var House 
     * 
     * @ORM\ManyToOne(targetEntity="House", inversedBy="users")
     * @ORM\JoinColumn(name="house_id", referencedColumnName="id")
     */
    protected $house;
    
    
    /**
     * Узнать, к какому дому относится данный юзер (когда уже связаны)
     * 
     * @return House
     */
    public function getHouse(): House
    {
        return $this->house;
    }
    
    /**
     * Установить связь с таблицей домов (когда ещё не связаны)
     * 
     * @param House $house
     * 
     * @return self
     */
    public function setHouse(House $house): self
    {
        $this->house = $house;
        
        return $this;
    }
}

Класс House (Дома):

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;

/**
 * @ORM\Entity
 * @ORM\Table(name="`house`")
 */
class House {
    
    /**
     * @var ArrayCollection
     * 
     * @ORM\OneToMany(targetEntity="User", mappedBy="house")
     */
    protected $users;
       
    public function __construct()
    {
        $this->users = new ArrayCollection();
    }
      
    /**
     * @param User $user
     * 
     * @return self
     */
    public function addUser(User $user): self
    {
        if (!$this->users->contains($user)) { // проверяем, есть ли уже наш юзер в коллекции дома (в свойстве users)
            $user->setHouse($this); // устанавливаем связь между таблицами
            $this->users->add($user); // добавляем юзера в коллекцию (в свойство users)
        }

        return $this;
    }
            
    /**
     * @param User $user
     * 
     * @return self
     */
    public function removeUser(User $user): self
    {
        if ($this->users->contains($user)) { // проверяем, есть ли наш юзер в коллекции дома (в свойстве users)
            $this->users->removeElement($user); // удаляем его из коллекции (из свойства users)
        }

        return $this;
    }
    
    /**
     * @return ArrayCollection
     */
    public function getUsers(): ArrayCollection
    {
        return $this->users; // получить список всех юзеров в коллекции (в свойстве users)
    }
}

Как можно заметить, аннотации настроены в полном соответствии с документацией Symfony.
У классов есть св-ва $house и $users соответственно, с помощью которых мы будем работать в контроллере.
В базе данных таблицы связаны через поля house_id и id соответственно, что указано в аннотациях.

Теперь что касается методов.

  • Для сущности, объектов которой предполагается много (в данном случае - User) создаём стандартные геттер и сеттер для свойства $house.
  • Для сущности, объект которой один будет иметь связи со многими (в данном случае House), свойство $users - объект ArrayCollection. Это определяется в конструкторе класса. О том, кто такой ArrayCollection, для чего он нужен и с чем его едят, можно почитать здесь.
    Создаём методы:
    addUser() - добавляет юзера в коллекцию (в свойство users) и устанавливает связь между таблицами User и House,
    removeUser() - удаляет юзера из коллекции,
    getUsers() - возвращает список всех юзеров данного дома.

Как это использовать в контроллере

Связывать сущности можно как в направлении одна ко многим(с помощью setProp()), так и в обратном(с помощью addProp()). Но рекомендуется привязывать многие к одной (второй вариант), т.к. это красивее с точки зрения здравого смысла:

"к этому Дому привязываем ещё одного Жильца"

==

$house->addUser($user);

Итак, примеры:

Контроллер 1 (Более общий вариант. Жилец и Дом уже существуют и связаны. Добавляем новые данные о Жильце и Доме, Жильца "узнаём" из сессии, а Дом - с помощью связанных таблиц):

// получаем данные из формы
$formData = $request->request->all();
            
// получаем Жильца из сессии
$user = $this->getUser();
$user->setEmail($formData['app_meeting_registration']['email']);
            
// достаём объект Дома, к которому привязан Жилец, и заполняем Дом данными
$house = $user->getHouse();
$house->setFirstFlatNumber($formData['app_meeting_registration']['firstFlatNumber']);
$house->setLastFlatNumber($formData['app_meeting_registration']['lastFlatNumber']);
            
// сохраняем оба обновлённых объекта в БД
$em = $this->getDoctrine()->getManager();
$em->persist($house);
$em->persist($user);
$em->flush();

Контроллер 2 (Происходит регистрация Жилеца. Проверяем, есть ли уже в базе Дом, который указан Жильцом. Если есть - привязываем, если нет - создаём и привязываем.):

$userManager->updateUser($user); // создание нового юзера (при регистрации)

// получаем Адрес из формы для сохранения в House 
$formData = $request->request->all();
$houseAddress = $formData['app_user_registration']['address'];
                
// проверяем, есть ли Дом с таким Адресом в нашей базе
$house = $this->getDoctrine()->getManager()->getRepository(House::class)
        ->findOneBy(['address' => $houseAddress]);
                
// если нет, создаём новый
if (empty($house)) {
     $house = new House();
}
                
// создаём связь (по факту, заполняется поле house_id, являющееся foreign key-ем)
$house->addUser($user); // Вариант предпочтительный. Доступ от одного ко многим
//      $user->setHouse($house); // Тоже сработает. Доступ от многих к одному
$house->setAddress($formData['app_user_registration']['address']);

// сохраняем изменения в БД                
$em = $this->getDoctrine()
    ->getManager();
$em->persist($house);
$em->flush();

Источники

Работа с коллекциями форм:
http://symfony.com/doc/current/form/form...
https://stackoverflow.com/questions/2508...
Связаные сущности:
https://symfony.com/doc/current/doctrine...
ArrayCollection:
https://habrahabr.ru/post/127711/

vedro-compota's picture

"источники" лучше оформлять как список

_____________
матфак вгу и остальная классика =)