Marcos Passos
Marcos Passos

Reputation: 406

Complex data structure using only the Form component

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):

Fixture:

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?

Upvotes: 1

Views: 403

Answers (1)

Bernhard Schussek
Bernhard Schussek

Reputation: 4841

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

Upvotes: 1

Related Questions