Php 基于动态表单的Symfony数据转换器
我正在使用FOSRestBundle构建一个API,用于将产品添加到篮子中。为了简化这个例子,我们有一系列不同尺寸的产品 我希望能够在JSON请求中指定大小代码。例如:Php 基于动态表单的Symfony数据转换器,php,symfony,Php,Symfony,我正在使用FOSRestBundle构建一个API,用于将产品添加到篮子中。为了简化这个例子,我们有一系列不同尺寸的产品 我希望能够在JSON请求中指定大小代码。例如: { "product": 3, "size": "S" } (我也希望使用产品代码而不是数据库ID,但这是另一天!) 在项目的其他部分,我使用数据转换器完成了类似的任务,但这些是更简单的形式,其中的值不会根据其他字段选择的值进行更改 所以我现在的篮子形式 class BasketAddType extends
{
"product": 3,
"size": "S"
}
(我也希望使用产品代码而不是数据库ID,但这是另一天!)
在项目的其他部分,我使用数据转换器完成了类似的任务,但这些是更简单的形式,其中的值不会根据其他字段选择的值进行更改
所以我现在的篮子形式
class BasketAddType extends AbstractType
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', 'entity', [
'class' => 'CatalogueBundle:Product',
]);
$builder->get('product')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
public function onPostSubmit(FormEvent $event)
{
// this will be Product entity
$form = $event->getForm();
$this->addElements($form->getParent(), $form->getData());
}
protected function addElements(FormInterface $form, Product $product = null)
{
if (is_null($product)) {
$sizes = [];
} else {
$sizes = $product->getSizes();
}
$form
->add('size', 'size', [
'choices' => $sizes
]);
}
public function getName()
{
return '';
}
}
我在上面使用的自定义大小表单类型是,这样我可以添加模型转换器。正如在这个答案中所发现的那样
最后是变压器
class SizeTransformer implements DataTransformerInterface
{
protected $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function transform($size)
{
if (null === $size) {
return '';
}
return $size->getCode();
}
public function reverseTransform($code)
{
// WE NEVER GET HERE?
$size = $this->em->getRepository('CatalogueBundle:Size')
->findOneByCode($code);
if (null === $size) {
throw new TransformationFailedException('No such size exists');
}
return $size;
}
}
所以我快速退出了
在reverseTransform
中,它从未被激发,因此我总是会在size元素上得到一个关于它无效的错误
在这里,将数据转换器放入大小字段的最佳方法是什么?这是一个相关字段
我有一个与分类实体相关的产品实体,该分类实体与集合实体相关
这是我添加产品表单的代码
class ProductsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$propertyPathToCategory = 'category';
$builder
->add('title')
->add('description','textarea')
->add('collection','entity', array(
'class' => 'FMFmBundle:Collections',
'empty_value' => 'Collection',
'choice_label' => 'title'
))
->addEventSubscriber(new AddCategoryFieldSubscriber($propertyPathToCategory));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'FM\FmBundle\Entity\Products'
));
}
public function getName()
{
return 'fm_fmbundle_products';
}
}
AddCategoryFieldSubscriber
//Form/EventListener
class AddCategoryFieldSubscriber implements EventSubscriberInterface
{
private $propertyPathToCategory;
public function __construct($propertyPathToCategory)
{
$this->propertyPathToCategory = $propertyPathToCategory;
}
public static function getSubscribedEvents()
{
return array(
FormEvents::PRE_SET_DATA => 'preSetData',
FormEvents::PRE_SUBMIT => 'preSubmit'
);
}
private function addCategoryForm($form, $collection_id)
{
$formOptions = array(
'class' => 'FMFmBundle:Categories',
'empty_value' => 'Category',
'label' => 'Category',
'attr' => array(
'class' => 'Category_selector',
),
'query_builder' => function (EntityRepository $repository) use ($collection_id) {
$qb = $repository->createQueryBuilder('category')
->innerJoin('category.collection', 'collection')
->where('collection.id = :collection')
->setParameter('collection', $collection_id)
;
return $qb;
}
);
$form->add($this->propertyPathToCategory, 'entity', $formOptions);
}
public function preSetData(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
if (null === $data) {
return;
}
$accessor = PropertyAccess::createPropertyAccessor();
$category = $accessor->getValue($data, $this->propertyPathToCategory);
$collection_id = ($category) ? $category->getCollection()->getId() : null;
$this->addCategoryForm($form, $collection_id);
}
public function preSubmit(FormEvent $event)
{
$data = $event->getData();
$form = $event->getForm();
$collection_id = array_key_exists('collection', $data) ? $data['collection'] : null;
$this->addCategoryForm($form, $collection_id);
}
}
在控制器中添加新操作
public function SelectCategoryAction(Request $request)
{
$collection_id = $request->get('collecton_id');
$em = $this->getDoctrine()->getManager();
$categories = $em->getRepository('FMFmBundle:Categories')->findByCollection($collection_id);
$Jcategories=array();
foreach($categories as $category){
$Jcategories[]=array(
'id' => $category->getId(),
'title' => $category->getTitle()
);
}
return new JsonResponse($Jcategories);
}
为操作添加新路由
select_category:
path: /selectcategory
defaults: { _controller: FMFmBundle:Product:SelectCategory }
还有一些ajax
$("#collectionSelect").change(function(){
var data = {
collecton_id: $(this).val()
};
$.ajax({
type: 'post',
url: 'selectcategory',
data: data,
success: function(data) {
var $category_selector = $('#categorySelect');
$category_selector.empty();
for (var i=0, total = data.length; i < total; i++)
$category_selector.append('<option value="' + data[i].id + '">' + data[i].title + '</option>');
}
});
});
$(“#collectionSelect”).change(函数(){
风险值数据={
collection_id:$(this.val()
};
$.ajax({
键入:“post”,
url:“selectcategory”,
数据:数据,
成功:功能(数据){
变量$category_selector=$('categorySelect');
$category_selector.empty();
对于(var i=0,total=data.length;i
参考资料:
因此,问题是在使用模型数据转换器时,我使用的是实体类型而不是文本类型
class SizeTransformer implements DataTransformerInterface
{
protected $sizes;
public function __construct(array $sizes)
{
$this->sizes = $sizes;
}
public function transform($size)
{
if (null === $size) {
return '';
}
return $size->getCode();
}
public function reverseTransform($code)
{
foreach ($this->sizes as $size) {
if ($size->getCode() == $code) {
return $size;
}
}
throw new TransformationFailedException('No such size exists');
}
}
这是我的工作代码,虽然可能不是完美的,主要形式
class BasketAddType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('product', 'entity', [
'class' => 'CatalogueBundle:Product'
]);
$builder->get('product')->addEventListener(FormEvents::POST_SUBMIT, [$this, 'onPostSubmit']);
}
public function onPostSubmit(FormEvent $event)
{
$form = $event->getForm()->getParent();
$product = $event->getForm()->getData();
$form
->add('size', 'size', [
'sizes' => $product->getSizes()->toArray() // getSizes() is an ArrayCollection
);
}
public function getName()
{
return '';
}
}
“我的自定义表单大小”类型,该类型使用提供的大小选项应用模型转换器
class SizeType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addModelTransformer(new SizeTransformer($options['sizes']));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired([
'sizes'
]);
}
public function getParent()
{
return 'text';
}
public function getName()
{
return 'size';
}
}
最后是尺寸变换器
class SizeTransformer implements DataTransformerInterface
{
protected $sizes;
public function __construct(array $sizes)
{
$this->sizes = $sizes;
}
public function transform($size)
{
if (null === $size) {
return '';
}
return $size->getCode();
}
public function reverseTransform($code)
{
foreach ($this->sizes as $size) {
if ($size->getCode() == $code) {
return $size;
}
}
throw new TransformationFailedException('No such size exists');
}
}
如果每个产品都有大量的可用尺寸,那么这个解决方案就不会很好地工作。猜猜如果是这种情况,我需要将EntityManager和产品都传递到transformer中,并相应地查询DB。这与我目前所做的非常相似,但您仍然依赖于通过表单提交的实体主键。如果我移除数据转换器并提交带有大小主键的请求,例如。。。{“product”:3,“size”:21},它可以正常工作。