Validating Symfony3 form with Command not Entity - php

I have prepared a simple application with symfony3.2 and use of DDD (at least I tried). But I have problems validating ProductType form, which uses NewProductCommand instad of Product object.
My form is:
<?php
namespace AdminBundle\Form;
use Shop\Domain\Command\NewProductCommand;
use AppBundle\Form\PriceType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\DataMapperInterface;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
/**
* Product add form
*
* #package AdminBundle\Form
*/
class ProductType extends AbstractType
{
/**
* Building form
*
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add(
'name',
TextType::class,
array(
'required' => true,
'label' => 'Nazwa',
// 'constraints' => [new NotBlank(), new Length(['max' => 255])],
)
)
->add(
'description',
TextareaType::class,
array(
'required' => true,
'label' => 'Opis',
'attr' => [
'rows' => 10
],
// 'constraints' => [new Length(['min' => 100, 'max' => 65000])],
)
)
->add(
'price',
PriceType::class,
array(
'required' => true,
'label' => 'Cena'
)
);
}
/**
* Options configuration
*
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'data_class' => NewProductCommand::class
)
);
}
/**
* Return form name
*
* #return string
*/
public function getName()
{
return 'product';
}
}
And in controller I have:
$command = new NewProductCommand();
$form = $this->createForm(ProductType::class, $command);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
The validation works when I remove commented constraints, but what I want to achieve is that the form uses my Product class validators.
I think that the problem is that, I pass here NewProductCommand::class, and not the Product::class, so the form doesn't know anything about Product class (which is defined in doctrine orm product mapping: https://github.com/wojto/washop/blob/master/src/Shop/Infrastructure/Repository/Doctrine/config/mapping/Product.orm.yml) and validation, which is in yml too (https://github.com/wojto/washop/blob/master/src/AdminBundle/Resources/config/validation.yml).
Where is the place, I should connect NewProductCommand with Product class so the form can use validation for Product class?
I hope, I make it clear ;) full app code is here: https://github.com/wojto/washop/
Maybe I am doing everything wrong, so I appreciate any help :)

Related

Symfony - Filter a form with a CollectionType of Entity

I have two entities, client and order.
I have an admin interface where I show all the orders of a client, where I can modify or delete every order.
To do that I use a Collection Type:
My controller:
$form = $this->createForm(ClientConfigType::class, $client);
This is my ClientConfigType :
<?php
namespace App\Form;
use App\Entity\Client;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ClientConfigType extends AbstractMainType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add("orders",
CollectionType::class,
[
'entry_type' => OrderConfigType::class,
'allow_add' => true,
'label' => false
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => Client::class,
'allow_extra_fields' => true,
));
}
}
And my OrderConfigType is a classic formType.
Everything is working perfectly without any filtering.
But I want to be able to filter and display my collectionType of Order.
For Exemple I would like to display the order of a specific date or the orders > 100$, etc
I tried to use query builder but it's only working for EntityType and not CollectionType
I tried to pass a variable from my Controller to my Form then to my Entity "get" function like that:
$minimumPrice = $request->query->get('minimumPrice');
$form = $this->createForm(ClientConfigType::class, $client, ['minimumPrice' => $minimumPrice ]);
Then in my ConfigType I can retrieve my variable in the configureOptions function but Then, I can't do anything to use that value to filter my collection Type.
How can I filter my collectionType ?
Instead of passing minutePrice you can query like you want your orders, and pass order's collection to the form.
Example here:
class ClientConfigType extends AbstractMainType {
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add("orders",
CollectionType::class,
[
'entry_type' => OrderConfigType::class,
'allow_add' => true,
'label' => false,
'data' => $options['orderCollection']
]);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => Client::class,
'orderCollection' => null,
));
}
}
$orderCollection = $em->getRepository(Order::class)->findAll(); //something like this or custom query it s an example
$form = $this->createForm(ClientConfigType::class, $client, ['orderCollection' => $orderCollection ]);

Symfony forms | Dynamic collection type values

