I am using your Bundle for my application. I am migrating a symfony 1.4 website to a symfony 2.1 and I have the following problem with the translations, since the doctrine extensions for 1.4 and 2.1 have a different approach. I have two entities, ProductOption (ProductOptionTranslation to keep the translation) and ProductOptionValue (ProductOptionValueTranslation to do the same as before). The relationship between ProductOption and ProductOptionValue is 1:N (i.e Chocolate/Strawberry/Vanilla are Flavours)
What I want is to filter in the index view of the ProductOptionValue by ProductOption, but the name of the ProductOption is in ProductOptionTranslation entity. So I created a new choice type with a datatransformer. The filter form show the choice perfectly, but the filter is not applied because in the code above the form does not have any children since I am using a DataTransformer and not a filter_XXXX field.
protected function addFilters(FormInterface $form, $filterBuilder, $alias = null, array &$parts = array(), $expr = null)
{
/** @var $child FormInterface */
foreach ($form->all() as $child) {
<?php
namespace Ecomm\Bundle\CatalogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* ProductOption
*
* @ORM\Entity(repositoryClass="Ecomm\Bundle\CatalogBundle\Entity\Manager\ProductOptionManager")
* @ORM\Table(name="products_options")
* @ORM\HasLifecycleCallbacks
*/
class ProductOption
{
/**
* @var integer
*
* @ORM\Id
* @ORM\Column(name="product_option_id", type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\OneToMany(targetEntity="Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionTranslation", mappedBy="productOption", cascade={"persist", "remove"})
*
*/
protected $translations;
protected $name;
/**
* @ORM\OneToMany(targetEntity="Ecomm\Bundle\CatalogBundle\Entity\ProductOptionValue", mappedBy="productOption")
**/
protected $productOptionValues;
/**
* Constructor
*/
public function __construct()
{
$this->resetTranslations();
$this->productOptionValues = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add translations
*
* @param \Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionTranslation $translations
* @return ProductOption
*/
public function addTranslation(\Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionTranslation $translation)
{
$this->translations[] = $translation;
$translation->setProductOption($this);
return $this;
}
/**
* Remove translations
*
* @param \Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionTranslation $translations
*/
public function removeTranslation(\Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionTranslation $translations)
{
$this->translations->removeElement($translations);
}
/**
* Get translations
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getTranslations()
{
return $this->translations;
}
public function resetTranslations()
{
$this->translations = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getName()
{
return $this->name;
}
/**
*
* @ORM\PostLoad
*
*/
public function onPostLoad()
{
$this->name = array();
if ($this->translations)
{
foreach($this->translations as $translation)
{
$locale = $translation->getLocale();
$this->name[$locale] = $translation->getName();
}
}
}
/**
* Add productOptionValue
*
* @param \Ecomm\Bundle\CatalogBundle\Entity\ProductOptionValue $productOptionValue
* @return ProductOption
*/
public function addProductOptionValue(\Ecomm\Bundle\CatalogBundle\Entity\ProductOptionValue $productOptionValue)
{
$this->productOptionValues[] = $productOptionValue;
return $this;
}
/**
* Remove productOptionValue
*
* @param \Ecomm\Bundle\CatalogBundle\Entity\ProductOptionValue $productOptionValue
*/
public function removeProductOptionValue(\Ecomm\Bundle\CatalogBundle\Entity\ProductOptionValue $productOptionValue)
{
$this->productOptionValues->removeElement($productOptionValue);
}
/**
* Get productOptionValue
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getProductOptionValues()
{
return $this->productOptionValues;
}
}
<?php
namespace Ecomm\Bundle\CatalogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* ProductOptionValue
*
* @ORM\Entity(repositoryClass="Ecomm\Bundle\CatalogBundle\Entity\Manager\ProductOptionValueManager")
* @ORM\Table(name="products_options_values")
* @ORM\HasLifecycleCallbacks
*/
class ProductOptionValue
{
/**
* @var integer
*
* @ORM\Id
* @ORM\Column(name="product_option_value_id", type="integer", nullable=false)
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* @ORM\OneToMany(targetEntity="Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionValueTranslation", mappedBy="productOptionValue", cascade={"persist", "remove"})
*
*/
protected $translations;
/**
* @var \ProductsOptions
*
* @ORM\ManyToOne(targetEntity="Ecomm\Bundle\CatalogBundle\Entity\ProductOption", inversedBy="productOptionValues")
* @ORM\JoinColumn(name="product_option_id", referencedColumnName="product_option_id", onDelete="RESTRICT")
*/
protected $productOption;
protected $name;
/**
* Constructor
*/
public function __construct()
{
$this->resetTranslations();
}
public function __toString()
{
return "" . $this->name;
}
/**
* Get id
*
* @return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add translations
*
* @param \Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionValueTranslation $translations
* @return ProductOptionValue
*/
public function addTranslation(\Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionValueTranslation $translation)
{
$this->translations[] = $translation;
$translation->setProductOptionValue($this);
return $this;
}
/**
* Remove translations
*
* @param \Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionValueTranslation $translations
*/
public function removeTranslation(\Ecomm\Bundle\CatalogBundle\Entity\Translation\ProductOptionValueTranslation $translations)
{
$this->translations->removeElement($translations);
}
/**
* Get translations
*
* @return \Doctrine\Common\Collections\Collection
*/
public function getTranslations()
{
return $this->translations;
}
public function resetTranslations()
{
$this->translations = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getName()
{
return $this->name;
}
/**
*
* @ORM\PostLoad
*
*/
public function onPostLoad()
{
$this->name = array();
if ($this->translations)
{
foreach($this->translations as $translation)
{
$locale = $translation->getLocale();
$this->name[$locale] = $translation->getName();
}
}
}
/**
* Set productOption
*
* @param \Ecomm\Bundle\CatalogBundle\Entity\ProductOption $productOption
* @return ProductOptionValue
*/
public function setProductOption(\Ecomm\Bundle\CatalogBundle\Entity\ProductOption $productOption)
{
$this->productOption = $productOption;
return $this;
}
/**
* Get productOption
*
* @return \Ecomm\Bundle\CatalogBundle\Entity\ProductOption
*/
public function getProductOption()
{
return $this->productOption;
}
}
<?php
namespace Ecomm\Bundle\CatalogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormError;
use Lexik\Bundle\FormFilterBundle\Filter\ORM\Expr;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Translation\Translator;
class ProductOptionValueFilterType extends AbstractType
{
/**
* @var ServiceContainer
*/
private $sc;
protected $translator;
public function __construct(ContainerInterface $container = null)
{
$this->sc = $container;
$this->translator = $this->sc->get('translator');
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', 'filter_text', array(
'label' => $this->translator->trans('form.filter.label.name'),
'apply_filter' => function (QueryBuilder $queryBuilder, Expr $expr, $field, array $values)
{
if (!empty($values['value']))
{
// add the join if you need it and it not already added
$alias = $queryBuilder->getRootAliases();
$queryBuilder->innerJoin($alias[0].'.translations', 'at');
$queryBuilder->andWhere('at.name = :name')
->setParameter('name', $values['value']);
}
},
))
/*
->add('productOption', 'filter_entity', array(
'label' => $this->translator->trans('form.filter.label.option'),
'class' => 'EcommCatalogBundle:ProductOption',
'empty_value' => 'Choose a Product Option'
))
*/
->add('productOption', $this->sc->get('ecomm.type.product_option_filter_field'));
;
$listener = function(FormEvent $event)
{
// Is data empty?
foreach ($event->getData() as $data) {
if(is_array($data)) {
foreach ($data as $subData) {
if(!empty($subData)) return;
}
}
else {
if(!empty($data)) return;
}
}
$event->getForm()->addError(new FormError('Filter empty'));
};
$builder->addEventListener(FormEvents::POST_BIND, $listener);
}
public function getName()
{
return 'ecomm_bundle_catalogbundle_productoptionvaluefiltertype';
}
}
<?php
namespace Ecomm\Bundle\CatalogBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Ecomm\Bundle\CatalogBundle\Form\DataTransformer\ProductOptionFieldTransformer;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Doctrine\ORM\QueryBuilder;
use Lexik\Bundle\FormFilterBundle\Filter\FilterBuilderExecuterInterface;
use Lexik\Bundle\FormFilterBundle\Filter\ORM\Expr;
use Lexik\Bundle\FormFilterBundle\Filter\Extension\Type\FilterTypeSharedableInterface;
class ProductOptionFilterFieldType extends AbstractType implements FilterTypeSharedableInterface
{
/**
* @var ServiceContainer
*/
private $sc;
public function __construct(ContainerInterface $container = null)
{
$this->sc = $container;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$transformer = new ProductOptionFieldTransformer($this->sc);
$builder->addModelTransformer($transformer);
}
/**
* This method aim to add all joins you need
*/
public function addShared(FilterBuilderExecuterInterface $qbe)
{
$closure = function(QueryBuilder $filterBuilder, $alias, $joinAlias, Expr $expr)
{
// add the join clause to the doctrine query builder
// the where clause for the label and color fields will be added automatically with the right alias later by the Lexik\Filter\QueryBuilderUpdater
$filterBuilder->innerJoin($alias . '.productOption', 'po');
};
// then use the query builder executor to define the join, the join's alias and things to do on the doctrine query builder.
$qbe->addOnce($qbe->getAlias().'.productOption', 'po', $closure);
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'invalid_message' => 'The selected productOption does not exist',
'empty_value' => 'Choose an option',
'choices' => $this->buildData(),
));
}
private function buildData()
{
$em = $this->sc->get('doctrine')->getManager();
$request = $this->sc->get('request');
$choices = array();
$productOptions = $em
->getRepository('EcommCatalogBundle:ProductOption')
->createQueryBuilder('c')
->select('c, ct')
->innerJoin('c.translations', 'ct')
->andWhere('ct.locale = :locale')
->setParameter('locale', $request->getLocale())
->orderBy('ct.name', 'ASC')
->getQuery()
->getResult();
foreach ($productOptions as $productOption)
{
// I assume key is retrieved by getId
$translation = $productOption->getTranslations();
$choices[$productOption->getId()] = $translation[0]->getName();
}
return $choices;
}
public function getName()
{
return 'product_option_filter_field';
}
public function getParent()
{
return 'choice';
}
}