抱歉,但是会很长。
我有 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);
})
再次抱歉,篇幅较长,并提前感谢您的回答。
您可以为每个选择/下拉菜单执行的操作是创建一个嵌套到父表单中的“类型”。这将显示人类可读的正确名称/字符串,并且仍然保留您的 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,
]
)