I am having an issue with Validating an EWZ recaptcha field in a form that I have in Symfony2.
Here is my Captcha Entity:
<?php
namespace Acme\FormBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints as Recaptcha;
/**
* Captcha
*
* #ORM\Table()
* #ORM\Entity
*/
class Captcha
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var boolean
*
* #ORM\Column(name="captcha", type="boolean")
*/
private $captcha;
/**
* #var integer
*
* #ORM\Column(name="uniqueid", type="integer")
*/
private $uniqueid;
/**
* #Recaptcha\True
*/
public $recaptcha;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set captcha
*
* #param boolean $captcha
* #return Captcha
*/
public function setCaptcha($captcha)
{
$this->captcha = $captcha;
return $this;
}
/**
* Get captcha
*
* #return boolean
*/
public function getCaptcha()
{
return $this->captcha;
}
/**
* Set uniqueid
*
* #param integer $uniqueid
* #return Captcha
*/
public function setUniqueid($uniqueid)
{
$this->uniqueid = $uniqueid;
return $this;
}
/**
* Get uniqueid
*
* #return integer
*/
public function getUniqueid()
{
return $this->uniqueid;
}
}
Here is my Form:
<?php
namespace Acme\FormBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\True;
class CaptchaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('recaptcha', 'ewz_recaptcha')
;
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\FormBundle\Entity\Captcha'
));
}
public function getName()
{
return 'captchaType';
}
}
Here is my Controller:
<?php
namespace Acme\FormBundle\Controller;
use Acme\FormBundle\Entity\User;
use Acme\FormBundle\Entity\Workstation;
use Acme\FormBundle\Entity\Captcha;
use Acme\FormBundle\Form\CaptchaType;
use EWZ\Bundle\RecaptchaBundle\Validator\Constraints\True;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Form\FormBuilderInterface;
class DefaultController extends Controller
{
public function indexAction(Request $request)
{
$captcha = new Captcha();
$form = $this->createForm(new CaptchaType(), $captcha);
if ($request->isMethod('POST')) {
echo "1";
$form->bind($request);
echo "2";
if ($form->isValid()) {
echo "3A";
// perform some action, such as saving the task to the database
$session = $this->getRequest()->getSession();
$temptime = strtotime("now");
$session->set('uniqueid', $temptime);
return $this->redirect($this->generateUrl('customer_info'));
}else{
return $this->redirect($this->generateUrl('captcha'));
}
echo "3B";
}
return $this->render('AcmeFormBundle:Default:index.html.twig', array('form' => $form->createView()));
}
When I check to see if the form is valid, it never equals true. I'm sure that I'm missing something pretty easy, but I have been trying to figure this out for the past 4 hours, and I'm stuck.
Any help is greatly appreciated.
Thanks
After a lot Googling, I ended up fixing my problem. I ended up changing my buildform to the following:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('recaptcha', 'ewz_recaptcha', array(
'attr' => array(
'options' => array(
'theme' => 'clean'
)
),
'mapped' => false,
'constraints' => array(
new True()
),
'error_bubbling' => true
));
}
And changed my config.yml to this:
validation: { enable_annotations: false }
That fixed my issue. Kind of wish this was a little more clear in the installation instructions.
Oh well.
Related
I am developing a web app on Symfony. I have 2 entities that is question about, "Line" and "Dosier". So, 1 dosier can have many lines.
Now I'm implementing the CRUD for the Line entity, so Line entity has a "dropdown" with all Dosiers from DataBase.
The problem is that in dropdown are all dosiers from any users, but I need in this dropdown to have just options that has user_id = currentUser_id.
So, my controller action :
/**
* #Route("/add-line", name="addLine")
*/
public function createLineAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$user = $this->getUser();
$line = new Line();
$form = $this->createForm(LineType::class, $line);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em->persist($line);
$em->flush();
return $this->redirectToRoute('homepage');
}
return $this->render('AppBundle:default:formLines.html.twig', array(
'form' => $form->createView(),
));
}//create dossier action
My LineType (form builder)
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class LineType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')->add('dosier')
->add('dosier', EntityType::class, array(
'class' => 'AppBundle:Dosier',
'query_builder' => function($repo) {
return $repo->dosiersOfCurrentUser();
},
'choice_label' => 'name',
))
->add('save', SubmitType::class, array(
'label' => 'Save',
'attr'=> array('class'=>'btn btn-success submitButton')
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Line'
));
}
public function getBlockPrefix()
{
return 'appbundle_line';
}
}
My Line.php (entity)
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Line
*
* #ORM\Table(name="line")
* #ORM\Entity(repositoryClass="AppBundle\Repository\LineRepository")
*/
class Line
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Dosier", inversedBy="lines")
* #ORM\JoinColumn(name="dosier_id", referencedColumnName="id")
*/
private $dosier;
/**
* #ORM\OneToMany(targetEntity="Loan", mappedBy="line")
*/
private $loans;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
public function __construct()
{
$this->loans = new ArrayCollection();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Line
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Add loan
*
* #param \AppBundle\Entity\Loan $loan
*
* #return Line
*/
public function addLoan(\AppBundle\Entity\Loan $loan)
{
$this->loans[] = $loan;
return $this;
}
/**
* Remove loan
*
* #param \AppBundle\Entity\Loan $loan
*/
public function removeLoan(\AppBundle\Entity\Loan $loan)
{
$this->loans->removeElement($loan);
}
/**
* Get loans
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getLoans()
{
return $this->loans;
}
/**
* Set dosier
*
* #param \AppBundle\Entity\Dosier $dosier
*
* #return Line
*/
public function setDosier(\AppBundle\Entity\Dosier $dosier = null)
{
$this->dosier = $dosier;
return $this;
}
/**
* Get dosier
*
* #return \AppBundle\Entity\Dosier
*/
public function getDosier()
{
return $this->dosier;
}
}
and my repository : DosierRepository.php
<?php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class DosierRepository extends \Doctrine\ORM\EntityRepository
{
public function dosiersOfCurrentUser() {
return $this->createQueryBuilder('dosier')
->where('dosier.userId = 1 ')
->orderBy('dosier.name', 'DESC');
}
}
How can I get the current user, or at least the current user id, to make a query like ... select from Dosier where dosier.user_id = $???
In controller where you are buiding your form you need pass the user object to your form type
$tokenStorage = $this->get('security.token_storage');
$form = $this->createForm(new LineType($tokenStorage), $line);
//... other stuff
Now in your form type recieve this tokenStorage object and retrieve user object and pass to your repo function
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
//.. other use statements
class LineType extends AbstractType
{
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$userId = $this->user->getId();
$builder
->add('name')->add('dosier')
->add('dosier', EntityType::class, array(
'class' => 'AppBundle:Dosier',
'query_builder' => function($repo) use($userId) {
return $repo->dosiersOfCurrentUser($userId);
},
'choice_label' => 'name',
))
->add('save', SubmitType::class, array(
'label' => 'Save',
'attr'=> array('class'=>'btn btn-success submitButton')
)
);
}
}
In repo apply your filter
class DosierRepository extends \Doctrine\ORM\EntityRepository
{
public function dosiersOfCurrentUser($userId) {
return $this->createQueryBuilder('dosier')
->where('dosier.userId = :userId ')
->setParameter('userId',$userId)
->orderBy('dosier.name', 'DESC');
}
}
I have a problem with my form in Symfony 3. I have one-to-many-to-one join (Doctrine 2). It handles Orders (Order), Products (Product) and joining entity (OrderProduct), which holds amount of product in order.
I have a form for add and update order entries, which uses Collection of OrderProducts. It's all based on documentation (link).
In the form I have a button for add a product (from documentation, adds a <li> to DOM) and every added has a button for remove it (from documentation, removes <li> from DOM). This part is working - adding to and removing from DOM.
Adding products works (as at the new order, than when editting).
But my problem is with removing. Products which was succesfully deleted from form, are still appearing in $editForm->getData().
OrderProduct Form
namespace AppBundle\Form;
use AppBundle\Entity\ProductType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrderProductType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('amount')
->add('product')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'AppBundle\Entity\OrderProduct',
)
);
}
public function getName()
{
return 'app_bundle_order_product_type';
}
}
Order Form
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class OrderType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('customer')
->add('date', null, array('widget' => 'single_text'))
->add('payment', null, array('widget' => 'single_text'))
->add('processed', null, array('widget' => 'single_text'))
->add(
'orderProducts',
CollectionType::class,
array(
'entry_type' => OrderProductType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'prototype' => true,
'delete_empty' => true,
'entry_options' => array('data_class' => 'AppBundle\Entity\OrderProduct'),
)
);
}
public function configureOptions(OptionsResolver $resolver)
{
}
public function getName()
{
return 'app_bundle_order_type';
}
}
Current action in OrderController
(Adding product works, removing not)
public function editAction (Request $request, $orderId) {
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('AppBundle:Order')->find($orderId);
if (!$order) {
throw $this->createNotFoundException('No order found for id '.$orderId);
}
$editForm = $this->createForm(OrderType::class, $order);
$editForm->add('submit', SubmitType::class);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$order = $editForm->getData();
//print '<pre>';
//var_dump($order->getOrderProducts());
//die();
$orderProducts = $order->getOrderProducts();
$em->persist($order);
foreach ($orderProducts as $oneOrderProduct) {
$oneOrderProduct->setOrder($order);
$em->persist($oneOrderProduct);
}
//print '<pre>';
//var_dump($order->getOrderProducts());
//die();
$em->flush();
return $this->redirectToRoute('one_order', array('orderId' => $order->getId()));
}
return $this->render(
'order/new.html.twig', array(
'form' => $editForm->createView(),
));
}
I know that I must remove removed OrderProducts from Order in editAction, but now I can't, because from the form is sent all OrderProducts.
Order Entity
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use AppBundle\Entity\OrderProduct;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
/**
* #ORM\Entity
* #ORM\Table(name="order_")
*/
class Order
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="orders")
* #ORM\JoinColumn(name="customer_id", referencedColumnName="id")
*/
private $customer;
/**
* #ORM\Column(type="date")
*/
private $date;
/**
* #ORM\Column(type="date")
*/
private $payment;
/**
* #ORM\Column(type="date")
*/
private $processed;
public function __toString()
{
return strval($this->getId());
}
/**
* #ORM\OneToMany(targetEntity="OrderProduct", mappedBy="order")
*/
private $orderProducts;
public function __construct()
{
$this->orderProducts = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set date
*
* #param \DateTime $date
*
* #return Order
*/
public function setDate($date)
{
$this->date = $date;
return $this;
}
/**
* Get date
*
* #return \DateTime
*/
public function getDate()
{
return $this->date;
}
/**
* Set payment
*
* #param \DateTime $payment
*
* #return Order
*/
public function setPayment($payment)
{
$this->payment = $payment;
return $this;
}
/**
* Get payment
*
* #return \DateTime
*/
public function getPayment()
{
return $this->payment;
}
/**
* Set processed
*
* #param \DateTime $processed
*
* #return Order
*/
public function setProcessed($processed)
{
$this->processed = $processed;
return $this;
}
/**
* Get processed
*
* #return \DateTime
*/
public function getProcessed()
{
return $this->processed;
}
/**
* Set customer
*
* #param \AppBundle\Entity\Customer $customer
*
* #return Order
*/
public function setCustomer(\AppBundle\Entity\Customer $customer = null)
{
$this->customer = $customer;
return $this;
}
/**
* Get customer
*
* #return \AppBundle\Entity\Customer
*/
public function getCustomer()
{
return $this->customer;
}
/**
* Add orderProduct
*
* #param \AppBundle\Entity\OrderProduct $orderProduct
*
* #return Order
*/
public function addOrderProduct(\AppBundle\Entity\OrderProduct $orderProduct)
{
$this->orderProducts[] = $orderProduct;
return $this;
}
/**
* Remove orderProduct
*
* #param \AppBundle\Entity\OrderProduct $orderProduct
*/
public function removeOrderProduct(\AppBundle\Entity\OrderProduct $orderProduct)
{
$this->orderProducts->removeElement($orderProduct);
}
/**
* Get orderProducts
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getOrderProducts()
{
return $this->orderProducts;
}
}
But in POST it's OK as you can see here, here I removed 2 of 4 products. Problem appears in form handling.
If by_reference is set to false the underlying Order entity MUST have a method in your case called [removeOrderProduct]. What also might be the problem is that you are not specifying data_class option inside the configureOptions method. In your case if Order entity is in 'AppBundle\Entity\Order', then the configureOptions method should contain:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Order',
));
}
And I would suggest you do the same in OrderProductType class also.
My guess would be that since you haven't specified the 'data_class' option in your OrderType class, the by_reference option in the 'orderProducts' field might not be able to figure out where to look for the [removeOrderProduct] method. So set that option and make sure you have that method inside your Order entity class.
IF this is not the problem then you should provide more information about your order entity and where exactly are you calling the getData method.
Update:
Looking at your code I can't identify the problem, that might be causing the entities to not get removed. But I have spotted a few oddities in your code:
In the controller, when handling form submittions it is not necessary to call the getData method: after you call handleRequest the $order object gets updated and holds the new information (since objects are passed by reference, the form can't apply the changes without changing the original $order as well). So there is no need to $order = $form->getData(), since you have already defined the $order variable before and it holds the reference to the same object to which the form mapped the posted values.
If that does not help, I suggest you add die; statements all over the place, just to make sure that at each step the right methods are called. For instance add die; to the removeOrderProduct method, to check if its hit. If it was hit, the problem is not going to be evident from the data you have provided to us, so it will require further debugging.
Also it might not be a problem now, but if you want to remove the products not present in the list after submittion, you have to call $order->getOrderProducts and add each of the items to a new collection that holds the previous orderProducts (before submittion) and compare that to the values after submittion to figure out which ones need removing.
I solved it last night :)
This is working controller action:
/**
* #Route("/{orderId}/edit", name="edit_order", requirements={"orderId": "\d+"})
* #param Request $request
* #param $orderId
*
* #return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
*/
public function editAction(Request $request, $orderId)
{
$em = $this->getDoctrine()->getManager();
$order = $em->getRepository('AppBundle:Order')->find($orderId);
if (!$order) {
return $this->redirectToRoute('new_order');
}
$originalOrderProducts = new ArrayCollection();
foreach ($order->getOrderProducts() as $orderProduct) {
$originalOrderProducts->add($orderProduct);
}
$editForm = $this->createForm(OrderType::class, $order);
$editForm->add('submit', SubmitType::class);
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$orderProducts = $order->getOrderProducts();
foreach ($originalOrderProducts as $oneOriginalOrderProduct) {
if (false === $order->getOrderProducts()->contains($oneOriginalOrderProduct)) {
$order->removeOrderProduct($oneOriginalOrderProduct);
$em->remove($oneOriginalOrderProduct);
$em->flush();
}
}
foreach ($orderProducts as $oneOrderProduct) {
if ($oneOrderProduct->getAmount() == 0) {
$order->removeOrderProduct($oneOrderProduct);
$em->remove($oneOrderProduct);
} else {
if (!$originalOrderProducts->contains($oneOrderProduct)) {
$oneOrderProduct->setOrder($order);
}
$em->persist($oneOrderProduct);
}
$em->persist($order);
$em->flush();
}
return $this->redirectToRoute('order');
}
return $this->render('order/new.html.twig', array('form' => $editForm->createView()));
}
I have a lot of Categories in database.
Here is Category Entity
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="categories")
*/
class Category
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Category")
*/
protected $rootCategory;
/**
* #ORM\Column(type="text")
*/
protected $name;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Category
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set rootCategory
*
* #param \AppBundle\Entity\Category $rootCategory
*
* #return Category
*/
public function setRootCategory(\AppBundle\Entity\Category $rootCategory = null)
{
$this->rootCategory = $rootCategory;
return $this;
}
/**
* Get rootCategory
*
* #return \AppBundle\Entity\Category
*/
public function getRootCategory()
{
return $this->rootCategory;
}
}
I want to get all categories in my edit form
EditFormType:
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use AppBundle\Controller\CategoryController;
class EditPhotoFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$categoryController = new CategoryController();
$builder->add('title', 'text');
$builder->add('description', 'textarea');
$builder->add('category', EntityType::class, array(
'class' => 'AppBundle:Category',
'choices' => $categoryController->getCategories(),
));
}
public function getName()
{
return 'app_photo_edit';
}
}
getCategories()
public function getCategories() {
$em = $this->getDoctrine()->getManager();
return $em->getRepository('AppBundle:Category')->findAll();
}
I am getting next error:
Error: Call to a member function has() on null
Thats because there is not Doctrine in controller object. Where should i get Doctrine and Repository in this case?
How should i do it correct way?
First, you should NEVER instantiate any Controller class yourself. Controller classes are used by Symfony's Kernel to handle a request, and they are loaded automatically with dependencies to do so.
Right here, you don't even need to require the EntityManager in your FormType, because EntityType has a built-in option query_builder to do what you need:
$builder->add('category', EntityType::class, array(
'class' => 'AppBundle:Category',
'query_builder' => function (EntityRepository $er) {
return $er->createQueryBuilder('c');
},
);
This should do the trick. (check here for more details)
However, if one day you really need to import a dependancy inside your Form (whether it is EntityManager or another service), here's how you should do:
A. import the given dependency in your constructor:
private $dependency;
public function __construct(Dependency $dependency)
{
$this->$dependency = $dependency;
}
B. Declare your Form as a Service, with your dependency's id as argument:
<service id="app.form.type.edit_photo"
class="AppBundle\Form\Type\EditPhotoFormType">
<tag name="form.type" />
<argument type="service" id="app.dependencies.your_dependency" />
</service>
Then use $this->dependency in your Form wherever you need.
Hope this helps! :)
I installed Symfony3 and I'm trying to validate a formchild (entity child/not mapped fields) inside a normal form, with #Assert\Valid annotation. I couldn't do it so I tried the example from the Manual:
http://symfony.com/doc/current/reference/constraints/Valid.html
This example, in Symfony 3, doesn't work (at least for me).
This is where #Assert\Valid is used. How does Symfony knows in this case (example from manual) to valid the Address Entity and not any other Entity?
/**
* #Assert\Valid
*/
protected $address;
Has someone tried to test the example from the manual to see if it works?
Can someone please provide the working example from the manual? I don't know what I'm doing wrong..
This is my TestingController.php:
namespace WebsiteBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use WebsiteBundle\Entity\Author;
use WebsiteBundle\Form\Type\AuthorRegistrationType;
class TestingController extends Controller
{
public function registerAccountAction(Request $request)
{
$author = new Author();
$form = $this->createForm(AuthorRegistrationType::class, $author, array(
'required' => false
));
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()) {
echo "It works";
}
return $this->render('TemplatesBundle:Default:testing_registration.html.twig', array(
'form' => $form->createView(),
));
}
}
The AuthorRegistrationType.php:
namespace WebsiteBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class AuthorRegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("firstname")
->add("lastname")
->add("zipcode", TextType::class, array('mapped' => false))
->add("street", TextType::class, array('mapped' => false))
->add('save', SubmitType::class);
}
}
Author Entity:
namespace WebsiteBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Author
{
/**
* #Assert\NotBlank
* #Assert\Length(min = 4)
*/
protected $firstname;
/**
* #Assert\NotBlank
*/
protected $lastname;
/**
* #Assert\Valid
*/
protected $address;
/**
* #return mixed
*/
public function getFirstname()
{
return $this->firstname;
}
/**
* #param mixed $firstname
*/
public function setFirstname($firstname)
{
$this->firstname = $firstname;
}
/**
* #return mixed
*/
public function getLastname()
{
return $this->lastname;
}
/**
* #param mixed $lastname
*/
public function setLastname($lastname)
{
$this->lastname = $lastname;
}
/**
* #return mixed
*/
public function getAddress()
{
return $this->address;
}
/**
* #param mixed $address
*/
public function setAddress(Address $address)
{
$this->address = $address;
}
}
Address Entity:
namespace WebsiteBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
class Address
{
/**
* #Assert\NotBlank()
*/
protected $street;
/**
* #Assert\NotBlank
* #Assert\Length(max = 5)
*/
protected $zipCode;
/**
* #return mixed
*/
public function getStreet()
{
return $this->street;
}
/**
* #param mixed $street
*/
public function setStreet($street)
{
$this->street = $street;
}
/**
* #return mixed
*/
public function getZipCode()
{
return $this->zipCode;
}
/**
* #param mixed $zipCode
*/
public function setZipCode($zipCode)
{
$this->zipCode = $zipCode;
}
}
This is what I get now:
Firstname
This value should not be blank.
Lastname
This value should not be blank.
Street
Zipcode
So: the main Entity is validated, but the inherited one (Street/Zipcode) is "ignored"..
How can I validate (with this metod, not creating Custom Validation) the child entity?
Thanks
The street and zipcode fields in your AuthorRegistrationTypeare not related to your Address entity. What is the reason you did it this way? I would create a separate form type for your Address entity:
namespace WebsiteBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class AddressType extends AbstractType
{
protected function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('street', TextType::class)
->add('zipCode', TextType::class)
;
}
}
Then you can embed this in your AuthorRegistrationType:
namespace WebsiteBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
class AuthorRegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add("firstname")
->add("lastname")
->add("address", AddressType::class)
->add('save', SubmitType::class);
}
}
xabbuh is right:
I added in the controller the following:
$address = new Address();
$author->setAddress($address);
before
$form = $this->createForm(AuthorRegistrationType::class, $author, array(
'required' => false
));
And in AddressType:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'WebsiteBundle\Entity\Address'
));
}
And it works!
In your entity Author you have an attribute address, which is another entity Address. In my opinion this is called embedded entity, not inherited. However I'm not a native English speaker, so I cannot guarantee.
The issue is in your entity Author:
/**
* #Assert\Valid
*/
protected $address;
This will validate the field itself, but not the fields street and zipCode from the entity Address. To achieve that, you should tell the validation to traverse to the entity Address and look up for the validations in there:
/**
* #Assert\Valid(
* traverse = true
* )
*/
protected $address;
This should do the job.
EDIT:
I couldn't see the relation between these two entities immediately in your examples. Now I see that Author can have only one Address. That means the above suggested code won't do the work for you. It is necessary only if you have a relation where an Author has a field address which is a collection, i.e one author can have many addresses.
I've created a custom form validator that does not seem to be working. It gets called during form submission and returns false when it should fail validation. But it doesn't seem to tell the form that validation has failed.
Here is the form validator: (code box scrolls)
<?php
namespace Redacted\AppBundle\Validator\Constraints;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RequiredIfPlainPasswordSetValidator extends ConstraintValidator
{
/**
* RequestStack instance.
*
* #var Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* dependency injection.
*
* #param Symfony\Component\HttpFoundation\RequestStack $requestStack
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
/**
* if the plain password has been set, the current password must not be
* empty.
*
* #param string $currentPassword
* #param Symfony\Component\Validator\Constraint $constraint
*
* #return bool
*/
public function validate($currentPassword, Constraint $constraint)
{
$plainPassword = $this->requestStack->getCurrentRequest()->request
->get('security_settings')['plainPassword']['first'];
if ($plainPassword && !$currentPassword) {
return false;
}
return true;
}
}
And here is the constraint that goes with it:
<?php
namespace Redacted\AppBundle\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* #Annotation
*/
class RequiredIfPlainPasswordSet extends Constraint
{
/**
* error message
*
* #var string
*/
protected $message = 'Please enter your current password.';
/**
* the alias for the related validator in services.yml
*
* #return string
*/
public function validatedBy()
{
return 'required_if_plain_password_set';
}
}
and the relevant part of app/config/services.yml:
services:
redacted.appbundle.required_if_plain_password_set:
class: Redacted\AppBundle\Validator\Constraints\RequiredIfPlainPasswordSetValidator
arguments: ["#request_stack"]
tags:
- { name: validator.constraint_validator, alias: required_if_plain_password_set }
I've created a custom form type: (code box scrolls)
<?php
namespace Redacted\AppBundle\Form;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\AbstractType;
class SecuritySettingsFormType extends AbstractType
{
/**
* the name of the form type
*
* #return string
*/
public function getName()
{
return 'security_settings';
}
/**
* add fields to form
*
* #param Symfony\Component\Form\FormBuilderInterface $formBuilder
* #param array $options
*/
public function buildForm(FormBuilderInterface $formBuilder, array $options)
{
$formBuilder
->add('email')
->add('currentPassword', 'password')
->add('plainPassword', 'repeated', [
'type' => 'password',
'invalid_message' => 'Passwords must match.',
]);
}
/**
* configureOptions.
*
* #param Symfony\Component\OptionsResolver\OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$options = [
'data_class' => 'AppBundle\Entity\User',
'validation_groups' => ['security_settings'],
];
$resolver->setDefaults($options);
}
}
which validates against the User entity due to the data_type. Here is the relevant part of the User entity:
<?php
namespace Redacted\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Redacted\AppBundle\Validator\Constraints\RequiredIfPlainPasswordSet;
class User implements UserInterface, \Serializable
{
// ...
/**
* #var string (default: null)
*
* #Assert\NotBlank(message="user.email_required", groups={"security_settings"})
* #Assert\Email(message="security_settings.valid_email" groups={"security_settings"})
* #ORM\Column(name="email", type="string", length=255, nullable=true, unique=true)
*/
private $email = null;
/**
* current password - used for validation only
*
* #RequiredIfPlainPasswordSet(
* message="security_settings.current_password_required",
* groups={"security_settings"}
* )
* #var string
*/
protected $currentPassword;
// ... setters and getters for above, etc.
}
And finally, here's the controller method I'm using to check. I'll leave out the view because it's probably irrelevant. (I'm defining my controller as a service so no redirectToRoute(), etc.)
// in the controller class...
/**
* Security settings page - email, password, etc.
*
* #Route("/security-settings", name="security_settings")
* #Template()
*
* #param Symfony\Component\HttpFoundation\Request $request
*
* #return array|Symfony\Component\HttpFoundation\RedirectResponse
*/
public function securitySettingsAction(Request $request)
{
$loggedInUser = $this->tokenStorage->getToken()->getUser();
$form = $this->formFactory
->create(new SecuritySettingsFormType(), $loggedInUser);
$form->handleRequest($request);
if ($form->isValid()) {
$user = $form->getData();
// persist the user
$this->saveUserSettings($user);
// set a success message
$this->setSecuritySettingsFlashSuccess($request);
// redirect back
$route = $this->router->generate('security_settings');
return new RedirectResponse($route);
}
return ['form' => $form->createView()];
}
The idea is that the current password should only be required if the new password is entered. Although that validate() function is getting called and returning false when it should, the form's isValid() is returning true and it's saving. If I add a #Assert\NotBlank(groups={"security_settings"}) assertion to the User::currentPassword field, it does fire and fail successfully, so it is looking for validation annotations on that field.
What am I missing?
The problem was that the validate() method of the ConstraintValidator should not just return true or false, it should build a violation like so:
/**
* if the plain password has been set, the current password must not be
* empty.
*
* #param string $currentPassword
* #param Symfony\Component\Validator\Constraint $constraint
*/
public function validate($currentPassword, Constraint $constraint)
{
$plainPassword = $this->requestStack->getCurrentRequest()->request
->get('security_settings')['plainPassword']['first'];
if ($plainPassword && !$currentPassword) {
$this->context->buildViolation($constraint->message)
->atPath('currentPassword')->addViolation();
}
}
Thanks to my coworker for catching it. All works as expected now.