Cannot set unbound field constraint in Symfony - forms

I'm trying to do as easy thing as make the date field not to be set under "now" value. I'd emphasise that this date field is not linked to any Entity. Symfony keeps ignoring constraint even though I set it in two places:
1. Constraint set in the class definition
This is the first thing I've done. I decided to put this constraint directly where I define this date field
namespace Asmox\BubblechecklistBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Validator\Constraints\LessThanOrEqual;
class DeadlineType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('date', 'datetime',array(
'mapped' => false,
'date_format' => 'dd MMMM yyyy',
'constraints' => array(
new LessThanOrEqual("now")
)
));
}
2. Constraint set in the validation.yml file
I also added constraint into separate file, because the first way didn't give the results:
Asmox\BubblechecklistBundle\Form\Type\DeadlineType:
properties:
date:
- LessThanOrEqual: now
I ensured that I have enabled validation in both my configuration files (for prod and dev):
validation: { enabled: true, enable_annotations: true }
But every time I try to submit form with date earlier than now, Symfony pass it. I don't know what I can do more. Please take a look on my paste from Controller:
public function newTaskAction(Request $request)
{
// ...
$form = $this->createForm(new TaskType());
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
// Add new task...
// Redirect to the task list
$this->addFlash('notice', 'I've added new task!');
return $this->redirectToRoute('tasksList');
}
else {
// Create view with form if form is not validate
$request = $this->getRequest();
$request->setLocale('pl');
if ($mobileDetector->isMobile())
$template = 'AsmoxBubblechecklistBundle:Task:taskFormPhone.html.twig';
else
$template = 'AsmoxBubblechecklistBundle:Task:taskForm.html.twig';
return $this->render($template, array('form'=>$form->createView()));
}
}
else {
// Create view with form if request isn't POST
$request = $this->getRequest();
$request->setLocale('pl');
// Utwórz widok
if ($mobileDetector->isMobile())
$template = 'AsmoxBubblechecklistBundle:Task:taskFormPhone.html.twig';
else
$template = 'AsmoxBubblechecklistBundle:Task:taskForm.html.twig';
return $this->render($template, array('form'=>$form->createView()));
}
}
Whats wrong?

Related

CRUD using form: Updating