I'm building a small tool on symfony 3.4, I'm experiencing two issue with a form that I cannot find a solution for.
For the context, the form that is causing me some difficulties is based on a doctrine entity : Event.
This event reference another entity : a Doctrine (nothing do to with the ORM). A doctrine references multiples Fittings.
For a given Event with a given Doctrine, I want to display a collectiontype built from the doctrine fittings that expose a number meant to be the required number of this fitting for this event.
This lead to 3 entities in my form : the event itself, the doctrine, and a collectiontype of fittingRequirements built on my end.
The right panel content is meant to change each time the doctrine change.
Here is the EventType :
<?php
namespace AppBundle\Form;
use AppBundle\Entity\Doctrine;
use AppBundle\Entity\Event;
use AppBundle\Form\DataTransformer\FittingRequirementTransformer;
use Doctrine\ORM\EntityRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EventType extends AbstractType
{
protected $requirementTransformer;
public function __construct(FittingRequirementTransformer $transformer)
{
$this->requirementTransformer = $transformer;
}
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('POST')
->add('name')
->add(
'date',
DateTimeType::class,
[
'widget' => 'single_text',
'format' => 'yyyy-MM-dd HH:mm',
]
)
->add('startLocation')
->add(
'eventType',
ChoiceType::class,
[
'choices' => [
'PvE' => 'PvE',
'PvP' => 'PvP',
'Other' => 'Other',
],
]
)
->add('target')
->add('description')
->add(
'doctrine',
EntityType::class,
[
'class' => 'AppBundle\Entity\Doctrine',
'choice_label' => 'name',
'query_builder' => function (EntityRepository $repository) {
return $repository->createQueryBuilder('d')->orderBy('d.name', 'ASC');
},
'required' => false,
]
);
$formModifier = function (FormInterface $form, Doctrine $doctrine = null, Event $event) {
$eventRequirements = [];
if ($doctrine) {
$doctrineFittings = $doctrine->getFittings();
$doctrineRequirements = $event->getDoctrineFittingRequirements($doctrine);
$eventRequirements = $this->requirementTransformer->dataToForm(
$doctrineFittings,
$doctrineRequirements,
$event
);
}
$form->add(
'eventRequirements',
CollectionType::class,
[
'entry_type' => FittingRequirementType::class,
'label' => false,
'entry_options' => ['label' => false],
'data' => $eventRequirements,
'mapped' => false,
]
);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
$formupEvent = $event->getData();
$formModifier($event->getForm(), $formupEvent->getDoctrine(), $formupEvent);
}
);
$builder->get('doctrine')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$eventForm = $event->getForm()->getParent();
$doctrine = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $doctrine, $eventForm->getData());
}
);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => 'AppBundle\Entity\Event',
]
);
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'event';
}
}
I'm building the list of eventFittingRequirements and adding it on PRE_SET_DATA and POST_SUBMIT
As you can see, I use a CollectionType of FittingRequirementType you can see bellow :
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FittingRequirementType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('number', NumberType::class);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\FittingRequirement'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'appbundle_fittingrequirement';
}
}
This is only used to expose the number of required fittings.
All this works well when I display the form, however when I submit the form using javascript to refresh the requirement part, the field are indeed replaced, but the returned form has no value in the inputs.
The $eventRequirements variable from my $formModifier contains a proper set of data, with the number value. However, when I check the XHR with the symfony profiler, the form has no values, even if I select the original doctrine again. I don't understand what is going on and how to fix this.
Thanks for reading
I just found out what was going on and fixed my issue.
My forms are fine, however the handleRequest method clears the unmapped fields I set with my custom fittingRequirement list.
I had to manually submit my form with the clearmissing parameter to false like bellow :
$form->submit($request->request->get($form->getName()), false);

Custom select in a form type in Symfony3

I am creating a select that takes the data of an entity, called category.
The select that I want to develop, would basically be the same that I have developed and working, but with the values ​​I take from the category entity.
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use BackendBundle\Entity\Categoria;
use BackendBundle\Entity\CategoriaRepository;
class ProductoType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nombre', TextType::class, array(
'label' => 'Nombre',
'required' => 'required',
'attr' => array(
'class' => 'form-name form-control'
)
))
->add('categoria', ChoiceType::class, array(
'choices' => array(
'General' => '1',
'Coffe' => '2'
),
'required' => false,
'empty_data' => null
))
->add('Save', SubmitType::class, array(
"attr" =>array(
"class" => "form-submit btn btn-success"
)
))
;
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'BackendBundle\Entity\Producto'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'backendbundle_producto';
}
}
I would add a section like the following, but I get the error Could not load type "entity"
->add('categoria', 'entity', array(
'class' => 'BackendBundle:Categoria'
)
)
The original BBDD is documented in Object of class \BackendBundle\Entity\Categoria could not be converted to string
Firstly if your are using symfony 3 you must use Symfony\Bridge\Doctrine\Form\Type\EntityType and the class should be the class name not the entity name
->add('categoria', 'Symfony\Bridge\Doctrine\Form\Type\EntityType', array(
'class' => 'BackendBundle\Entity\Categoria'
)
)
and categoria should look like:
namespace BackendBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table()
* #ORM\Entity()
*/
class Categoria
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(name="id", type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string")
*/
protected $name;
public function __toString()
{
return $this->name;
}
}
'entity' should be EntityType::class,
you should use the classname of the EntityType instead of just the 'entity' string
See:
https://github.com/symfony/symfony/blob/master/UPGRADE-3.0.md#form

Symfony Embedded Form Validation

