Trouble loading in edit form, incorrect form view data instance

78 views Asked by At

I have a module which is setup with the following entities like this:

  • Product
    • ProductTranslation
      • ContentBlock

I can add a new product just fine but when I go to edit it, the form can't be loaded. It gives me the following error:

The form's view data is expected to be an instance of class ContentBlockDataTransferObject, but is an instance of class ContentBlock. You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms an instance of class ContentBlock to an instance of ContentBlockDataTransferObject.

Setting the data_class to null didn't seem to change anything.

ProductType.php

<?php

namespace Backend\Modules\Products\Domain\Product;

use Backend\Core\Language\Language;
use Backend\Modules\MediaLibrary\Domain\MediaGroup\MediaGroupType;
use Backend\Modules\Products\Domain\Categories\Category;
use Backend\Modules\Products\Domain\Divisions\Division;
use Common\Form\CollectionType;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query\Expr\Join;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Common\Form\ImageType;
use Symfony\Component\Validator\Constraints\Valid;

final class ProductType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add(
            'categories',
            EntityType::class,
            [
                'class' => Category::class,
                'multiple' => true,
                'expanded' => true,
                'label' => 'lbl.Categories',
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('i')
                        ->join('i.translations', 't')
                        ->innerJoin(Division::class, 'd', Join::WITH, 'i.division = d')
                        ->addOrderBy('d.id', 'ASC')
                        ->addOrderBy('t.title', 'ASC');
                },
            ]
        );
        $helpTextMessageMainImage = sprintf(
            Language::msg('HelpImageFieldWithMaxFileSize'),
            $this->getUploadMaxFileSize()
        );
        $helpTextMessageMainImage .= '<br><strong>' . Language::msg('MainImageHelp') . '</strong>';
        $builder->add(
            'mainImage',
            ImageType::class,
            [
                'image_class' => MainImage::class,
                'help_text_message' => $helpTextMessageMainImage,
            ]
        );
        $builder->add(
            'translations',
            CollectionType::class,
            [
                'entry_type' => ProductTranslationType::class,
                'error_bubbling' => false,
                'constraints' => [new Valid()],
            ]
        );
        $builder->add(
            'imageGallery',
            MediaGroupType::class,
            [
                'required' => false,
            ]
        );
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults(['data_class' => ProductDataTransferObject::class]);
    }

    private function getUploadMaxFileSize(): ?string
    {
        $uploadMaxFileSize = ini_get('upload_max_filesize');
        if ($uploadMaxFileSize === false) {
            return null;
        }

        // reformat if defined as an integer
        if (is_numeric($uploadMaxFileSize)) {
            return $uploadMaxFileSize / 1024 . 'MB';
        }

        // reformat if specified in kB
        if (mb_strtoupper(mb_substr($uploadMaxFileSize, -1)) === 'K') {
            return mb_substr($uploadMaxFileSize, 0, -1) . 'kB';
        }

        // reformat if specified in MB
        if (mb_strtoupper(mb_substr($uploadMaxFileSize, -1)) === 'M') {
            return $uploadMaxFileSize . 'B';
        }

        // reformat if specified in GB
        if (mb_strtoupper(mb_substr($uploadMaxFileSize, -1)) === 'G') {
            return $uploadMaxFileSize . 'B';
        }

        return $uploadMaxFileSize;
    }
}

ProductTranslationType.php

<?php

namespace Backend\Modules\Products\Domain\Product;

use Backend\Core\Engine\Model;
use Backend\Core\Language\Language;
use Backend\Form\Type\EditorType;
use Backend\Form\Type\MetaType;
use Backend\Modules\Products\Domain\ContentBlock\ContentBlockType;
use Backend\Modules\Products\Domain\Product\Command\CreateProduct;
use Backend\Modules\Products\Domain\Product\Command\UpdateProduct;
use Backend\Modules\Products\Domain\Product\ProductTranslation\ProductTranslationRepository;
use Common\Form\CollectionType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;

final class ProductTranslationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add(
                'title',
                TextType::class,
                [
                    'label' => 'lbl.Title',
                ]
            )
            ->add(
                'contentBlocks',
                CollectionType::class,
                [
                    'entry_type' => ContentBlockType::class,
                    'allow_delete' => true,
                    'allow_add' => true,
                    'allow_sequence' => false,
                    'attr' => [
                        'class' => 'tab-pane tab-pane-collection',
                        'data-role' => 'products-content-blocks',
                    ],
                ]
            )
            ->addEventListener(
                FormEvents::PRE_SET_DATA,
                function (FormEvent $event) {
                    /** @var CreateProduct|UpdateProduct $productFormData */
                    $productFormData = $event->getForm()->getParent()->getParent()->getData();

                    // Add meta
                    $event->getForm()
                        ->add(
                            'meta',
                            MetaType::class,
                            [
                                'detail_url' => Model::getUrlForBlock('Products', 'ProductDetail'),
                                'base_field_name' => 'title',
                                'generate_url_callback_class' => ProductTranslationRepository::class,
                                'generate_url_callback_method' => 'self::getUrl',
                                'generate_url_callback_parameters' => [
                                    $event->getData()->getLocale(),
                                    $productFormData->hasExistingProduct()
                                        ? $productFormData->getProductEntity()->getId() : null,
                                ],
                            ]
                        );
                }
            );
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults(['data_class' => ProductTranslationDataTransferObject::class]);
    }
}

ContentBlockType.php

<?php

namespace Backend\Modules\Products\Domain\ContentBlock;

use Backend\Core\Engine\TemplateModifiers;
use Backend\Form\Type\EditorType;
use Common\Form\CollectionType;
use Common\Form\ImageType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\Valid;

final class ContentBlockType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add(
            'sequence',
            HiddenType::class,
            [
                'required' => false,
                'attr' => [
                    'data-role' => 'sequence'
                ]
            ]
        );
        $builder->add(
            'type',
            ChoiceType::class,
            [
                'label' => 'lbl.Type',
                'choices' => array_map(
                    function ($contentType) {
                        return new ContentType($contentType);
                    },
                    ContentType::POSSIBLE_VALUES
                ),
                'choice_label' => function (ContentType $contentType) {
                    return TemplateModifiers::toLabel($contentType);
                },
                'choice_translation_domain' => false,
                'choice_value' => function (ContentType $contentType = null) {
                    return (string) $contentType;
                },
                'attr' => [
                    'data-role' => 'products-content-blocks-type',
                ],
            ]
        );
        $builder->add(
            'text',
            EditorType::class,
            [
                'label' => 'lbl.Text',
                'required' => false,
                'attr' => [
                    'class' => 'inputEditor',
                    'data-show' => 'type-text',
                ],
            ]
        );
        $builder->add(
            'image',
            ImageType::class,
            [
                'label' => 'lbl.Image',
                'image_class' => Image::class,
                'required' => false,
                'show_remove_image' => false,
            ]
        );
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults(['data_class' => ContentBlockDataTransferObject::class]);
    }
}

ProductAdd.php Action

<?php

namespace Backend\Modules\Products\Actions;

use Backend\Core\Engine\Base\ActionAdd;
use Backend\Core\Language\Language;
use Backend\Core\Language\Locale;
use Backend\Modules\Products\Domain\Product\Command\CreateProduct;
use Backend\Modules\Products\Domain\Product\Command\CreateProductHandler;
use Backend\Modules\Products\Domain\Product\ProductType;
use Backend\Core\Engine\Model as BackendModel;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormError;

final class ProductAdd extends ActionAdd
{
    public function execute(): void
    {
        parent::execute();

        $form = $this->getForm();

        if (!$form->isSubmitted() || !$form->isValid()) {
            $this->template->assign('activeTranslationTab', 'tab' . ucfirst(Locale::workingLocale()));
            $this->template->assign('locale', Locale::workingLocale());
            $this->template->assign('form', $form->createView());
            $this->template->assign('backLink', $this->getBackLink());
            $this->template->assign('imageGallery', $form->getData()->imageGallery);

            $this->parse();
            $this->display();

            return;
        }

        $this->handleForm($form);
    }

    private function handleForm(Form $form): void
    {
        /** @var CreateProduct $product */
        $product = $form->getData();

        // Handle the Product create
        $this->get('command_bus')->handle($product);

        $this->redirect(
            $this->getBackLink(
                [
                    'report' => 'added',
                    'var' => $product->translations[(string) Locale::workingLocale()]->title,
                ]
            )
        );
    }

    private function getForm(): Form
    {
        $form = $this->createForm(ProductType::class, new CreateProduct());
        $form->handleRequest($this->getRequest());

        return $form;
    }

    private function getBackLink(array $parameters = []): string
    {
        return BackendModel::createUrlForAction(
            'ProductsIndex',
            null,
            null,
            $parameters
        );
    }
}

ProductEdit.php Action

<?php

namespace Backend\Modules\Products\Actions;

use Backend\Core\Engine\Base\ActionEdit;
use Backend\Core\Language\Language;
use Backend\Core\Language\Locale;
use Backend\Form\Type\DeleteType;
use Backend\Modules\Products\Domain\Product\Command\UpdateProductHandler;
use Backend\Modules\Products\Domain\Product\Product;
use Backend\Modules\Products\Domain\Product\Command\UpdateProduct;
use Backend\Core\Engine\Model as BackendModel;
use Backend\Modules\Products\Domain\Product\ProductRepository;
use Backend\Modules\Products\Domain\Product\ProductType;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormError;

final class ProductEdit extends ActionEdit
{
    public function execute(): void
    {
        parent::execute();

        /** @var Product $product */
        $product = $this->getProduct();

        /** @var Form $form */
        $form = $this->getForm($product);

        $deleteForm = $this->createForm(
            DeleteType::class,
            ['id' => $product->getId()],
            [
                'module' => $this->getModule(),
                'action' => 'ProductDelete',
            ]
        );

        $this->template->assign('deleteForm', $deleteForm->createView());

        if (!$form->isSubmitted() || !$form->isValid()) {
            $this->template->assign('activeTranslationTab', 'tab' . ucfirst(Locale::workingLocale()));
            $this->template->assign('locale', Locale::workingLocale());
            $this->template->assign('form', $form->createView());
            $this->template->assign('product', $product);
            $this->template->assign('imageGallery', $form->getData()->imageGallery);

            $this->parse();
            $this->display();

            return;
        }

        /** @var UpdateProduct $updateProduct */
        $updateProduct = $form->getData();

        // Handle the Product update
        $this->get('command_bus')->handle($updateProduct);

        $this->redirect(
            $this->getBackLink(
                [
                    'report' => 'edited',
                    'var' => $updateProduct->translations[(string)Locale::workingLocale()]->title,
                    'highlight' => 'row-' . $updateProduct->getProductEntity()->getId(),
                ]
            )
        );
    }

    private function getProduct(): Product
    {
        try {
            /** @return Product */
            return $this->get(ProductRepository::class)->findOneById($this->getRequest()->query->get('id'));
        } catch (\Exception $e) {
            $this->redirect(
                $this->getBackLink(
                    [
                        'error' => 'non-existing'
                    ]
                )
            );
        }
    }

    private function getBackLink(array $parameters = []): string
    {
        return BackendModel::createUrlForAction(
            'ProductsIndex',
            null,
            null,
            $parameters
        );
    }

    private function getForm(Product $product): Form
    {
        $form = $this->createForm(ProductType::class, new UpdateProduct($product));
        $form->handleRequest($this->getRequest());

        return $form;
    }

}
0

There are 0 answers