I've been getting errors when I press update.
I'm using the same code as adding.
I think it may be the picture upload that is causing the problem.
Here's the error:
The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) string.
You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Symfony\Component\HttpFoundation\File\File.
My Controller:
public function updateAction(Request $request ,$id)
{
$em = $this->getDoctrine()->getManager();
$forum = $em->getRepository("ForumBundle:Forum")->find($id);
$forum->setModifiee(new \DateTime('now'));
$form = $this->createForm(ForumType::class, $forum);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$file = $form['image']->getData();
$file->move("images/", $file->getClientOriginalName());
$forum->setImage("images/" . $file->getClientOriginalName());
$em->persist($forum);
$em->flush();
return $this->redirectToRoute('forum_show');
}
return $this->render("#Forum/Sujet/Update_topic.html.twig", array("form" => $form->createView()));
}
My Form:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('titre')
->add('tags')
->add('image', FileType::class,array('attr' => array(
'class'=>'form-control'
//'class'=>'btn btn-default btn-file'
)))
->add('blog',TextareaType::class)
->add('Ajouter',SubmitType::class,array('attr' => array(
'class'=>'theme_button color3 wide_button'
)));
}
These are my includes:
namespace ForumBundle\Controller;
use blackknight467\StarRatingBundle\Form\RatingType;
use ForumBundle\Form\RateType;
use ForumBundle\Entity\Commentaire;
use ForumBundle\Entity\Forum;
use ForumBundle\Entity\Rating;
use ForumBundle\Form\CommentaireType;
use ForumBundle\Form\ForumType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\File\File;
According to the Symfony documentation : How to upload file, you have to set a File Object into Image setter, not a string. That'a why you have this error :
The form's view data is expected to be an instance of class Symfony\Component\HttpFoundation\File\File, but is a(n) string.
You can avoid this error by setting the "data_class" option to null or by adding a view transformer that transforms a(n) string to an instance of Symfony\Component\HttpFoundation\File\File.
The rigth way to accomplish this is to set a new File instance to your image on your controller object, like this :
use Symfony\Component\HttpFoundation\File\File;
// ...
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$forum = $em->getRepository("ForumBundle:Forum")->find($id);
$forum->setModifiee(new \DateTime('now'));
$form = $this->createForm(ForumType::class, $forum);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid())
{
$file = $form['image']->getData();
$file->move("images/", $file->getClientOriginalName());
$forum->setImage("images/" . $file->getClientOriginalName());
$forum->setImage(new File($this->getParameter('images_directory').'/'.$forum->getImage()));
$em->persist($forum);
$em->flush();
return $this->redirectToRoute('forum_show');
}
}
// ...
The parameter images_directory have to be set in the services.yml file, according to the symfony's documentation :
# config/services.yaml
# ...
parameters:
# depending of your symfony version
images_directory: '%kernel.project_dir%/web|public/uploads/images'
Another way is to use an uploader service or a dedicated bundle like : VichUploaderBundle or OneupUploaderBundle
Hope this help and have a nice day !!!
In symfony 2.8 in updateAction just correct define fileType for $id
I think it should look like this:
public function updateAction(Request $request ,$id) {
$id->setImage(
new File($this->getParameter('images_directory').'/'.$id->getImage()
));
...

notBlank constraint not working for File input - Form with OneToMany relationship Symfony2

I have a OneToMany relationship between Article and Image entities. I created the Article's form like:
class ArticleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('articleTitle','text',array('required' => false))
->add('articlePrice','number',array('required' => false))
->add('images', new ImageType())
;
}
....
}
And
class ImageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('file', 'file',array('required' => false))
;
}
....
}
I have a problem in validating the file attribute. My goal is to return an error message when user chooses no file (at least one image should be associated to an Article). I tried by asserting the NotBlank but it doesn't work.
<?php
....
use Symfony\Component\Validator\Constraints as Assert;
class Image
{
/**
* Image file
*
* #var File
* #Assert\NotBlank
*/
private $file;
....
}
I don't embed collections while creating my form, since project requirements oblige AJAX uploading for each image separately before submission of the whole form. In other words, a javascript event listener, bound to input changeevent, creates the AJAX call which uploads the image after validation (ie. file is validated for size, extension, in controller). But when I send the overall form, only the other fields are validated and form is submitted even if no file is choosed (I see in headers all formData elements except the file one).
I want to notice that the form is rendered correcly when I inspect element in browser.
I spent a lot of time trying to resolve this issues but to no avail, your help is a rescue.
Edit: As requested by Mr Nawfal Serrar
The AJAX call is performed like:
$(':file').change(function(){
var file = this.files[0];
var url= $('.article-form').attr('data-iu-url');
var formData = new FormData($('form')[0]);
$.ajax({
url: url,
type: 'POST',
success: completeHandler,
data: formData,
});
});
url contains the route image_upload respecting following config:
image_upload:
pattern: /{id}/image_upload
defaults: { _controller: ShopManagementBundle:Image:uploads}
requirements: { _method: post|put }
Controller:
public function uploadsAction(Request $request, $id) {
if ($request->isMethod('POST')) {
$image = $request->files->get('articletype')['images']['file'];
$status='success';
$message='';
$uploadedURL='';
$the_id=0;
if (($image instanceof UploadedFile) && ($image->getError() == 0)) {
if ($image->getSize() < 50000000000) {
$originalName = $image->getClientOriginalName();
$name_array = explode('.', $originalName);
$extension = $name_array[sizeof($name_array) - 1];
$valid_file_types = array('jpeg', 'jpg', 'bmp', 'png', 'gif');
if (in_array(strtolower($extension), $valid_file_types)) {
$imagee= new Image();
$em = $this->getDoctrine()->getManager();
$imagee->setFile($image);
$imagee->setSubDir('hg');
$imagee->upload();
$entity = $em->getRepository('ShopManagementBundle:Article')->find($id);
$imagee->setAricle($entity);
$uploadedURL= $imagee->getUploadDir(). DIRECTORY_SEPARATOR . $imagee->getSubDir(). DIRECTORY_SEPARATOR . $image->getBasename();
$em->persist($entity);
$em->persist($imagee);
$em->flush();
$the_id=$imagee->getId();
} else {
$status = "fail";
$message = "extension problem";
}
} else {
$status = "fail";
$message = "Image size too big";
}
} else {
$status = "fail";
$message = "Error uploading";
}
return $this->render('ShopManagementBundle:Image:image_portion.html.twig', array(
'status' => $status,
'message' => $message,
'uploadedURL' => $uploadedURL,
'image_id'=>$the_id,
));
}
else
return new Response('RE try uploading');
}
As you can see I am not validation using isValid in controller, I validate with if-else statements assuming that the file is already sent.
After all we said in comment zone, I would recommand you to use #Assert\File for your file upload validation. It will prevent your if/else stack.
Since you told me you were attaching the uploaded files to an already existing Article, then using an embedded collection of forms would be the solution as I mentionned.
Since you'll retrieve the article and then create a form type with it, all the Images will be linked to it.
Add the count constraint on the Images list of your:
/**
* #Assert\Count(
* min = "1",
* minMessage = "You must upload at least one image",
* )
*/
private $images;
Since you will retrieve the Article which is already linked to uploaded Image instances, the validation will not throw an error. Otherwise, the user is trying to submit the form without images. The validation constraint will then make the form invalid.
There might be other little stuff to do to make it work, but it should help you to go ahead.
To properly handle file uploads better to follow this method :
http://symfony.com/doc/current/cookbook/doctrine/file_uploads.html
Its the official one, then at the return of the upload method you check if there is no file return your error message else continue with your upload.

Correct way to use FormEvents to customise fields in SonataAdmin

I have a Sonata Admin class with some form fields in it, but I'd like to use the FormEvents::PRE_SET_DATA event to dynamically add another form field based on the bound data.
However, I'm running into several problems:
1) The only way I can find to add the form field to the correct 'formGroup' in the admin is by adding the new field twice (once via the formMapper and once via the form itself)... this seems very wrong and I cannot control where in the formGroup it appears.
2) The added element doesn't seem to know that it has a connected Admin (probably because it is added using Form::add()). This means, amongst other things, that it renders differently to the other fields in the form since it triggers the {% if sonata_admin is not defined or not sonata_admin_enabled or not sonata_admin.field_description %} condition in form_admin_fields.html.twig
So, this leads me to believe that I'm doing this all wrong and there must be a better way.
So...
What is the correct way to use a FormEvent to add a field to a form group, ideally in a preferred position within that group, when using SonataAdmin?
Here's some code, FWIW...
protected function configureFormFields(FormMapper $formMapper)
{
$admin = $this;
$formMapper->getFormBuilder()->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($admin, $formMapper) {
$subject = $event->getData();
// do something fancy with $subject
$formOptions = array(/* some cool stuff*/);
// If I don't add the field with the $formMapper then the new field doesn't appear on the rendered form
$formMapper
->with('MyFormGroup')
->add('foo', null, $formOptions)
->end()
;
// If I don't add the field with Form::add() then I get a Twig Exception:
// Key "foo" for array with keys "..." does not exist in my_form_template.html.twig at line xx
$event
->getForm()
->add('foo', null, $formOptions)
;
});
$formMapper
->with('MyFormGroup')
->add('fieldOne')
->add('fieldTwo')
->end()
;
}
The aim is to add the new foo field between fieldOne and fieldTwo in MyFormGroup.
Edit: here's what I came up with with the help of Cassiano's answer
protected function configureFormFields(FormMapper $formMapper)
{
$builder = $formMapper->getFormBuilder();
$ff = $builder->getFormFactory();
$admin = $this;
$formMapper->getFormBuilder()->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) use ($ff, $admin) {
$subject = $event->getData();
// do something fancy with $subject
$formOptions = array(
'auto_initialize' => false,
'class' => 'My\ProjectBundle\Entity\MyEntity',
/* some cool stuff*/
);
$event->getForm()->add($ff->createNamed('foo', 'entity', null, $formOptions));
});
$formMapper
->with('MyFormGroup')
->add('fieldOne')
->add('foo') // adding it here gets the field in the right place, it's then redefined by the event code
->add('fieldTwo')
->end()
;
}
No time here for a long answer, I will paste a piece of code and I don't know if fits exactly in your case, in my it's part of multi dependent selects (country, state, city, neighbor).
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Doctrine\ORM\EntityRepository;
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper->add('myfield');
$builder = $formMapper->getFormBuilder();
$ff = $builder->getFormFactory();
$func = function (FormEvent $e) use ($ff) {
$form = $e->getForm();
if ($form->has('myfield')) {
$form->remove('myfield');
}
$form->add($ff->createNamed('myfield', 'entity', null, array(
'class' => '...',
'attr' => array('class' => 'form-control'),
'auto_initialize' => false,
'query_builder' => function (EntityRepository $repository) use ($pais) {
$qb = $repository->createQueryBuilder('estado');
if ($pais instanceof ...) {
$qb = $qb->where('myfield.other = :other')
->setParameter('other', $other);
} elseif(is_numeric($other)) {
$qb = $qb->where('myfield.other = :other_id')
->setParameter('other_id', $other);
}
return $qb;
}
)));
};
$builder->addEventListener(FormEvents::PRE_SET_DATA, $func);
$builder->addEventListener(FormEvents::PRE_BIND, $func);
}

Symfony 2 basic GET form generated URL

I have been trying to create an extremely basic symfony form (used for search functionality) with only one input. It uses GET method on submit. It seems to work as expected, however it generates an extremely ugly and unnecessarily long URL. I have been trying to 'clean' the URL up for a quite a while now, I was wondering if someone ran into the same problem and knows how to fix it?
Form
$form = $this->createFormBuilder($search)
->setMethod('GET')
->add('q', 'text')
->add('search', 'submit')
->getForm();
On submit the form generates the following URL:
search?form[q]=red+apple&form[search]=&form[_token]=bb342d7ef928e984713d8cf3eda9a63440f973f2
Desired URL:
search?q=red+apple
Thanks in advance!
To create your desired URL, you will have to set the form name by using createNamedBuilder which you'll just leave blank ''.
To remove _token you need to set csrf_protection to false. Please look into csrf protection to make sure you know what could happen if it is turned off.
Changing your code to the following should give you the results you want.
$form = $this->get('form.factory')->createNamedBuilder('', 'form', $search, array(
'csrf_protection' => false,
))->setMethod('GET')
->add('q', 'text')
->add('search', 'submit')
->getForm();
This should produce a URL like:
search?q=red+apple&search=
Edit:
If you want to get rid of &search=, one way would be to change search from submit to button.
->add('search', 'button')
This will require javascript to submit your form.
Here is simple example in jquery:
//This assumes one form and one button
$(document).ready(function(){
$('button').click(function(){
$('form').submit();
});
});
This will produce a URL like:
search?q=red+apple
To access GET vars you put something like this in your controller:
public function yourSearchAction(Request $request)
{
// your code ...
$form->handleRequest($request);
if ($form->isValid()) {
$getVars = $form->getData();
$q = $getVars['q'];
$page = $getVars['page'];
$billing = $em
//Do something
}
return //your code
}
Just to clarify if you are adding page to your URL you will need to add it to your form:
->add('page', 'text')
Old question but, for people who want to know, this does the job too (Symfony 2.8) :
<?php
// src/AppBundle/Form/SearchType.php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setMethod('GET')
->add('q', TextType::class)
->add('submit', SubmitType::class))
;
}
public function getBlockPrefix(){
return '';
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'csrf_protection' => false,
]);
}
}
In your controller :
<?php
//...
use AppBundle\Form\SearchType;
//...
public function yourSearchAction(Request $request)
{
$form = $this->createForm(SearchType::class);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$q = $form->get('q')->getData();
// ...
}
// ...
}

Symfony2 - Dynamic form choices - validation remove

I have a drop down form element. Initially it starts out empty but it is populated with values via javascript after the user has made some interactions. Thats all working ok. However when I submit it always returns a validation error This value is not valid..
If I add the items to the choices list in the form code it will validate OK however I am trying to populate it dynamically and pre adding the items to the choices list is not going to work.
The problem I think is because the form is validating against an empty list of items. I don't want it to validate against a list at all. I have set validation required to false. I switched the chocie type to text and that always passes validation.
This will only validate against empty rows or items added to choice list
$builder->add('verified_city', 'choice', array(
'required' => false
));
Similar question here that was not answered.
Validating dynamically loaded choices in Symfony 2
Say you don't know what all the available choices are. It could be loaded in from a external web source?
after much time messing around trying to find it. You basically need to add a PRE_BIND listener. You add some extra choices just before you bind the values ready for validation.
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
public function buildForm(FormBuilderInterface $builder, array $options)
{
// .. create form code at the top
$ff = $builder->getFormFactory();
// function to add 'template' choice field dynamically
$func = function (FormEvent $e) use ($ff) {
$data = $e->getData();
$form = $e->getForm();
if ($form->has('verified_city')) {
$form->remove('verified_city');
}
// this helps determine what the list of available cities are that we can use
if ($data instanceof \Portal\PriceWatchBundle\Entity\PriceWatch) {
$country = ($data->getVerifiedCountry()) ? $data->getVerifiedCountry() : null;
}
else{
$country = $data['verified_country'];
}
// here u can populate choices in a manner u do it in loadChoices use your service in here
$choices = array('', '','Manchester' => 'Manchester', 'Leeds' => 'Leeds');
#if (/* some conditions etc */)
#{
# $choices = array('3' => '3', '4' => '4');
#}
$form->add($ff->createNamed('verified_city', 'choice', null, compact('choices')));
};
// Register the function above as EventListener on PreSet and PreBind
// This is called when form first init - not needed in this example
#$builder->addEventListener(FormEvents::PRE_SET_DATA, $func);
// called just before validation
$builder->addEventListener(FormEvents::PRE_BIND, $func);
}
The validation is handled by the Validator component: http://symfony.com/doc/current/book/validation.html.
The required option in the Form layer is used to control the HTML5 required attribute, so it won't change anything for you, and that is normal.
What you should do here is to configure the Validation layer according to the documentation linked above.
Found a better solution which I posted here: Disable backend validation for choice field in Symfony 2 Type
Old answer:
Just spent a few hours dealing with that problem. This choice - type is really annoying. My solution is similar to yours, maybe a little shorter. Of course it's a hack but what can you do...
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('place', 'choice'); //don't validate that
//... more form fields
//before submit remove the field and set the submitted choice as
//"static" choices to make "ChoiceToValueTransformer" happy
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if ($form->has('place')) {
$form->remove('place');
}
$form->add('place', 'choice', array(
'choices' => array($data['place']=>'Whatever'),
));
});
}
Add this inside buildForm method in your form type class so that you can validate an input field value rather a choice from a select field value;
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$form = $event->getForm();
if ($form->has('verified_city')) {
$form->remove('verified_city');
$form->add(
'verified_city',
'text',
['required' => false]
)
}
}
);
Update in Validations.yml
Kindly update the Validation.yml file in the below format : setting the group names in the each field
password:
- NotBlank: { message: Please enter password ,groups: [Default]}
Update in Form Type
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver->setDefaults(array(
'data_class' => 'RegistrationBundle\Entity\sf_members',
'validation_groups' => function(FormInterface $form){
$data = $form->getData();
$member_id = $data->getMemberId();
// Block of code;
// starts Here :
if( condition == 'edit profile') {
return array('edit');
} else
{
return array('Default');
}
},
Update in Entity
/**
* #var string
*
* #ORM\Column(name="password", type="text")
* #Assert\Regex(
* pattern="/(?i)^(?=.[a-zA-Z])(?=.\d).{8,}$/",
* match=true,
* message="Your password must be at least 8 characters, including at least one number and one letter",
* groups={"Default","edit"}
* )
*/
private $password;