I have several Entities, that are used in a single form, that extends from an abstract class.
I have created a form type for each entity and then they were embedded in a parent form.
I would like to perform validation based on groups, so the EmailType must check the "elemento" property only on Assert\NotBlank (Default group) and Assert\Email (email group), and Telefono must check Assert\NotBlank (Default group) and Assert\Regex (phone group).
With my configuration both checks (constraints) are performed, so email are checked on Email constraint AND Regex, and so the phone filed is... Where am i wrong?
Staff Entity on Collection emails and phones have configured the Assert\Valid() contraint
This is the example
Parent Form
<?php
namespace App\Form\Staff;
class StaffType extends AbstractType {
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(['data_class' => \Cowbell\Entity\Staff\Staff::class,
'validation_groups' => ['Default', 'email', 'phone']]);
}
/**
*
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
// ... Other field of Staff Entity
->add('emails', CollectionType::class, ['label' => 'admin.emails',
'entry_type' => \App\Form\Contatto\CBEmailType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'empty_data' => null,
'translation_domain' => 'admin',
'validation_groups' => ['email']])
->add('telefoni', CollectionType::class, ['label' => 'admin.phones',
'entry_type' => \App\Form\Contatto\CBTelefonoType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'empty_data' => null,
'translation_domain' => 'admin',
'validation_groups' => ['phone']]);
}
}
Then CBEmailType
<?php
namespace App\Form\Contatto;
class CBEmailType extends AbstractType{
/**
*
* #param OptionsResolver $resolver
*/
public function configureOptions( OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => \App\Entity\Contatto\Email::class,
'validation_groups' => ['Default', 'email']]);;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('elemento', EmailType::class, ['label' => 'admin.email',
'translation_domain' => 'admin'])
}
}
CBTelefonoType
<?php
namespace App\Form\Contatto;
class CBTelefonoType extends AbstractType{
/**
*
* #param OptionsResolver $resolver
*/
public function configureOptions( OptionsResolver $resolver)
{
$resolver->setDefaults(['data_class' => \Cowbell\Entity\Contatto\Telefono::class,
'validation_groups' => ['Default', 'phone']]);
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('elemento', TextType::class, ['label' => 'admin.phone',
'translation_domain' => 'admin'])
}
}
Both, Email and Telefono extend
<?php
namespace App\Entity\Contact;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
abstract class AbstractElementoContact {
/**
*
* #var string
*
* #ORM\Column(name="elemento", type="string", length=100, nullable=false)
* #Assert\NotBlank()
* #Assert\Email(strict=true, checkHost=true, checkMX=true, groups={"email"})
* #Assert\Regex("/[0-9]{6,50}/", groups={"phone"})
*/
protected $elemento;
AFAIK you can't set validation_groups on CollectionType form field (resp. you can set it, but it has no effect), so the whole form, including the set of sub-forms in the collection is allways validated with validation_groups set on the whole parent form.
The purpose of validation_groups is to allow constraints modification of object's properties for different purposes (e.g. creating new vs. editing existing), but not for what you described above.
Think about, if it would be possible, to use your current Email and Telephono as a property directly in the Staff object (or StaffType respectivelly) with using validation_groups to solve the $elemento should be Email elemento once and Telephono elemento once...
The solution for your case is to define Email and Telephono as different classes (not inherited from AbstractElementoContact) with specific contraint for each of them.

Symfony 2 : preselect multiple values in a form by loading manualy attribute data from an ArrayCollection

I need to get preselected some values of an entity attribute that I get in the PRE_SET_DATA event, not from data base.
I have a Form working, all datas from my Entity AccessGroup is loaded but my problem is to get selected the ArrayCollection attribute named accessGroups from entity User which is not stored in database.
To make it clear, attribute accessGroups is loaded by User's roles.
Here is the FormType Class
namespace Pkg\ExtranetBundle\Form;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Doctrine\ORM\EntityManager;
class RoleType extends AbstractType
{
/**
* #var EntityManager
*/
protected $em;
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$this->em = $options['em'];
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
}
/**
* Listener before normalizing data form
*
* #param FormEvent $event
*/
public function onPreSetData(FormEvent $event)
{
$user = $event->getData();
$accessGroups = $this->em->getRepository('PkgExtranetBundle:AccessGroup')->getSelected($user->getRoles());
$user->setAccessGroups(new ArrayCollection($accessGroups));
$event->setData($user);
$form = $event->getForm();
$form->add('accessGroups', EntityType::class, array(
'class' => 'PkgExtranetBundle:AccessGroup',
'choice_label' => 'name',
'choice_value' => 'role',
'multiple' => true,
'expanded' => false
))
->add('save', SubmitType::class, array('label' => 'registration.submit', 'translation_domain' => 'FOSUserBundle'));
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Pkg\ExtranetBundle\Entity\User',
'em' => null
));
}
}
Okey, the class I made has its own problem with the choice_value parameter :
$form->add('accessGroups', EntityType::class, array(
'class' => 'PkgExtranetBundle:AccessGroup',
'choice_label' => 'name',
'choice_value' => 'role', // If choice_value is not the entity index, then preselection will not be applied as the index could not be retrieved.
'multiple' => true,
'expanded' => false
))

Categories