了解如何在处理表单时解决 Symfony 5 异常(Argument 1 passed to Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader::getIdValue() must be an object or null, string given)。
在过去的几天里,我正在研究一个搜索表单,其中的结果由表单中的一些参数过滤。我决定继续使用 FormType 创建的 Symfony 表单,这样我就可以使用 EntityType 字段等等。虽然我认为这比用纯 HTML 编写表单要容易得多,但结果却变得更加复杂,因为我最终遇到了这个奇怪的问题:
Argument 1 passed to Symfony\Bridge\Doctrine\Form\ChoiceList\IdReader::getIdValue() must be an object or null, string given
如何解决异常Argument 1 passed to IdReader::getIdValue()?在本文中,我将向你解释在你的 Symfony 5 项目中解决此问题的 2 种方法。
如何触发错误的示例
在讨论Argument 1 passed to IdReader::getIdValue()的解决办法之前,为了触发这个错误,我们有以下代码。首先,我们确实有一个没有实体的 FormType,它将用于向用户显示一个简单的表单,他应该能够在其中过滤一些带有字段的东西,在这种情况下,只有一个字段,即Categories
字段。FormType 如下所示:
<?php
// src/Form/FilterFormType.php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// 1. Import the Entity Class of Categories
use App\Entity\Categories;
class FilterFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
// 2. Create the Form with your fields, in this casem just a single field
$builder
->add('categories', EntityType::class, [
// Look for choices from the Categories entity
'class' => Categories::class,
// Display as checkboxes
'expanded' => true,
'multiple' => true,
// The property of the Categories entity that will show up on the select (or checkboxes)
'choice_label' => 'name'
]);
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}
此表单将从控制器呈现到 Twig 视图,如下所示:
<?php
// src/Controller/SomeController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use App\Repository\CategoriesRepository;
// 1. Import the FormType
use App\Form\FilterFormType;
class SomeController extends AbstractController
{
public function index(Request $request): Response
{
// 2. Obtain possibly submitted data of the form (we will use a GET form)
// so, the filter_form parameter should contain the information of the form
$data = $request->query->all("filter_form");
// 3. Instantiate the Form with the class with the data that it may be submitted
$form = $this->createForm(FilterFormType::class, $data);
return $this->render('pages/search.html.twig', [
'form' => $form->createView()
]);
}
}
Twig 视图将是以下视图:
{# templates/pages/index.html #}
{% extends 'base.html.twig' %}
{% block body %}
{# Use the GET method for this Form #}
{{ form_start(form, {'method': 'GET'}) }}
<div class="row">
<div class="col-md-12 col-lg-12">
<div class="card">
<div class="card-header">
<div class="card-title">Filter by Category</div>
<div class="card-options">
<a href="#" class="card-options-collapse" data-toggle="card-collapse">
<i class="fe fe-chevron-up"></i>
</a>
</div>
</div>
<div class="card-body">
<div class="custom-controls-stacked">
{# We customized every option in the expanded entity field #}
{% for CategoryField in form.categories %}
<label class="custom-control custom-checkbox">
{{ form_widget(CategoryField, {"attr": {"class": "custom-control-input"}}) }}
<span class="custom-control-label">
{{ form_label(CategoryField) }}
</span>
</label>
{% endfor %}
</div>
</div>
</div>
<button type="submit" class="btn btn-success btn-block">
Filter
</button>
</div>
</div>
{{ form_end(form) }}
{% endblock %}
如果执行此操作,表单将如下所示,允许用户简单地检查要在表单中过滤的类别:
理论上,它应该可以正常工作,但是,如果用户尝试提交表单,则会出现上述异常(🤯)。
有什么问题
问题的发生基本上是因为它没有处理对象(实体),在这种情况下是Categories
对象,它接收一个数组,其中包含以下形式的所选类别的主键:
array:2 [▼
"categories" => array:1 [▼
0 => "1",
1 => "2",
]
"_token" => "ZrHIxYstXtq86hljZ8C_nRpMlQhcXIMoGSVSij3gGwY"
]
搞砸你的代码。在本文中,我将向你解释在你的 Symfony 项目中解决此问题的 2 种方法。
A. 用实体集合替换 id 数组
解决异常Argument 1 passed to IdReader::getIdValue()?解决这个问题的第一个也是最简单的解决方案就是提供已处理(之前createForm
)数组中的对象集合,而不是数值(id)数组。
<?php
// src/Controller/SomeController.php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use App\Repository\CategoriesRepository;
// 1. Import the FormType
use App\Form\FilterFormType;
use App\Entity\Categories;
class SomeController extends AbstractController
{
public function index(Request $request): Response
{
// 2. Obtain possibly submitted data of the form (we will use a GET form)
// so, the filter_form parameter should contain the information of the form
$data = $request->query->all("filter_form");
// !MONKEYPATCH FIX!
// If the user did select at least a single option in the categories filter
if(isset($data["categories"])){
// Retrieve the Categories repository
$em = $this->getDoctrine()->getManager();
$repoCategories = $em->getRepository(Categories::class);
// Replace the categories array (1,2) with the result of a findBy of the Categories Repository
$data["categories"] = $repoCategories->findBy(['id' => $data["categories"]]);
}
// 3. Instantiate the Form with the class with the data that it may be submitted
$form = $this->createForm(FilterFormType::class, $data);
return $this->render('pages/search.html.twig', [
'form' => $form->createView()
]);
}
}
通过这样做,在提交时接收 FormType 的集合将是以下带有对象而不是普通数字的数组:
array:1 [▼
0 => App\Entity\Categories {#822 ▼
-id: "1"
-nombre: "Prestadores de Servicios de Salud"
-slug: "prestadores-de-servicios-de-salud"
},
1 => App\Entity\Categories {#823 ▼
-id: "2"
-nombre: "Bancos de sangre"
-slug: "bancos-de-sangre"
}
]
因此,如果你尝试再次提交表单,至少选择一个类别,它将正常工作,并且在新页面上,它将显示为选中状态。
B. 使用数据转换器
Argument 1 passed to IdReader::getIdValue()的解决办法:如果你不同意猴子补丁解决方案,你可以选择 Data Transformer 解决方案。在这种情况下,在给定的表单逻辑下,数据转换器的结构将不会发生任何变化reverseTransform
。相反,逻辑将始终是将数字数组转换为类别集合。像这样为表单创建DataTransformer(在/Form/DataTransformer
目录下创建transformer ):
<?php
// src/Form/DataTransformer/CategoriesToNumbersTransformer.php
namespace App\Form\DataTransformer;
use App\Entity\Categories;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class CategoriesToNumbersTransformer implements DataTransformerInterface
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* Transforms the numeric array (1,2,3,4) to a collection of Categories (Categories[])
*
* @param Array|null $categories
* @return array
*/
public function transform($categoriesNumber): array
{
$result = [];
if (null === $categoriesNumber) {
return $result;
}
return $this->entityManager
->getRepository(Categories::class)
->findBy(["id" => $categoriesNumber])
;
}
/**
* In this case, the reverseTransform can be empty.
*
* @param type $value
* @return array
*/
public function reverseTransform($value): array
{
return [];
}
}
解决异常Argument 1 passed to IdReader::getIdValue()?现在 DataTransformer 存在,你需要将它附加到 FormType。通过构造函数方法注入它,检索有问题的字段,在本例中为类别,并添加创建的模型转换器:
<?php
namespace App\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Entity\Categories;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
// 1. Use the CategoriesToNumbersTransformer
use App\Form\DataTransformer\CategoriesToNumbersTransformer;
class RegistrosFilterType extends AbstractType
{
// 2. Define the transformer
private $transformer;
// 3. Assign the injected transformer to the class accessible variable
public function __construct(CategoriesToNumbersTransformer $transformer) {
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Build the form as usual
$builder
->add('categories', EntityType::class, [
// looks for choices from this entity
'class' => Categories::class,
'expanded' => true,
'multiple' => true,
'choice_label' => 'name'
]);
;
// 4. Add the Data Transformer
$builder->get('categories')->addModelTransformer($this->transformer);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => null,
]);
}
}
就是这样,就像第一个解决方案一样,如果用户不选择、选择一个或所有类别,表单将成功提交,并且在刷新时,它们仍将被选中。
快乐编码❤️!