簡體   English   中英

Silex表格中的驗證

[英]Validation in Silex forms

我遇到一個我無法自行解決的有關嵌入式表單的問題。

我將Silex 1.3與composer一起使用, composer.json文件復制到下面。 我沒有使用Doctrine,而是制作了自己的DAO,因此沒有使用注釋。

我認為我的問題來自驗證或此處的數據映射。

上下文:我正在嘗試使用以下對象:

  • Region (如歐洲,北美等),
  • 屬於一個Region Country (法國,加拿大等)(因此將其作為屬性)
  • State (法蘭西島,法蘭西島),屬於一個Country (因此將其作為屬性)

我的目標是使用所謂的SelectType ,它基本上是一種允許我逐步選擇某些對象的形式,而不必直接選擇一個巨大的列表。 表單與對象具有相同的邏輯,我有:

  • RegionType ,它允許我編輯或添加Region
  • RegionSelectType ,它允許我選擇現有的Region
  • CountryType ,它使用RegionSelectType
  • CountrySelectType ,它采用RegionSelectType這讓我選擇一個Region ,那么Country在選定的Region
  • StateType ,它采用了CountrySelectType
  • 在不久的將來,我將基於相同的原理創建一個StateSelectType

當我嘗試通過Ajax或手動提交表單( StateType )時, $form->isSumbitted()&&$form->isValid()返回true ,填充了Region ,但沒有填充Country (這很明顯)因為我沒有選擇它)。

我的表格做錯了嗎?

我注意到,當我不使用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 ();
    }
}

CountrySelectType:

<?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['']
    }
}

StateType:

<?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觸發?

自修復以來已經有很多時間了,所以我可能在這里忘記了一些東西。

但基本上,我的理解是我的驗證未正確進行,驗證實體中的數據,將正確的數據映射到實體以及級聯驗證以確保屬性也得到驗證是必要的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM