nematite10
nematite10

Reputation: 11

form symfony 2 many many self reference entity

I would like to create a form with a collection of self reference entity.

I need a form to create new Product ,this form will have a select field (child) with existing products.

I have a product entity and this entity include a child field (child is a product too).

Product entity :

/**
 * @var integer
 *
 * @ORM\Column(name="id", type="bigint", length=20)
 * @ORM\Id
 * @ORM\GeneratedValue(strategy="AUTO")
 */
protected $id;

/**
 * @var string
 *
 * @ORM\Column(name="title", type="string", length=255)
 */
protected $title;

/**
 * @var string
 *
 * @ORM\Column(name="manufacturer_reference", type="string", length=255, nullable=true)
 */
protected $manufacturer_reference;

/**
 * @var string
 *
 * @ORM\Column(name="resume", type="text", nullable=true)
 */
protected $resume;

/**
 * @var boolean
 *
 * @ORM\Column(name="is_salable", type="boolean", options={"default" = 1})
 */
protected $is_salable = 1;

/**
 * @var boolean
 *
 * @ORM\Column(name="is_active", type="boolean", options={"default" = 1})
 */
protected $is_active = 1;

/**
 * @ORM\ManyToOne(targetEntity="\Hexanet\Common\CatalogBundle\Entity\ProductCategory")
 * @ORM\JoinColumn(name="product_category_id", referencedColumnName="id", nullable=true)
 */
protected $product_category;

/**
 * @ORM\ManyToOne(targetEntity="\Hexanet\Common\CatalogBundle\Entity\Manufacturer")
 * @ORM\JoinColumn(name="manufacturer_id", referencedColumnName="id", nullable=true)
 */
protected $manufacturer;

/**
 * @ORM\ManyToMany(targetEntity="\Hexanet\Common\CatalogBundle\Entity\Product", mappedBy="parents" )
 */
protected $children;

/**
 * @ORM\ManyToMany(targetEntity="\Hexanet\Common\CatalogBundle\Entity\Product")
 * @ORM\JoinTable(name="product_to_product",
 *     joinColumns={@ORM\JoinColumn(name="child_product_id", referencedColumnName="id")},
 *     inverseJoinColumns={@ORM\JoinColumn(name="parent_product_id", referencedColumnName="id")}
 * )
 */
protected $parents;

/**
 * @ORM\OneToMany(targetEntity="\Hexanet\Common\CatalogBundle\Entity\ProductPrice", mappedBy="product" )
 */
protected $product_prices;

/**
 * @ORM\OneToMany(targetEntity="\Hexanet\Common\CatalogBundle\Entity\ProductPricePurchase", mappedBy="product")
 */
protected $product_prices_purchase;

/**
 * @ORM\OneToMany(targetEntity="\Hexanet\Common\CatalogBundle\Entity\ProductPriceCustom", mappedBy="product")
 */
protected $product_prices_custom;

/**
 * Get id
 *
 * @return integer
 */
public function getId()
{
    return $this->id;
}

/**
 * Set title
 *
 * @param string $title
 * @return Product
 */
public function setTitle($title)
{
    $this->title = $title;

    return $this;
}

/**
 * Get title
 *
 * @return string
 */
public function getTitle()
{
    return $this->title;
}

/**
 * Set product category
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\ProductCategory $product_category
 * @return Product
 */
public function setProductCategory(\Hexanet\Common\CatalogBundle\Entity\ProductCategory $product_category = null)
{
    $this->product_category = $product_category;

    return $this;
}

/**
 * Get product category
 *
 * @return \Hexanet\Common\CatalogBundle\Entity\ProductCategory
 */
public function getProductCategory()
{
    return $this->product_category;
}

/**
 * Set resume
 *
 * @param string $resume
 * @return Product
 */
public function setResume($resume)
{
    $this->resume = $resume;

    return $this;
}

/**
 * Get resume
 *
 * @return string
 */
public function getResume()
{
    return $this->resume;
}

/**
 * Set manufacturer reference
 *
 * @param string $title
 * @return Product
 */
public function setManufacturerReference($ref)
{
    $this->manufacturer_reference = $ref;

    return $this;
}

/**
 * Get manufacturer reference
 *
 * @return string
 */
