-
-
Save n3b/1068357 to your computer and use it in GitHub Desktop.
<?php | |
namespace n3b\Bundle\Shop\Form; | |
use Symfony\Component\Form\AbstractType; | |
use Symfony\Component\Form\FormBuilder; | |
use Symfony\Component\Form\FormEvents; | |
class CheckoutFullType extends AbstractType | |
{ | |
public function buildForm(FormBuilder $builder, array $options) | |
{ | |
$builder->add('deliver', 'checkbox') | |
->add('user_save', 'checkbox') | |
->add('checkout', new CheckoutType()); | |
$builder->addEventSubscriber(new EventSubscriber\PreBindDataSubscriber()); | |
$builder->addValidator(new Validator\CheckoutDeliveryValidator()); | |
} | |
public function getDefaultOptions(array $options) | |
{ | |
return array( | |
'validation_groups' => array('pass_through'), | |
'data_class' => 'n3b\Bundle\Shop\Entity\Customer', | |
); | |
} | |
} |
<?php | |
namespace n3b\Bundle\Shop\Entity; | |
use Doctrine\ORM\Mapping as ORM; | |
use Symfony\Component\Validator\Constraints as Assert; | |
use Doctrine\Common\Collections\ArrayCollection; | |
use n3b\Bundle\Shop\Model\Customer as BaseCustomer; | |
/** | |
* @ORM\Entity | |
*/ | |
class Customer extends BaseCustomer | |
{ | |
/** | |
* @ORM\Id | |
* @ORM\Column(type="integer") | |
* @ORM\GeneratedValue(strategy="AUTO") | |
*/ | |
protected $id; | |
/** | |
* @ORM\Column(unique="true", nullable="true") | |
* @Assert\NotBlank(groups={"registration"}) | |
* @Assert\Blank(groups={"pass_through"}) | |
*/ | |
protected $login; | |
} |
<?php | |
namespace Symfony\Component\Form; | |
use Symfony\Component\Form\Event\DataEvent; | |
use Symfony\Component\Form\Event\FilterDataEvent; | |
use Symfony\Component\Form\Exception\FormException; | |
use Symfony\Component\Form\Exception\UnexpectedTypeException; | |
use Symfony\Component\Form\Exception\TransformationFailedException; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\EventDispatcher\EventDispatcherInterface; | |
class Form implements \IteratorAggregate, FormInterface | |
{ | |
... | |
//этого метода нет в дефолтной комплектации | |
public function setAttribute($name, $value) | |
{ | |
$this->attributes[$name] = $value; | |
} | |
} |
<?php | |
namespace n3b\Bundle\Shop\Form\EventSubscriber; | |
use Symfony\Component\Form\FormEvents; | |
use Symfony\Component\Form\Event\DataEvent; | |
use Symfony\Component\EventDispatcher\EventSubscriberInterface; | |
class PreBindDataSubscriber implements EventSubscriberInterface | |
{ | |
public function onPreBindData(DataEvent $event) | |
{ | |
$data = $event->getData(); | |
if(isset($data['user_save'])) | |
$event->getForm()->get('checkout')->get('customer')->setAttribute('validation_groups', array('registration')); | |
} | |
static public function getSubscribedEvents() | |
{ | |
return array(FormEvents::PRE_BIND => 'onPreBindData'); | |
} | |
} |
Странно... Если до события FormEvents::PRE_BIND
для формы вызывался метод setData()
, то по идее не должно возвращать null. А если попробовать получить данные для корневой формы $event->getForm()->getData()
, тоже будет null или нет?
Да, компонент форм еще сыроват, но уже на многое способен и имеет достаточно гибкую архитектуру :) Мне кажется, значительного рефакторинга до 2.1 не будет, сейчас комманда сосредоточена на стабилизации ядра.
По поводу, валидаторов и атрибута required
. Между валидаторами и атрибутом нет связи. Не знаю, правильно ли понял Вас, но если речь про зависимость состояния атрибута от наличия валидаторов для данных элемента формы, то такого не должно быть. Валидаторы могут быть сопоставлены с моделью (объектом), т.е. данными формы (как в Вашем случае в аннотациях модели), так и относиться к элементу формы (можно добавить через опцию validation_constraint
). В первом случае, форма ничего не знает про валидаторы, не считая названия группы валидаторов.
Странно, сейчас проверил - $event->getForm()->get('checkout')->getData()
действительно возвращает пустой объект модели (да, именно этот объект привязывается до байнда), однако родительская форма $event->getForm()->getData()
отдает все же null
. Аналогично с дочерней формой $event->getForm()->get('checkout')->get('customer')
тоже возвращает null
. Видимо, связывание билдером модели с формой происходит позже.
Насчет required
связь есть. В FormFactory:
public function createBuilderForProperty($class, $property, $data = null, array $options = array())
{
if (!$this->guesser) {
$this->loadGuesser();
}
$typeGuess = $this->guesser->guessType($class, $property);
$maxLengthGuess = $this->guesser->guessMaxLength($class, $property);
$minLengthGuess = $this->guesser->guessMinLength($class, $property);
$requiredGuess = $this->guesser->guessRequired($class, $property);
...
Эти guesser'ы и задают атрибуты формам, которые в дальнейшем выводятся при генерации вида формы. А сами guesser'ы проверяют в аннотациях только соответствие классам. Таким образом, @Assert\NotBlank(groups={"registration"})
будет проводить валидацию только для групп registration
, а вот атрибут формы будет, вне зависимости от группы, required
. Что с учетом html5 вообще не даст отправить форму.
З.Ы. посмотрите класс Symfony\Component\Form\Extension\Validator\ValidatorTypeGuesser
То, что родительская форма возвращает null ($event->getForm()->getData()
) вроде как правильно, судя по этому коду:
$form = $this->services['ff']->create(new CheckoutFullType());
$form->get('checkout')->setData($checkout);
Т.к. setData()
вызывается непосредственно для элемента checkout.
А в объекте $checkout
для формы $event->getForm()->get('checkout')->get('customer')
есть данные или null?
Спасибо за информацию о guesser'ах и FormFactory, не знал про эту фичу. В таком случае Вы правы, это баг - что не учитывается группа валидации при определении валидаторов.
Я не стал там ковыряться, решил инициализировать модель после байнда:
$form = $this->services['ff']->create(new CheckoutFullType());
if ($this->services['request']->getMethod() == 'POST') {
$form->bindRequest($this->services['request']);
$checkout = $form->get('checkout')->getData();
при таком раскладе в onPreBindData
все поголовно в нулях.
По guesser'ам ждем фиксов. Пока накидал цепочку инжектов опций, начиная от билдера и заканчивая непосредственно guesser'ами.
Поковырялся -
$event->getForm()->get('checkout')->getData()
возвращает null. Оно и верно, форма еще байнд не начинала. Ну а раз не начинала - достаточно просто создать новый объект класса: