Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add children objects to a parent only if they don't yet exist

Tags:

php

symfony

I am busy developing an action for parent that should add a number of children given by the user input.

The children have 3 properties, and combining them, each child should always be unique.

I make use of Symfony and Doctrine and my basic parent class looks like this:

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;
    }
}

My child class look like this (again really basic):

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;
    }
}

Then I have an action per parent object (kind of the same as an edit action) that has to create children for it. When I click the link (for the specific parent) a form is generated that has three input fields:

  • Cupboard
  • Shelf
  • Number of items

The user then has to specify with integers how many items he wants to add in what cupboard and what shelf. If the item (integer) already exist in that given cupboard on that given shelf it should not make it again, but the first next available integer should be used for item.

How can I make this as simple as possible? I understand I can use the addChild function from the Parent class, but I'm not sure how.

What I have tried so far is to create an array and group all items according to cupboard and shelf and then if the item does not exist in that array it should be created.

This is my code:

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,
    ));
}

For extra information, this is how my form builder looks:

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'))
    ;
}

How can I make this action as simple as possible with the fact to make sure only unique items is added to a shelf in a cupboard for the parent. I can't change the properties nor the classes. I have to work with this. I don' want to use any other complex way like creating any listeners or other until functions.

I hope I've explained my situation well and I hope somebody can help me with some good feedback.

like image 842
Jack Brummer Avatar asked Mar 28 '17 20:03

Jack Brummer


People also ask

What is the purpose of a parent and child object?

When discussing objects a "parent-child" relationship implies a hierarchy of objects which can be represented as a tree with parents possessing strong references to their children. If you can draw that tree of objects a "parent" would be closer to the root while a "child" would be closer to a leaf node.

What are parent objects?

The object to which a given property or method belongs.

How to parent an object to another in blender?

To parent objects, select at least two objects (select the Child Objects first, and select the Parent Object last), and press Ctrl-P . The Set Parent To menu will pop up allowing you to select from one of several possible different parenting types.


2 Answers

You are correct that you can modify the addChild function of the Parent class like this:

    /**
     * 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 conditional simply states that if there are no children yet, add the child or if the child is not already in the $this->childs array then add it.

like image 147
Wes Crow Avatar answered Oct 23 '22 00:10

Wes Crow


Because you access to $childs via removeChild as a Doctrine collection:

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

So, in the constructor, you should initialize your childs collection first.

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

To avoid duplicate children, you need to checks existing before add to collection.

/**
 * 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;
}
like image 28
Pig Ball Avatar answered Oct 22 '22 23:10

Pig Ball