Silex 表单中的验证

Validation in Silex forms

关于嵌入式表单,我遇到了一个我自己无法解决的问题。

我在 Composer 中使用 Silex 1.3,composer.json 文件复制在下面。我没有使用 Doctrine,我创建了自己的 DAO,因此我没有使用注释。

我认为我的问题来自于我的验证,或者我的数据映射。

上下文:我正在尝试使用以下对象:

我的目标是使用我所说的 SelectType,这基本上是一种允许我逐步 select 一些对象的形式,而不必 select 直接进入巨大的清单。 表单和对象的逻辑是一样的,我有:

当我尝试通过 ajax 或手动提交表单 (StateType) 时,$form->isSumbitted()&&$form->isValid() returns true,以及 Region 填充,但没有 Country 填充(这很明显,因为我没有 select 它)。

我的表格有问题吗?

我注意到当我不使用我的 SelectType 时一切正常,但是当我为每个表单手动填充表单选项时(这导致很多代码重复出现)。然后正确验证了表单。

感谢您的宝贵时间和帮助!

Composer.json:

{
    "require": {
        "silex/silex": "~1.3",
        "doctrine/dbal": "2.5.*",
        "symfony/security": "2.7.*",
        "twig/twig": "1.21.*",
        "symfony/twig-bridge": "2.7.*",
        "symfony/form": "2.7.*",
        "symfony/translation": "2.7.*",
        "symfony/config": "2.7.*",
        "jasongrimes/silex-simpleuser": "*",
        "twig/extensions": "1.3.*",
        "symfony/validator": "2.*",
        "phpoffice/phpexcel": "1.*",
        "symfony/monolog-bridge": "*"
    },
    "require-dev": {
        "phpunit/phpunit": "*",
        "symfony/browser-kit": "*",
        "symfony/css-selector": "*",
        "silex/web-profiler": "*"
    },
    "autoload":{
        "psr-4":{"Easytrip2\": "src"}
    },
    "autoload-dev":{
        "psr-4":{"Easytrip2\": "tests"}
    }
}

做表单管理的StateController:

public function stateAddAction(Request $request, Application $app) {
    $formView = null;
    if ($app ['security.authorization_checker']->isGranted ( 'ROLE_ADMIN' ) and $app ['security.authorization_checker']->isGranted ( 'ROLE_ADMIN' )) {
        // A user is fully authenticated : he can add comments
        $new = new State ();
        $form = $app ['form.factory']->create ( new StateType ( $app ), $new );
        $form->handleRequest ( $request );
        //this returns true, event if the country is not filled.
        if ($form->isSubmitted () && $form->isValid ()) {
            if ($app ['dao.state']->save ( $new )) {
                $app ['session']->getFlashBag ()->add ( 'success', 'Succesfully added.' );
                return $app->redirect ( $app ['url_generator']->generate ( 'state' ) );
            } else {
                $app ['session']->getFlashBag ()->add ( 'error', 'Error in SQL ! Not added...' );
            }
        }
        $formView = $form->createView ();

        return $app ['twig']->render ( 'form.html.twig', array (
                'title' => 'Add state',
                'scripts_ids' => StateType::getRefNames (),
                'form' => $formView
        ) );
    } else {
        $app ['session']->getFlashBag ()->add ( 'error', 'Don\'t have the rights...' );
        return $app->redirect ( $app ['url_generator']->generate ( 'home' ) );
    }
}

AbstractEasytrip2Type,这基本上是应用程序的注入,以便能够使用 DAO:

<?php

namespace Easytrip2\Form;

use Silex\Application;
use Symfony\Component\Form\AbstractType;

abstract class AbstractEasytrip2Type extends AbstractType {
    /**
     *
     * @var Application
     */
    protected $app;
    public function __construct(Application $app/*, $data*/) {
        $this->app = $app;
    }
    public static function getRefNames() {
        return null;
    }
}

RegionSelectType:

<?php

namespace Easytrip2\Form\Select;

use Easytrip2\Form\AbstractEasytrip2Type;
use Easytrip2\Form\Select\DataMapper\RegionSelectDataMapper;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class RegionSelectType extends AbstractEasytrip2Type {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $obj = $this->app ['dao.region']->findAll ();
        $builder->add ( 'choice', 'choice', array (
                'choices' => $obj,
                'choices_as_values' => true,
                'choice_label' => function ($value) {
                    // if nothing exists, then an empty label is generated.
                    return is_null ( $value ) ? "" : $value->getName ();
                },
                'choice_value' => function ($value) {
                    // here i only have int unsigned in database, so -1 is safe. This is probably used for comparison for selecting the stored object between the list and the stored object.
                    return is_null ( $value ) ? - 1 : $value->getId ();
                },
                'placeholder' => 'Select a region',
                'label' => 'Region'
        ) );
        $builder->setDataMapper ( new RegionSelectDataMapper () );
    }
    /**
     *
     * {@inheritDoc}
     *
     * @see \Symfony\Component\Form\AbstractType::setDefaultOptions()
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults ( array (
                'data_class' => 'Easytrip2\Domain\Region',
                'cascade_validation' => true
        ) );
    }
    public function getName() {
        return 'region';
    }
    public static function getRefNames() {
        return array ();
    }
}

RegionSelectDataMapper:

<?php

namespace Easytrip2\Form\Select\DataMapper;

use Symfony\Component\Form\DataMapperInterface;

class RegionSelectDataMapper implements DataMapperInterface {
    public function mapDataToForms($data, $forms) {
        $forms = iterator_to_array ( $forms );
        $forms ['choice']->setData ( $data );
    }
    public function mapFormsToData($forms, &$data) {
        $forms = iterator_to_array ( $forms );
        $data = $forms ['choice']->getData ();
    }
}

国家选择类型:

<?php

namespace Easytrip2\Form\Select;

use Easytrip2\Domain\Region;
use Easytrip2\Form\AbstractEasytrip2Type;
use Easytrip2\Form\Select\DataMapper\CountrySelectDataMapper;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class CountrySelectType extends AbstractEasytrip2Type {
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->add ( 'region', new RegionSelectType ( $this->app ), array (
                'label' => false,
                'cascade_validation' => true
        ) );
        $builder->addEventListener ( FormEvents::PRE_SET_DATA, function (FormEvent $event) {
            $this->modifyFormFromRegion ( $event->getForm (), $event->getData () ? $event->getData ()->getRegion () : null );
        } );
        $builder->get ( 'region' )->addEventListener ( FormEvents::POST_SUBMIT, function (FormEvent $event) {
            $this->modifyFormFromRegion ( $event->getForm ()->getParent (), $event->getForm ()->getData () );
        } );
        $builder->setDataMapper ( new CountrySelectDataMapper () );
    }
    public function modifyFormFromRegion(FormInterface $builder, Region $data = null) {
        $obj = array ();
        if (! is_null ( $data )) {
            $obj = $this->app ['dao.country']->findByRegionId ( $data->getId () );
        } else {
            // change this if you do not want the country to be filled with all countries.
            // $obj = $this->app ['dao.country']->findAll ();
            $obj = array ();
        }
        $builder->add ( 'choice', 'choice', array (
                'choices' => $obj,
                'choices_as_values' => true,
                'choice_label' => function ($value) {
                    // if nothing exists, then an empty label is generated.
                    return is_null ( $value ) ? "" : $value->getName ();
                },
                'choice_value' => function ($value) {
                    // here i only have int unsigned in database, so -1 is safe. This is probably used for comparison for selecting the stored object between the list and the stored object.
                    return is_null ( $value ) ? - 1 : $value->getId ();
                },
                'placeholder' => 'Select a country',
                'label' => 'Country',
                'required' => true,
                'data_class' => 'Easytrip2\Domain\Country',
                'cascade_validation' => true
        ) );
    }
    /**
     *
     * {@inheritDoc}
     *
     * @see \Symfony\Component\Form\AbstractType::setDefaultOptions()
     */
    public function setDefaultOptions(OptionsResolverInterface $resolver) {
        $resolver->setDefaults ( array (
                'data_class' => 'Easytrip2\Domain\Country',
                'cascade_validation' => true
        ) );
    }
    function getName() {
        return 'country';
    }
    public static function getRefNames() {
        $ret = array (
                'in' => 'country_region_choice',
                'out' => 'country_choice'
        );
        return array (
                $ret
        );
    }
}

CountrySelectDataMapper:

<?php

namespace Easytrip2\Form\Select\DataMapper;

use Symfony\Component\Form\DataMapperInterface;

class CountrySelectDataMapper implements DataMapperInterface {
    public function mapDataToForms($data, $forms) {
        $forms = iterator_to_array ( $forms );
        $forms ['choice']->setData ( $data );
        if (isset ( $forms ['region'] )) {
            if ($data) {
                $forms ['region']->setData ( $data->getRegion () );
            }
        }
    }
    public function mapFormsToData($forms, &$data) {
        $forms = iterator_to_array ( $forms );
        $data = $forms ['choice']->getData ();
    //  $data->getRegion() === $forms['']
    }
}

州类型:

<?php

namespace Easytrip2\Form\Type;

use Easytrip2\Form\AbstractEasytrip2Type;
use Easytrip2\Form\Select\CountrySelectType;
use Easytrip2\Form\Select\GeopointSelectType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class StateType extends AbstractEasytrip2Type {

    /**
     *
     * {@inheritDoc}
     *
     * @see \Symfony\Component\Form\AbstractType::buildForm()
     */
    public function buildForm(FormBuilderInterface $builder, array $options) {
        $builder->add ( 'name', 'text', array (
                'label' => 'State name'
        ) );
        $builder->add ( 'code', 'text', array (
                'label' => 'State code'
        ) );
        $builder->add ( 'unloc', 'text', array (
                'label' => 'State unloc code'
        ) );
        // TODO : the validation on this form appears to not be done, thus i try to save (as it is considered as valid) a object which is null, thus fail in the setters.
        $builder->add ( 'country', new CountrySelectType ( $this->app ), array (
                'label' => false,
                'cascade_validation' => true
        ) );
    /**
     * $builder->add ( 'hub', new GeopointSelectType ( $this->app, 'HUB' ), array (
     * 'label' => 'Select a hub if necessary'
     * ) );
     */
    }
    public static function getRefNames() {
        $return = array ();
        $countries = CountrySelectType::getRefNames ();
        // $hubs = GeopointSelectType::getRefNames ();
        $last;
        foreach ( $countries as $value ) {
            $return [] = array (
                    'in' => 'state_' . $value ['in'],
                    'out' => 'state_' . $value ['out']
            );
        }
        /*
         * foreach ( $hubs as $value ) {
         * $return [] = array (
         * 'in' => 'state_' . $value ['in'],
         * 'out' => 'state_' . $value ['out']
         * );
         * }
         */

        return $return;
    }

    /**
     *
     * {@inheritDoc}
     *
     * @see \Symfony\Component\Form\AbstractType::configureOptions()
     */
    public function configureOptions(OptionsResolver $resolver) {
        $resolver->setDefaults ( array (
                'data_class' => 'Easytrip2\Domain\State'
        ) );
    }

    /**
     *
     * {@inheritDoc}
     *
     * @see \Symfony\Component\Form\FormTypeInterface::getName()
     */
    public function getName() {
        return 'state';
    }
}

我设法在我的 类 中使用 loadValidatorMetadata 并使用了一些解决方法。 这样,Silex 就会进行验证,甚至将其发送给浏览器以基本验证数据。

我还简化了很多映射器。

如果您对这个项目有任何疑问,请随时问我。

编辑:因为,我有:

  • 更改了 select 表格的 DataMappers
  • 使用 setDefaultOptions
  • 添加了 cascade_validation 并仔细填写了 data_class 选项到我的表格中
  • 使用了 loadValidatorMetadata,我不确定它做了什么(也许它允许检查特定选择是否有效,由 cascade_validation 触发?

自从我修复它已经有很多时间了,所以我可能会忘记这里的一些东西。

但基本上,我的理解是我的验证没有正确进行,并且验证实体中的数据、将正确的数据映射到实体以及级联验证以确保属性也得到验证是必要的。