1

I'm developing a e-commerce system with customizable products. Each product may have some options that, for its part, may have one or many values selected by consumer. I can't use the variant approach due the high level of customization of my users (some products could have more than 1M of variants), so I need persist the option combination chosen by costumer.

The form options are assembled dynamically once each product may have distinct options. This form should transform the user choice into a storable structure for a relational database.

Basically, it's my scenario (my attempt):

  • Product
  • Option
  • OptionValue
  • ProductOption
  • Order
  • OrderItem
  • OrderItemOption

Fixture:

  • Option: 1#Salad
    • Values: 1#Tomato, 2#Lettuce, 3#Pickles, 3#Carrot
  • Product: Hamburger
  • ProductOption: 1#Salad
    • Values: 1#Tomato, 2#Lettuce, Picles

My target is something like:

class OrderItemType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $field = $builder->create('options', new OptionPickerType(), ['options' => $options['product']->getOptions()]);
        $field->addModelTransformation(new FixOptionIndexTransformer());
        $builder->add($field);
    }
}

class OptionPickerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        foreach ($options['options'] as $productOption) {
            $name = $productOption->getId();
            $builder->add($name, 'choice', array(
                'choice_list'   => new ObjectChoiceList($productOption->getValues(), 'label', array(), null, 'id'),
                'multiple' => true,
                'cascade_validation' => true,
                'property_path' => '['.$name.']'
            ));
        }
    }
}

$form = $factory->create(new OrderItemType(), ['product' => $product]);

if ($request->isMethod('POST')) {
    $form->bind($request);

    if ($form->isValid()) {
        $item = $form->getItem(); // A collection of ItemOption filled with the OptionValue picked out from Choice field
    }
}

This configuration will return a collection of arrays of OptionValue as expected. In fact it isn't enough for my purposes. What I really need is a flatten collection with all chosen values more some extra data:

class ItemOption
{
    protected $item;
    protected $productOption;
    protected $option; // $productOption->getName()
    protected $optionValue;
    protected $value; // / $optionValue->getLabel()
}

As you can see, the value of Choice field in fact is inside the ItemOption.

After some days trying, I could not figure out how to do this or even think in other approach.

Can you help me?

Marcos Passos
  • 400
  • 1
  • 3
  • 15
  • Here is where using frameworks can be a real pain. Your problem screams out for a NoSQL solution, which isn't really what Symfony was designed to do. – Mike Brant Jan 29 '13 at 22:46
  • 1
    @MikeBrant Symfony is a framework that can work with many different backends. It doesn't come with a specific data persistence backend. It's typically used with Doctrine2, but can also be used with Propel, or configured to use whatever other backend you want. Furthermore, Doctrine2 offers an ODM that makes it trivial for people who have never done anything with a nosql db like Mongo. In short, Symfony is not the problem. The form component that he's asking about is one of the most sophisticated classes available for doing form data manipulation, and can be used with pure PHP classes. – gview Jan 29 '13 at 22:51
  • @MikeBrant, thanks for the considerations. Yes, I agree that it's a case of NoSQL, but we can't use this one. The Symfony2 form component is a powerfull tool and probably there is some solution even that more complicated (like creating a new field type from scratch), but I've no ideas. – Marcos Passos Jan 29 '13 at 23:50
  • You've tagged the question with both symfony-2.1 and symfony-2.2. Which version are you using? Reason I ask is that in 2.2, a new PropertyAccess component was created from classes that existed in the Form component in 2.1 and a solution might lie with utilising these classes. – Adam Elsodaney Mar 10 '13 at 01:28

1 Answers1

1

First of all, often when I find it hard to map a form to my model, I later discover that the model is overly complicated. Simplifying the model to have clear relationships and intermediate objects where necessary (and none where not necessary) often helps here.

That being said, it seems to me that a model transformer on your option picker should do the job:

foreach ($options['options'] as $productOption) {
    // ...
}

$builder->addModelTransformer(new CallbackTransformer(
    // model to normalized
    // needed when setting default values, not sure if required in your case
    function ($modelData) {
    },
    // normalized to model
    // converts the array of arrays of OptionValues to an array of ItemOptions
    function ($normalizedData) {
        $itemOptions = array();

        foreach ($normalizedData as $optionValues) {
            foreach ($optionValues as $optionValue) {
                $itemOption = new ItemOption();
                $itemOption->setProductOption($optionValue->getProductOption());
                $itemOption->setOptionValue($optionValue);
                $itemOptions[] = $itemOption;
            }
        }

        return $itemOptions;
    }
));
Bernhard Schussek
  • 4,823
  • 26
  • 33