Добавление дочерних объектов родительскому объекту, только если они еще не существуют

Я занят разработкой действия для parent, которое должно добавить число детей, заданных пользователем.

У детей есть 3 свойства и их объединение, каждый ребенок всегда должен быть уникальным.

Я использую Symfony и Doctrine, и мой основной родительский класс выглядит следующим образом:

class Parent
{
    /**
     * @var Child[]|ArrayCollection
     *
     * @ORM\OneToMany(targetEntity="Child", mappedBy="parent")
     * @ORM\OrderBy({"dateCreated": "DESC"})
     */
    private $childs;

    /**
     * Add child
     *
     * @param \AppBundle\Entity\Child $child
     *
     * @return Parent
     */
    public function addChild(\AppBundle\Entity\Child $child)
    {
        $this->childs[] = $child;
        $child->setParent($this);
        return $this;
    }

    /**
     * Remove child
     *
     * @param \AppBundle\Entity\Child $child
     */
    public function removeChild(\AppBundle\Entity\Child $child)
    {
        $this->childs->removeElement($child);
    }

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

Мой дочерний класс выглядит так (снова действительно базовый):

class Child
{
    /**
     * @var int
     *
     * @ORM\Column(name="cupboard", type="integer")
     */
    private $cupboard;

    /**
     * @var int
     *
     * @ORM\Column(name="shelf", type="integer")
     */
    private $shelf;

    /**
     * @var int
     *
     * @ORM\Column(name="item", type="integer")
     */
    private $item;

    /**
     * @var Parent
     *
     * @ORM\ManyToOne(targetEntity="Parent", inversedBy="childs")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
     * })
     */
    private $parent;

    /**
     * Set cupboard
     *
     * @param string $cupboard
     *
     * @return Child
     */
    public function setCupboard($cupboard)
    {
        $this->cupboard = $cupboard;

        return $this;
    }

    /**
     * Get cupboard
     *
     * @return int
     */
    public function getCupboard()
    {
        return $this->cupboard;
    }

    /**
     * Set shelf
     *
     * @param string $shelf
     *
     * @return Child
     */
    public function setShelf($shelf)
    {
        $this->shelf = $shelf;

        return $this;
    }

    /**
     * Get shelf
     *
     * @return int
     */
    public function getShelf()
    {
        return $this->shelf;
    }

    /**
     * Set item
     *
     * @param string $item
     *
     * @return Child
     */
    public function setItem($item)
    {
        $this->item = $item;

        return $this;
    }

    /**
     * Get item
     *
     * @return int
     */
    public function getItem()
    {
        return $this->item;
    }

    /**
     * Set parent
     *
     * @param Parent $parent
     *
     * @return Child
     */
    public function setParent(Parent $parent)
    {
        $this->parent = $parent;

        return $this;
    }

    /**
     * Get parent
     *
     * @return Parent
     */
    public function getParent()
    {
        return $this->parent;
    }
}

Затем у меня есть действие за объект parent (вроде того же, что и действие редактирования), которому нужно создать для него детей. Когда я нажимаю ссылку (для конкретного родителя), создается форма, которая имеет три поля ввода:

  • Тумба
  • Полка
  • Количество элементов

Затем пользователь должен указать целыми числами, сколько предметов он хочет добавить в какой шкаф и какую полку. Если элемент (целое число) уже существует в данном шкафу на данной полке, он не должен повторять его, но первое следующее доступное целое число должно использоваться для элемента.

Как я могу сделать это максимально простым? Я понимаю, что я могу использовать функцию addChild из класса parent, но я не уверен, как это сделать.

До сих пор я пытался создать массив и группировать все элементы в соответствии с шкафом и полкой, а затем, если элемент не существует в этом массиве, он должен быть создан.

Это мой код:

public function addItemAction(Request $request, $id = null){
    $parent = $this->admin->getSubject();

    if (!$parent) {
        throw new NotFoundHttpException(sprintf('Unable to find the object with id: %s', $id));
    }

    $em = $this->getDoctrine()->getManager();

    $form = $this->createForm(AddItemType::class);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $kids = $popUnit->getChilds();
        $formParams = $request->request->get('add_item');

        $units = array();
        foreach ($kids as $kid) {
            $units[$kid->getCupboard()][$kid->getShelf()][$kid->getItem()] = $kid->getItem();
        }

        $givenShelf = $units[$formParams['cupboard']][$formParams['shelf']];

        for ($i = 1; $i <= $formParams['itemAmount']; $i++) {
            if (!in_array($i, $givenShelf)) {
                $child = new Child();
                $child->setParent($parent);
                $child->setCupboard($formParams['cupboard']);
                $child->setShelf($formParams['shelf']);
                $child->setItem($i);

                $em->persist($child);
                $em->flush();
            }
        }
        return new RedirectResponse($this->admin->generateUrl('show', array('id' => $parent->getId())));
    }

    return $this->render('AppBundle:Parent:add_childs.html.twig', array(
        'form' => $form->createView(),
        'parent' =>$parent,
    ));
}

За дополнительной информацией, как выглядит мой строитель форм:

public function buildForm(FormBuilderInterface $builder, array $options) {
    $builder
        ->add('cupboard', NumberType::class)
        ->add('shelf', NumberType::class)
        ->add('itemAmount', NumberType::class, array('label' => 'Number of Items'))
    ;
}

Как я могу сделать это действие максимально простым с тем, чтобы убедиться, что на полку в шкафу для родителя добавлены только уникальные элементы. Я не могу изменить свойства и классы. Я должен работать с этим. Я не хочу использовать любой другой сложный способ, как создание любых слушателей или других функций до тех пор, пока функции не будут.

Надеюсь, я хорошо объяснил свою ситуацию, и я надеюсь, что кто-то может помочь мне с хорошей обратной связью.

Ответ 1

Вы правы, что можете изменить функцию addChild класса Parent следующим образом:

    /**
     * Add child
     *
     * @param \AppBundle\Entity\Child $child
     *
     * @return Parent
     */
    public function addChild(Child $child)
    {
        if ( ! is_array($this->childs) || ! in_array($child, $this->childs)) {
            $this->childs[] = $child;
            $child->setParent($this);
        }
        return $this;
    }

Это условное условие просто указывает, что если детей еще нет, добавьте его или если дочерний объект еще не находится в массиве $this->childs, добавьте его.

Ответ 2

Поскольку вы получаете доступ к $childs через removeChild как к коллекции Doctrine:

/**
 * Remove child
 *
 * @param \AppBundle\Entity\Child $child
 */
public function removeChild(\AppBundle\Entity\Child $child)
{
    $this->childs->removeElement($child);
}

Итак, в конструкторе вы должны сначала инициализировать свою коллекцию childs.

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

Чтобы избежать дублирования детей, вам необходимо проверить существующие перед добавлением в коллекцию.

/**
 * Add child
 *
 * @param \AppBundle\Entity\Child $child
 *
 * @return Parent
 */
public function addChild(\AppBundle\Entity\Child $child)
{
    if (!$this->childs->contains($child)) {
        $this->childs->add($child);
        $child->setParent($this);
    }

    return $this;
}