public function getManufacturerReference()
{
    return $this->manufacturer_reference;
}

/**
 * Set is salable
 *
 * @param boolean $active
 * @return Product
 */
public function setIsSalable($salable)
{
    $this->is_salable = $salable;

    return $this;
}

/**
 * Get is salable
 *
 * @return boolean
 */
public function getIsSalable()
{
    return $this->is_salable;
}

/**
 * Set is active
 *
 * @param boolean $active
 * @return Product
 */
public function setIsActive($active)
{
    $this->is_active = $active;

    return $this;
}

/**
 * Get is active
 *
 * @return boolean
 */
public function getIsActive()
{
    return $this->is_active;
}

/**
 * Set manufacturer
 *
 * @param $manufacturer
 * @return Product
 */
public function setManufacturer($manufacturer)
{
    $this->manufacturer = $manufacturer;

    return $this;
}

/**
 * Get manufacturer
 *
 */
public function getManufacturer()
{
    return $this->manufacturer;
}

/**
 * Constructor
 */
public function __construct()
{
    $this->parents = new \Doctrine\Common\Collections\ArrayCollection();
    $this->children = new \Doctrine\Common\Collections\ArrayCollection();
    $this->product_prices = new \Doctrine\Common\Collections\ArrayCollection();
    $this->product_prices_purchase = new \Doctrine\Common\Collections\ArrayCollection();
    $this->product_prices_custom = new \Doctrine\Common\Collections\ArrayCollection();
}

/**
 * Add child
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\Product $product
 * @return Product
 */
public function addChild(\Hexanet\Common\CatalogBundle\Entity\Product $product)
{
    die(var_dump($product));
    $this->children[] = $product;

    return $this;
}

/**
 * Remove child
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\Product $product
 */
public function removeChild(\Hexanet\Common\CatalogBundle\Entity\Product $product)
{
    $this->children->removeElement($product);
}

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

/**
 * Add parent
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\Product $product
 * @return Product
 */
public function addParent(\Hexanet\Common\CatalogBundle\Entity\Product $product)
{
    $this->parents[] = $product;

    return $this;
}

/**
 * Remove parent
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\Product $price
 */
public function removeParent(\Hexanet\Common\CatalogBundle\Entity\Product $product)
{
    $this->parents->removeElement($product);
}

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

/**
 * Add product price
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\ProductPrice $price
 * @return Product
 */
public function addProductPrice(\Hexanet\Common\CatalogBundle\Entity\ProductPrice $price)
{
    $this->product_prices[] = $price;

    return $this;
}

/**
 * Remove product price
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\ProductPrice $price
 */
public function removeProductPrice(\Hexanet\Common\CatalogBundle\Entity\ProductPrice $price)
{
    $this->product_prices->removeElement($price);
}

/**
 * Get product prices
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getProductPrices()
{
    return $this->product_prices;
}

/**
 * Add product price purchase
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\ProductPricePurchase $price
 * @return Product
 */
public function addProductPricePurchase(\Hexanet\Common\CatalogBundle\Entity\ProductPricePurchase $price)
{
    $this->product_prices_purchase[] = $price;

    return $this;
}

/**
 * Remove product price purchase
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\ProductPricePurchase $price
 */
public function removeProductPricePurchase(\Hexanet\Common\CatalogBundle\Entity\ProductPricePurchase $price)
{
    $this->product_prices_purchase->removeElement($price);
}

/**
 * Get product prices purchase
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getProductPricesPurchase()
{
    return $this->product_prices_purchase;
}

/**
 * Add product price custom
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\ProductPriceCustom $price
 * @return Product
 */
public function addProductPriceCustom(\Hexanet\Common\CatalogBundle\Entity\ProductPriceCustom $price)
{
    $this->product_prices_custom[] = $price;

    return $this;
}

/**
 * Remove product price custom
 *
 * @param \Hexanet\Common\CatalogBundle\Entity\ProductPriceCustom $price
 */
public function removeProductPriceCustom(\Hexanet\Common\CatalogBundle\Entity\ProductPriceCustom $price)
{
    $this->product_prices_custom->removeElement($price);
}

/**
 * Get product prices custom
 *
 * @return \Doctrine\Common\Collections\Collection
 */
public function getProductPricesCustom()
{
    return $this->product_prices_custom;
}}

