We're running into a small code-design smell with symfony and our forms. It is not a problem per se, but makes me wonder if we could attain our goals any other way.
For the sake of simplicity, let me briefly explain a setup: let "Product" be an entity that represents a product in a database, meant to be sold in an online store. Since the online store is designed to have several languages in it, every single bit of information that could be related to a language is in the entity "Product_descriptions" that is related in a manyToOne fashion to the "Product". Finally we have designed a "Language" entity, representing every single language the user can see the store in.
As you can imagine, the code is pretty standard stuff:
class Language
{
private $language_id;
private $language_name;
private $language_code;
//Some other stuff.
};
class Product
{
private $product_id;
private $product_reference;
private $product_weight;
private $product_descriptions; //As an arrayCollection of "Product_description" objects.
//Some other stuff.
};
class Product_description
{
private $product_description_id;
private $product_name;
private $product_long_description;
private $product_short_description;
private $product; //A reference to the Product itself.
private $language; //A reference to the language this is meant to be seen in.
};
Okay, now for the problem itself. The setup, as expected, works wonderfully. It is in the backend where the smell resides.
To create new products we have designed a symfony form Type. In the same form we would like to be able to set all the product information as well as the information for every possible language. The smell comes in when we need to feed all possible "Language"s to the form type, check if a "Product_description" exists for a "Language" and "Product", show the empty text field (in case it does not exist) or the filled field... Our solution requests that a repository for all languages is injected into the form . Let me show you how it goes (please, take into consideration that this is not the real code... something may be missing):
class ProductType extends AbstractType
{
private $language_repo;
public function __construct($r)
{
$this->language_repo=$r;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('product_name', 'text')
->add('product_code', 'text');
$product=$builder->getData();
//We retrieve all languages here, to check if an entry for that
//language exists and show its data.
$languages=$this->language_repo->findAll();
foreach($languages as $key => &$lan)
{
//Here we look for existing data... This will return null if there's none.
$product_description=$product->get_description_for_language($lan);
$default_name=$product_description ? $product_description->getProductName() : '';
$default_long=$product_description ? $product_description->getProductLongDescription() : '';
$default_short=$product_description ? $product_description->getProductShortDescription() : '';
//Here we manually create the name_#language_id# form data... That we will retrieve later.
$builder->add('name_'.$lan->getLanguageId(), 'text', array(
'label' => 'Name for '.$lan->getName(),
'mapped' => false,
'data' => $default_name))
->add('long_'.$lan->getLanguageId(), 'text', array(
'label' => 'Name for '.$lan->getName(),
'mapped' => false,
'data' => $default_long))
->add('short_'.$lan->getLanguageId(), 'text', array(
'label' => 'Name for '.$lan->getName(),
'mapped' => false,
'data' => $default_short));
}
$builder->add('save', 'submit', array('label' => 'Save data'));
}
//And some other stuff here.
}
As you can see, we are manually setting some data keys that we need to retrieve later in the controller. The setup works, of course. Any new language will yield an empty form field. Any existing language shows the related information.
Now for the controller, this gets messier even... When we're submitting the form we go like this:
private function process_form_data(Form &$f, Product &$item, Request &$request)
{
//Find all languages...
$em=$this->getDoctrine()->getManager();
$languages=$em->getRepository("MyBundle:Language")->findAll();
//Get submitted data for that language..
foreach($languages as $key => &$lan)
{
$name_language=$f->get('name_'.$lan->getLanguageId())->getData();
$long_language=$f->get('long_'.$lan->getLanguageId())->getData();
$short_language=$f->get('short_'.$lan->getLanguageId())->getData();
//Check if the language entry exists... Create it, if it doesn't. Feed the data.
$product_description=$product->get_description_for_language($lan);
if(!$product_description)
{
$product_description=new Product_description();
$product_description->setLanguage($lan);
$product_description->setProduct($product);
}
$product_description->setName($name_language);
$product_description->setLongDescription($long_language);
$product_description->setShortDescription($short_language);
$em->persist($product_description);
}
//Do the product stuff, persist, flush, generate a redirect...Not shown.
}
It works, but seems to me that is not the "symfony" way of doing things. How would you do this?. Have you found a more elegant approach?.
Thanks a lot.
Aucun commentaire:
Enregistrer un commentaire