Задать вопрос
@sgidlev

Symfony Doctrine Коллекция. Как избежать дублирование записей в отношении (OneToMany — ManyToOne)?

Всех приветствую.
Прошу помочь с задачей дублирования записей в БД, которые создаются при добавлении связанных сущностей.

Итак есть класс Room и связанный класс RoomRenter, у одного помещения может быть много арендаторов и у одного арендатора комнаты может быть только одна комната.

Мне необходимо соблюсти условия, чтобы в таблице RoomRenter были уникальны поля RoomId и RenterId.

Запрос на изменение данных по помещению приходит в виде:
{
    "title": "Room #1",
    "price": 200,
    "square": 200,
    "status": 2,
    "isBusy": true,
    "buildingId": 1,
    "rentersId": [2,3]
}


Стандартная проверка из документации не проходит, т.к. нет сравнения по RenterId:
if (!$this->renters->contains($renter)) {
            $this->renters[] = $renter;
            $renter->setRoom($this);
        }


Поэтому у меня родился такой код:
#[ORM\HasLifecycleCallbacks]
#[ORM\Entity(repositoryClass: RoomRepository::class)]
#[ORM\Table(name: "room", options: ['comment' => 'Помещение.'])]
class Room
{
...

    /**
    * @var Collection<RoomRenter>
    */
    #[ORM\JoinColumn(nullable: true)]
    #[ORM\OneToMany(mappedBy: 'room', targetEntity: RoomRenter::class, cascade: ["persist", "remove"], orphanRemoval: true)]
    private Collection $renters;
    
     /**
     * @param RoomRenter $roomRenter
     * @return $this
     */
    public function addRenter(RoomRenter $roomRenter): self
    {
        $renterExists = false;
        foreach ($this->renters as $renter) {
            if ($roomRenter->getRenter()->getId() === $renter->getRenter()->getId()) {
                $renterExists = true;
            }
        }

        if (!$renterExists) {
            $this->renters[] = $roomRenter;
            $roomRenter->setRoom($this);
        }

        return $this;
    }

    /**
     * @param RoomRenter $renter
     * @return $this
     */
    public function removeRenter(RoomRenter $renter): self
    {
        if ($this->renters->contains($renter)) {
            $this->renters->removeElement($renter);
            // setThe owning side to null (unless already changed)
            if ($renter->getRoom() === $this) {
                $renter->setRoom(null);
            }
        }

        return $this;
    }
    
...
}


#[ORM\HasLifecycleCallbacks]
#[ORM\Entity(repositoryClass: RoomRenterRepository::class)]
#[ORM\Table(name: "room_renter", options: ['comment' => 'Арендаторы в помещении.'])]
#[UniqueEntity(
    fields: ['room', 'renter'],
    message: 'This renter is already in use on that room.',
    errorPath: 'renter',
)]
class RoomRenter {
...

    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column(type: 'integer')]
    private int $id;

    #[ORM\JoinColumn(nullable: false)]
    #[ORM\ManyToOne(targetEntity: Room::class)]
    private ?Room $room;

    #[ORM\JoinColumn(nullable: false)]
    #[ORM\ManyToOne(targetEntity: Renter::class)]
    private Renter $renter;
    
...
 
}


class RoomService {
    ...
    
    private function upsertRoom(Room $room, RoomUpdateRequest $request): Room
    {
        $room->setTitle($request->getTitle());
        $room->setPrice($request->getPrice());
        $room->setStatus($request->getStatus());
        $room->setIsBusy($request->isBusy());
        $room->setSquare($request->getSquare());

        if ($request->getRentersId()) {
            $currentRenters = [];
            if (!$room->getRenters()->isEmpty()) {
                foreach ($room->getRenters() as $renter) {
                    if (!in_array($renter->getRenter()->getId(), $request->getRentersId(), true)) {
                        $room->removeRenter($renter);
                    } else {
                        $currentRenters[] = $renter->getRenter()->getId();
                    }
                }
            }

            foreach ($request->getRentersId() as $renterId) {
                if (!in_array($renterId, $currentRenters, true)) {
                    $room->addRenter(
                        (new RoomRenter())
                            ->setTitle('Test room renter')
                            ->setRenter($this->renterRepository->getRenter($renterId))
                    );
                }
            }
        } else {
            foreach ($room->getRenters() as $renter) {
                $room->removeRenter($renter);
            }
        }
    
    ...
    
}


Может кто знает поизящнее способ?
  • Вопрос задан
  • 107 просмотров
Подписаться 1 Средний 4 комментария
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы