转换器字符串 ID ChoicesType (ajax) en 表单中的 ID EntityType (Symfony)

问题描述 投票:0回答:1

抱歉,但是会很长。

我有 6 个链接选择,第一个在 EntityType 中,另外 5 个在 ChoiceType 中。最后 5 个选择将根据前一个选择的数据在 Ajax 中填充。

我的 6 个表处于 ManyToOne 关系。

- car_make  
- car_model  
- car_generation  
- car_serie  
- car_trim  
- car_equipment  

一个车辆表,必须与上面的6个表连接。

Ajax 正确返回我的选择,但 ID 是字符串,除了我需要通过关系(整数 ID)将我的 vehicle 表与其他表连接起来

原理是使用所选标签的数据创建车辆。

提交表单时,我正确获取了 CarMake,但所有其他字段都是 null,认为它来自 ChoiceType,我创建了一个 DataTransformer,但它似乎也不起作用。

我发现的唯一方法是在我的“车辆”表中以简单的 int 创建 6 个字段,然后通过我的控制器使用 $request->request 中的数据手动填充它们。

它有效,但不幸的是我失去了 Symfony 的功能(特别是 get 函数),我发现这个解决方案不太好。

如何确保正确检索 EntityType 中的 ChoiceType ID 以将它们正确链接到我的表?

我认为我们应该使用事件,但我不知道如何去做,我做了不同的测试,但没有找到将 DataTransformer 注入事件的解决方案。

表格类型

<?php

declare(strict_types=1);

namespace App\Form;

use App\Entity\CarMake;
use App\Entity\CarModel;
use App\Entity\Vehicle;
use App\Form\DataTransformer\CarModelToEntityTransformer;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
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\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class CreateVehicleApiType extends AbstractType
{

    public function __construct(private CarModelToEntityTransformer $transformModel){}

    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('car_make', EntityType::class, [
                'class' => CarMake::class,
                'label' => false,
                'placeholder' => 'Sélectionnez une marque',
                'attr' => [
                    'class' => 'linked-select',
                    'data-target' => '#create_vehicle_api_model_id',
                    'data-source' => '/api/getModel/$id'
                ]
            ])
            ->add('model_id', ChoiceType::class, [
                'placeholder' => "Veuillez choisir un modèle",
                'label' => false,
                'attr' => [
                    'class' => 'linked-select',
                    'data-target' => '#create_vehicle_api_generation_id',
                    'data-source' => '/api/getGeneration/$id'
                ]
            ])
            ->add('generation_id', ChoiceType::class, [
                'placeholder' => "Veuillez choisir une génération",
                'label' => false,
                'attr' => [
                    'class' => 'linked-select',
                    'data-target' => '#create_vehicle_api_series_id',
                    'data-source' => '/api/getSerie/$id'
                ],
            ])
            ->add('series_id', ChoiceType::class, [
                'placeholder' => "Veuillez choisir une série",
                'label' => false,
                'attr' => [
                    'class' => 'linked-select',
                    'data-target' => '#create_vehicle_api_trim_id',
                    'data-source' => '/api/getTrim/$id'
                ]
            ])
            ->add('trim_id', ChoiceType::class, [
                'placeholder' => "Veuillez choisir une motorisation",
                'label' => false,
                'required' => false,
                'attr' => [
                    'class' => 'linked-select',
                    'data-target' => '#create_vehicle_api_equipment_id',
                    'data-source' => '/api/getEquipment/$id'
                ]
            ])
            ->add('equipment_id', ChoiceType::class, [
                'placeholder' => "Equipement",
                'label' => false,
                'required' => false
            ])
            ->add('typeName', ChoiceType::class, [
                'label' => 'Type',
                'choices' => [
                    'Berline' => 'Berline',
                    'Utilitaire' => 'Utilitaire',
                    'Citadine' => 'Citadine'
                ]
            ])
            ->add('modelName', HiddenType::class)
            ->add('generationName', HiddenType::class)
            ->add('seriesName', HiddenType::class)
            ->add('trimName', HiddenType::class)
        ;
    }

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

控制器

<?php

declare(strict_types=1);

namespace App\Controller\Admin\AutoAd;

use App\Controller\AbstractController;
use App\Entity\Vehicle;
use App\Form\CreateVehicleApiType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class AutoAdController extends AbstractController
{

    public function __construct(private readonly EntityManagerInterface $em){}

    #[Route('/admin/create-vehicle', name: 'ads_dash')]
    public function index(Request $request): Response
    {
        $vehicle = new Vehicle();
        $form = $this->createForm(CreateVehicleApiType::class, $vehicle);

        $form->handleRequest($request);

        if($form->isSubmitted() && $form->isValid()){
            $data = $request->request->all('create_vehicle_api');
            $vehicle->setModelId((int)$data['model_id']);
            $vehicle->setGenerationId((int)$data['generation_id']);
            $vehicle->setSeriesId((int)$data['series_id']);
            $vehicle->setTrimId((int)$data['trim_id']);
            $vehicle->setEquipmentId((int)$data['equipment_id']);
            $vehicle->setTypeId(1);
            $this->em->persist($vehicle);
            $this->em->flush();
            //return $this->redirectToRoute('ads_create', ['vehicle' => $vehicle->getId()]);
        }

        return $this->render('admin/autoad/index.html.twig', [
            'form' => $form
        ]);
    }
}

数据转换器

<?php

declare(strict_types=1);

namespace App\Form\DataTransformer;

use App\Entity\CarModel;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;

class CarModelToEntityTransformer implements DataTransformerInterface
{

    public function __construct(private readonly EntityManagerInterface $entityManager){}

    public function transform($value): mixed
    {
        if (null === $value) {
            return '';
        }

        return $value->getId();
    }

    public function reverseTransform($value): ?CarModel
    {
        if(!$value) {
            return null;
        }

        $carModel = $this->entityManager
            ->getRepository(CarModel::class)
            ->findOneBy(['id' => $value])
            ;
        if($value === null) {
            throw new TransformationFailedException(sprintf(
                'An CarModel ID with number "%s" does not exist!',
                $value
            ));
        }
        return $carModel;
    }
}

Javascript(用于选择 html)

function debounce(callback, delay){
    let timer;
    return function () {
        let args = arguments;
        let context = this;
        clearTimeout(timer);
        timer = setTimeout(function () {
            callback.apply(context, args)
        }, delay)
    }
}

class LinkedSelect {

    /**
     *
     * @param {HTMLSelectElement} $select
     */
    constructor($select) {
        this.$select = $select
        this.$target = document.querySelector(this.$select.dataset.target)
        this.$placeholder = this.$target.firstElementChild
        this.onChange = debounce(this.onChange.bind(this), 500)
        this.$loader = null
        this.cache = {}
        this.$select.addEventListener('change', this.onChange)
    }

    /**
     * Se déclenche au changement de valeur d'un select
     * @param {Event} e
     */
    onChange (e) {
        this.loadOptions(e.target.value, (options) => {
            this.$target.innerHTML = options;
            this.$target.insertBefore(this.$placeholder, this.$target.firstChild)
            this.$target.selectedIndex = 0
            this.$target.style.display = null
        })
    }

    /**
     *
     * @param {string} id
     * @param callback
     */
    loadOptions (id, callback) {
        if(this.cache[id]){
            callback(this.cache[id])
            return
        }
        this.showLoader()
        let request = new XMLHttpRequest()
        request.open('GET', this.$select.dataset.source.replace('$id', id), true)
        request.onload = () => {
            if(request.status >= 200 && request.status < 400){
                let data = JSON.parse(request.responseText)
                let options = data.reduce(function (acc, option) {
                    return acc + '<option value="'+ option.id +'">' + option.name +'</option>'
                }, '')

                this.cache[id] = options
                this.hideLoader()
                callback(options)
            } else {
                alert('Impossible de charger la liste')
            }
        }
        request.onerror = function () {
            alert('Impossible de charger la liste')
        }
        request.send()
    }

    showLoader () {
        this.$loader = document.createTextNode('Chargement...')
        this.$target.parentNode.appendChild(this.$loader);
    }

    hideLoader () {
        if(this.$loader !== null){
            this.$loader.parentNode.removeChild(this.$loader);
            this.$loader = null
        }
    }
}

let $select = document.querySelectorAll('.linked-select')

$select.forEach(function ($select){
    new LinkedSelect($select);
})

再次抱歉,篇幅较长,并提前感谢您的回答。

javascript php ajax symfony
1个回答
0
投票

您可以为每个选择/下拉菜单执行的操作是创建一个嵌套到父表单中的“类型”。这将显示人类可读的正确名称/字符串,并且仍然保留您的 ID 的 onSubmit。

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder->add(
        'parent',
        EntityType::class,
        [
            'class'         => Element::class,
            'label'         => false,
            'placeholder'   => '-- Select Element --',
            'multiple'      => false,
            'attr'          => [ 'class' => 'select2' ],
            'choice_label' => function (?Element $element): string {
                return $element->getName() .
                    ' (' . $element->getIdentifier() . ') - ' .
                    substr($element->getContent(), 0, 80);
                ;
            },
            'query_builder' => function (ElementRepository $elementRepo) {
                return $elementRepo->findElements();
            }
        ]
    );
}

在家长表格中

    ->add(
        'model',
        NestedFormType::class,
        [
            'label'        => 'Elements',
            'by_reference' => false,
            'required'     => false,
        ]
    )
© www.soinside.com 2019 - 2024. All rights reserved.