for the form i have this :

class ProductType extends AbstractType{

public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('title')
        ->add('manufacturer_reference')
        ->add('resume')
        ->add('product_category', 'entity', array(
        'class' => 'HexanetCatalogBundle:ProductCategory',
        'property' => 'title',
        ))
        ->add('children', 'collection', array(
        'type' => new ProductChildrenType,
        'allow_add' => true));
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Hexanet\Common\CatalogBundle\Entity\Product'
    ));
}

public function getName()
{
    return 'hexanet_common_catalogbundle_producttype';
}}

The problem is there, i dont know how to create the ProductChildrenType builder :

class ProductChildrenType extends AbstractType{
public function buildForm(FormBuilderInterface $builder, array $options)
{
    $builder
        ->add('product', 'entity', array(
        'class' => 'HexanetCatalogBundle:Product',
        'property' => 'title',
        ));
}

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(array(
        'data_class' => 'Hexanet\Common\CatalogBundle\Entity\Product'
    ));
}

public function getName()
{
    return 'hexanet_common_catalogbundle_productchildrentype';
}}

->add('product', 'entity',...) I have the error :

Neither property "product" nor method "getProduct()" nor method "isProduct()" exists in class "Hexanet\Common\CatalogBundle\Entity\Product".

Thx for the Help

Upvotes: 1

Views: 1228

Answers (1)

Erwin
Erwin

Reputation: 183

I have a similar case for a store, so i can add extra products on the admin area, so i can offer them on checkout... My partner at work and me solved this problem yesterday, so if you're still interested, here we go.... We are using Symfony 2.6.x , i haven't tested it on older versions of symfony yet.

->add('myExtras', 'collection', array(
            'type' => 'entity',
            'options' => array(
                'class'       => 'StoreBundle:Productos',
                'placeholder' => '-- Select an extra product --',
                'property' => 'name',
                'query_builder' => function (EntityRepository $er) use( $options ) {
                    return $er->createQueryBuilder('p')
                        ->where('p.asociable = :asociable')
                        ->andWhere('p.id != :selfid')
                        ->setParameters( array('adjuntable' => '1', 'selfid' => $options['selfid'] ));
                },
                'label' => 'Extra Product'
            ),
            'by_reference' => false,
            'allow_add'    => true,
            'allow_delete' => true
        ))

instead of using a collection of form type for "children", we used a collection of type "entity", and we used a querybuilder to control the conditions we needed to get the right options to show.

using this we stopped having the messages, that you're getting... and for saving and removing the relation, when we add the children, we had to tell the children to set the parent... and for removing the same, first tell the children to remove the parent from the parent's list, and then remove the children from the children list... (in code is easier to see)

in the entity i have to collections myExtras (childrens) and imExtraOf (parents), so when adding a children, i have to tell the children i'm receiving the counterpart ->addImExtraOf (i am your father function) ... then we add the product to our extra list. and for removing, the same, we call first ->removeImExtraOf , if you don't do it this way, the relation will not be saved.

the Entity :

public function addMyExtra(Productos $extra)
{
    $extra->addImExtraOf($this);
    if( !$this->myExtras->contains($extra) ) {
        $this->myExtras[] = $extra;
    }


    return $this;
}

public function removeMyExtra(Productos $extra)
{
    $extra->removeImExtraOf($this);
    $this->myExtras->removeElement($extra);
}

the orm mapping (yml): (myExtras = children, imExtraOf = parents )

manyToMany:        
    myExtras:
        targetEntity: Productos
        cascade: [ persist ]
        mappedBy: imExtraOf
        inversedBy: null  
        joinTable:
            name: productos_has_productos
            joinColumns:
                -
                    name: extra_id
                    referencedColumnName: id
            inverseJoinColumns:
                -
                    name: base_id
                    referencedColumnName: id
        orderBy: null
    imExtraOf:
        targetEntity: Productos
        cascade: [ persist ]
        mappedBy: null
        inversedBy: myExtras  
        joinTable:
            name: productos_has_productos
            joinColumns:
                -
                    name: base_id
                    referencedColumnName: id
            inverseJoinColumns:
                -
                    name: extra_id
                    referencedColumnName: id
        orderBy: null

hope it helps someone.

Upvotes: 2

Related Questions