symfony2's FOSRestController can't validate an ajax post request - rest

I'm developing a Symfony 2.6.1 application and I have a form I render with the FormTypes and an Entity using annotations as validation. The form is submitted an AJAX POST call to a FOSRestController. The thing is the isValid() function is returning FALSE and I get no error messages...
My FOSRestController looks as follows:
class RestGalleryController extends FOSRestController{
/**
* #Route(requirements={"_format"="json"})
*/
public function postGalleriesAction(\Symfony\Component\HttpFoundation\Request $request){
return $this->processForm(new \Law\AdminBundle\Entity\Gallery());
}
private function processForm(\Law\AdminBundle\Entity\Gallery $gallery){
$response = array('result' => 'Default');
$gallery->setName('TEST'); //Just added this to be sure it was a problem with the validator
$form = $this->createForm(
new \Law\AdminBundle\Form\Type\GalleryType(),
$gallery
);
$form->handleRequest($this->getRequest());
if ($form->isValid()) {
$response['result'] = 'Is Valid!';
}else{
var_dump( $form->getErrorsAsString() );
die;
}
return $response;
}
My Gallery Entity class below:
<?php
namespace Law\AdminBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\ORM\Mapping as ORM;
/**
* Gallery
*
* #ORM\Table(name="gallery")
* #ORM\Entity
*/
class Gallery{
/**
* #var string
* #Assert\NotBlank()
* #ORM\Column(name="name", type="text", nullable=false)
*/
private $name;
public function __construct(){
$this->images = new ArrayCollection();
}
/**
* Set name
*
* #param string $name
* #return Gallery
*/
public function setName($name){
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName(){
return $this->name;
}
}
The GalleryType, encapsulating the form:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class GalleryType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options){
$builder->add('name');
}
/**
* {#inheritdoc}
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Law\AdminBundle\Entity\Gallery',
'csrf_protection' => false,
));
}
/**
* {#inheritdoc}
*/
public function getName()
{
return 'Gallery';
}
}
Finally, In my app/config/config.yml, the validation is set up as follows:
validation: { enable_annotations: true }
To get the validation error I've also tried with the following function, unsuccessfully :
private function getErrorMessages(\Symfony\Component\Form\Form $form) {
$errors = array();
foreach ($form->getErrors() as $key => $error) {
if ($form->isRoot()) {
$errors['#'][] = $error->getMessage();
} else {
$errors[] = $error->getMessage();
}
}
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$errors[$child->getName()] = $this->getErrorMessages($child);
}
}
return $errors;
}
EDIT:
If I manually use a validator, it works:
$formGallery = new Gallery();
$formGallery->setName($this->getRequest()->get('name', NULL));
$validator = $this->get('validator');
$errors = $validator->validate($formGallery);
So it's like somehow my GalleryType wasn't using the validator.

This is because you are using handleRequest with empty submitted data I guess. In such scenario you such call:
// remove form->handleRequest call
// $form->handleRequest($this->getRequest());
$form->submit($request->request->all());
as handleRequest will auto-submit form unless one field is present. When you handle request with empty array form is not being submitted, thats why isValid return false with no errors.
Note: check if you are sending empty POST array or something like:
`Gallery` => []
If you are sending empty Gallery array everything should work as expected.
Could you paste data that you are sending via AJAX request?

Related

Symfony, How to properly set up a form that uses multiple entities?

I have a user creation page that uses User entity form with Username and Email attributes.
I would like when creating a user to be able to choose the tools he will have access to. To do this, retrieve all the tools and display them in a checkbox. Thus, once the form has been validated, the user obtains a username, an email and the tools to which he has access.
In my User class I can add a tool from the Tool entity using the AddTool() method.
How can I integrate the tools into my user creation form? I don't see how to do I'm lost.
Class User :
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180)
*/
private $username;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\ManyToMany(targetEntity=Tool::class, mappedBy="users", fetch="EAGER")
*/
private $tools;
/**
* #ORM\Column(type="string", length=125, unique=true)
*/
private $email;
public function __construct()
{
$this->tools = new ArrayCollection();
}
// SOME FUNCTIONS
/**
* #return Collection|Tool[]
*/
public function getTools(): Collection
{
return $this->tools;
}
public function addTool(Tool $tool): self
{
if (!$this->tools->contains($tool)) {
$this->tools[] = $tool;
$tool->addUser($this);
}
return $this;
}
public function removeTool(Tool $tool): self
{
if ($this->tools->removeElement($tool)) {
$tool->removeUser($this);
}
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
}
UserType :
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('username')
->add('email')
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}
In Your UserType buildForm function add something like:
$builder
->add('username')
->add('email')
->add('Tools', EntityType::class, [
'class' => Tool::class,
'multiple' => true
])
;
And You need to create a function in Tool::class definition that will allow display it as a string:
#[Pure] public function __toString(): string
{
return ''.$this->getFullName();
}
It should allow You to select Tools entities during the generation of User forms.

How to get data in a form event within a CollectionType?

I have a problem with Symfony 4 on an issue already identified and described on Github (here: https://github.com/symfony/symfony/issues/5694#issuecomment-110398953) but I can't find a way to apply this answer.
When I try to use a POST_SET_DATA form event in a ChildType form, the function getData() gives me a null value because the "allow_add" option is set on true in the ParentType form which is a CollectionType.
I have 3 collections: Page, Moduling and Module. The Moduling document is used to embed a collection of Module forms. The purpose is to be able to add multiple forms to the Page collection with one request, following this Symfony article: https://symfony.com/doc/current/form/form_collections.html.
I have 2 different embedded documents: Tag and Task. Both of them are embedded in the Module document (EmbedOne). What I want to do is to be able to custom the ModuleType field with a form event listener so that I just need to set the title of the Module in the controller and then Symfony knows it needs to use the TaskType or the TagType within the ModuleType.
So first, here is my controller
class TaskingController extends Controller
{
/**
* The controller from which I set the module title, "task" here
*
* #Route("/{slug}/task/create", name="tasking_create")
*
* #ParamConverter("page", options={"mapping": {"slug": "slug"}})
*
* #return Response
*/
public function createTasking(DocumentManager $dm, $id, Module $module, Moduling $moduling)
{
$page = $dm->find(Page::class, $id);
$module->setTitle('task');
$moduling->addModule($module);
$page->addModuling($moduling);
$form = $this->createForm(ModulingType, $moduling);
$form->handleRequest($request);
if ($form->isValid() && $form->isSubmitted() {
// Form validation then redirection
}
// Render form template}
}
}
Now, here are my three collections: pages, moduling and modules
/**
* My page document
*
* #MongoDB\Document(collection="pages")
*/
class Page
{
/**
* #MongoDB\Id(strategy="AUTO")
*/
protected $id;
/**
* #MongoDB\ReferenceMany(targetDocument="App\Document\Moduling")
*
* #var Moduling
*/
protected $moduling = array();
public function __construct()
{
$this->moduling = new ArrayCollection();
}
/**
* Get the value of id
*/
public function getId()
{
return $this->id;
}
/**
* #return Collection $moduling
*/
public function getModuling()
{
return $this->moduling;
}
/**
* #param Moduling $moduling
*/
public function addModuling(Moduling $moduling)
{
$this->moduling[] = $moduling;
}
/**
* #param Moduling $moduling
*/
public function removeModuling(Moduling $moduling)
{
$this->moduling->removeElement($moduling);
}
}
/**
* #MongoDB\Document(collection="moduling")
*/
class Moduling
{
/**
* #MongoDB\Id(strategy="AUTO")
*/
protected $id;
/**
* #MongoDB\ReferenceOne(targetDocument="App\Document\Page", storeAs="id")
*
* #var Page
*/
protected $parentPage;
/**
* #MongoDB\ReferenceMany(targetDocument="App\Document\Module", mappedBy="moduling")
*/
protected $module = array();
public function __construct()
{
$this->module = new ArrayCollection();
}
/**
* Get the value of id
*/
public function getId()
{
return $this->id;
}
public function getModule()
{
return $this->module;
}
public function addModule(Module $module): self
{
$this->module[] = $module;
}
public function removeModule(Module $module)
{
$this->module->removeElement($module);
}
/**
* Get the value of parentPage
*
* #return Page
*/
public function getParentPage()
{
return $this->parentPage;
}
/**
* Set the value of parentPage
*
* #param Page $parentPage
*
* #return self
*/
public function setParentPage(Page $parentPage)
{
$this->parentPage = $parentPage;
return $this;
}
}
/**
* #MongoDB\Document(collection="modules")
*/
class Module
{
/**
* #MongoDB\Id(strategy="AUTO")
*/
public $id;
/**
* #MongoDB\Field(type="string")
*/
public $title;
/**
* #MongoDB\ReferenceOne(targetDocument="App\Document\Moduling", inversedBy="module", storeAs="id")
*/
public $moduling;
/**
* #MongoDB\EmbedOne(targetDocument="App\Document\Task", strategy="set")
* #Assert\Valid
*/
public $task;
public function getTitle()
{
return $this->title;
}
/**
* #return self
*/
public function setTitle($title)
{
$this->title = $title;
return $this;
}
public function getTask()
{
return $this->task;
}
public function setTask(Task $task = null)
{
$this->task = $task;
}
}
My embedded document Task. The Tag document has the same structure.
/**
* #MongoDB\EmbeddedDocument
*/
class Task
{
/**
* #MongoDB\Id(strategy="AUTO")
*/
protected $id;
public function getId()
{
return $this->id;
}
}
My ModulingType, which is a collection of ModuleType
class ModulingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('module', CollectionType::class, [
'entry_type' => ModuleType::class,
'entry_options' => [
'label' => false,
],
'by_reference' => false,
'allow_add' => true,
'allow_delete' => true
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Moduling::class
]);
}
}
class ModuleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
$module = $event->getData();
$form = $event->getForm();
if ('task' == $module->getTitle()) {
$form->add('task', TaskType::class);
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Module::class
]);
}
}
So I have identified the problem. When I try to make this work, Symfony sends me this error message: "Call to a member function getTitle() on null". It seems the getData() doesn't get anything.
Actually, after reading few posts on Github I've realized that the "allow_add" option set on "true" was the origin of this issue. And indeed, when I set it on "false" I don't have any error message. But the consequence of this is that my JQuery doesn't allow me to duplicate the form if I want to, the "allow_add" option is necessary to do that.
In the Github post I uploaded, they say that the solution is to write this code first in the ModuleType:
$builder->addEventListener(FormEvents::POST_SET_DATA, function (FormEvent $event) {
if (null != $event->getData()) {
}
}
It's what I did but it doesn't change anything. I wrote this, followed by the code written in the ModuleType but I still have the same error message... Perhaps I don't know how to insert it correctly in the ModuleType.
I hope someone has a solution. I know I can still add the Tag and Task types directly in the ModulingType but I would have more collections.
Thanks a lot for helping me, I hope I've been clear enough!
Cheers
Did you tried this:
if (!is_null($module) && 'task' == $module->getTitle()) {
$form->add('task', TaskType::class);
}
So actually I found a solution, I was really closed, but I got a new problem...
class ModuleType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$module = $event->getData();
$form = $event->getForm();
if (null != $event->getData()) {
if ('task' == $module->getTitle()) {
$form->add('task', TaskType::class);
}
}
});
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Module::class
]);
}
}
This was the solution so as you can see it was not that complicated BUT the fact of using a form event in the ModuleType creates a new issue.
In my ModulingType, I add an option
'allow_add' => true,
This really useful tool allows to automatically add a "data-prototype" in my form so that I can copy/past some jQuery lines available here (https://symfony.com/doc/current/form/form_collections.html) and then be able to duplicate or delete my form. However, when using a form event, the data-prototype doesn't register anything as it is created before my TaskType.
So after spending hours reading discussions on Github and trying to find the solution, I came to the conclusion I had to create a TaskingType and a TagingType, which look like this:
class TaskingType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('task', TaskType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Module::class
]);
}
}
So ok, this solution is not perfect and I have some code duplication. But at least it allows me to only have 3 collections: Page, Moduling and Module.
If someone finds an elegant way to manage everything with one form without deleting the content available in data-prototype, please keep me posted :)

The class 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' was not found in the chain configured namespaces during Rest API Unit Test

I'm writing a REST API client and I am trying to unit test the user creation process which let a user to upload an image.
I am using Symfony 2.7, Doctrine Extension Bundle (Uploadable extension) and FOS Rest Bundle
The unit tests are working well excepted when I try to upload a file, it triggers me the following error when I error_log the 500 HTTP Reponse :
The class 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' was not found in the chain configured namespaces
Please find the relevant code :
UsersControllerTest.php
<?php
namespace Acme\Bundle\UserBundle\Tests\Controller;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeGuesser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class UsersControllerTest extends WebTestCase
{
public function testAvatar(){
$client = static::createClient();
$shortImage = tempnam(sys_get_temp_dir(), 'upl');
imagepng(imagecreatetruecolor(10, 10), $shortImage);
$file = new UploadedFile(
$shortImage,
basename($shortImage),
MimeTypeGuesser::getInstance()->guess($shortImage),
filesize($shortImage)
);
$crawler = $client->request(
"POST",
"/api/users",
array(
"user_registration" => array(
"firstName" => "test",
"lastName" => "test"
),
),
array(
'user_registration'=>array('avatar'=>$file)
),
array(
'Content-Type' => 'multipart/formdata'
)
);
error_log($client->getResponse()); //Here I see the Uploadable class namespace error
$this->assertEquals(200, $client->getResponse()->getStatusCode());
}
}
UserRegistrationType.php
<?php
namespace Acme\Bundle\UserBundle\Form\Type;
use Acme\Bundle\UserBundle\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormBuilderInterface;
class UserRegistrationType extends AbstractType{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\Bundle\UserBundle\Entity\User',
'cascade_validation' => true,
'csrf_protection' => false
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('firstName', 'text');
$builder->add('lastName', 'text');
$builder->add('avatar', 'file', array(
'required' => false
));
}
public function getParent()
{
return 'form';
}
public function getName()
{
return 'user_registration';
}
}
UsersController.php
<?php
namespace Acme\Bundle\UserBundle\Controller;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Controller\Annotations\View;
use Acme\Bundle\UserBundle\Entity\User;
use Acme\Bundle\UserBundle\Entity\Avatar;
use Acme\Bundle\UserBundle\Form\Type\UserRegistrationType;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations\RouteResource;
use JMS\SecurityExtraBundle\Annotation as JMSSecurity;
class UsersController extends FOSRestController
{
/**
* #View(serializerGroups={"Registration"})
*/
public function postUsersAction(Request $request){
$user = new User();
$form = $this->createForm(new UserRegistrationType(), $user);
$form->submit($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
if(null !== $user->getAvatar()){
$uploadableManager = $this->get('stof_doctrine_extensions.uploadable.manager');
$uploadableManager->markEntityToUpload($user, $user->getAvatar());
}
$em->flush();
return $user;
}
else {
$validator = $this->get('validator');
$errors = $validator->validate($user, array('Default','Registration'));
$view = $this->view($errors, 400);
return $this->handleView($view);
}
}
}
Avatar.php
<?php
namespace Acme\Bundle\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* Avatar
*
* #ORM\Table("avatar")
* #ORM\Entity
* #Gedmo\Uploadable(pathMethod="getPath", callback="postUploadAction", filenameGenerator="SHA1", allowOverwrite=true, appendNumber=true)
*/
class Avatar
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="path", type="string")
* #Gedmo\UploadableFilePath
*/
private $path;
/**
* #ORM\Column(name="name", type="string")
* #Gedmo\UploadableFileName
*/
private $name;
/**
* #ORM\Column(name="mime_type", type="string")
* #Gedmo\UploadableFileMimeType
*/
private $mimeType;
/**
* #ORM\Column(name="size", type="decimal")
* #Gedmo\UploadableFileSize
*/
private $size;
public function postUploadAction(array $info)
{
// Do some stuff with the file..
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
public function getPath(){
return __DIR__.'../../web/avatars/';
}
/**
* Set path
*
* #param string $path
* #return Image
*/
public function setPath($path)
{
$this->path = $path;
return $this;
}
/**
* Set mimeType
*
* #param string $mimeType
* #return Image
*/
public function setMimeType($mimeType)
{
$this->mimeType = $mimeType;
return $this;
}
/**
* Get mimeType
*
* #return string
*/
public function getMimeType()
{
return $this->mimeType;
}
/**
* Set size
*
* #param string $size
* #return Image
*/
public function setSize($size)
{
$this->size = $size;
return $this;
}
/**
* Get size
*
* #return string
*/
public function getSize()
{
return $this->size;
}
}
User.php
class User{
...
/**
* Avatar
*
* #ORM\OneToOne(targetEntity="Acme\Bundle\UserBundle\Entity\Avatar", cascade={"all"})
* #ORM\JoinColumn(nullable=true)
* #Assert\Valid
*/
private $avatar;
public function getAvatar(){
return $this->avatar;
}
public function setAvatar($avatar = null){
$this->avatar = $avatar;
return $this;
}
}
Error stack (JSON)
For full stack please see here : http://pastebin.com/sgPE4Uh1
{
"error":{
"code":500,
"message":"Internal Server Error",
"exception":[
{
"message":"The class 'Symfony\\Component\\HttpFoundation\\File\\UploadedFile' was not found in the chain configured namespaces Acme\\Bundle\\UserBundle\\Entity",
"class":"Doctrine\\Common\\Persistence\\Mapping\\MappingException",
"trace":[
{
"namespace":"",
"short_class":"",
"class":"",
"type":"",
"function":"",
"file":"D:\\xampp\\htdocs\\RestApi\\vendor\\doctrine\\common\\lib\\Doctrine\\Common\\Persistence\\Mapping\\MappingException.php",
"line":37,
"args":[
]
}]
}
}
}
I really don't know the origin of the error, On my config.yml the doctrine mapping is set to auto
Thank you in advance for any help
EDIT 1
I have completely refactored the code with this scenario :
The upload request is now independant, it has it own isolated request with a Content-Type set to multipart/form-data
This time it would not have any problem, but..the same error is still here :
{
code: 500
message: "The class 'Symfony\Component\HttpFoundation\File\UploadedFile' was not found in the chain configured namespaces Sowq\Bundle\UserBundle\Entity"
}
I am wondering if that's not a bug or a compatibility issue between Symfony 2.7 and the doctrine Uploadable extension, any idea?
I think the problem is that you or (uploadableManager extension - I don't know this) try set an object file (of type "UploadedFile Class")
$Avatar is a file object if you want save to DataBase, you need a datatype blob.
Usually, before save the file on File System and then save $Avatar on DB, where $Avatar is just file path or file name o link to File System resource
see https://github.com/Atlantic18/DoctrineExtensions/issues/1353

How to set a default value in a Symfony 2 form field?

I've been trying to set up a form with Symfony 2.
So I followed the tutorial and I've created a special class for creating the form and handling the validation process outside the controller (as shown in the documentation)
But now I need to fill in a field automatically, I've heard that I have to do it in the ProductType.php, where the form (for my product) is created.
But I don't know how to do, here is my buildForm function in ProductType.php :
class QuotesType extends AbstractType
{
private $id;
public function __construct($id){
$this->product_id = $id;
}
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('user_name', 'text')
->add('user_lastname', 'text')
->add('user_email', 'email')
->add('user_comments', 'textarea')
->add('user_product_id', 'hidden', array(
'data' => $this->product_id,
));
;
}
and it obviously doesnt work since I got a SQL error saying that my field is null.
How can I put a default value to the user_product_id ? should I do it directly to the object ?
EDIT:
Here is a part of the code of my entity :
namespace QN\MainBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* QN\MainBundle\Entity\Quotes
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="QN\MainBundle\Entity\QuotesRepository")
*/
class Quotes
{
public function __construct($p_id)
{
$this->date = new \Datetime('today');
}
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer $user_product_id
*
* #ORM\Column(name="user_product_id", type="integer")
*/
private $user_product_id = "1";
/**
* #var datetime $date
*
* #ORM\Column(name="date", type="datetime")
*/
private $date;
And my controller :
public function requestAction($id)
{
$repository = $this->getDoctrine()
->getEntityManager()
->getRepository('QNMainBundle:Categories');
$categories = $repository->findAll();
$quote = new Quotes($id);
$form = $this->createForm(new QuotesType(), $quote);
$formHandler = new QuotesHandler($form, $this->get('request'), $this->getDoctrine()->getEntityManager());
if( $formHandler->process() )
{
return $this->redirect( $this->generateUrl('QNMain_Product', array('id' => $id)) );
}
return $this->render('QNMainBundle:Main:requestaform.html.twig', array(
'categories' => $categories,
'id' => $id,
'form' => $form->createView(),
));
}
My Handler :
namespace QN\MainBundle\Form;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManager;
use QN\MainBundle\Entity\Quotes;
class QuotesHandler
{
protected $form;
protected $request;
protected $em;
public function __construct(Form $form, Request $request, EntityManager $em)
{
$this->form = $form;
$this->request = $request;
$this->em = $em;
}
public function process()
{
if( $this->request->getMethod() == 'POST' )
{
$this->form->bindRequest($this->request);
if( $this->form->isValid() )
{
$this->onSuccess($this->form->getData());
return true;
}
}
return false;
}
public function onSuccess(Quotes $quote)
{
$this->em->persist($quote);
$this->em->flush();
}
}
I've also put here the Date I try to set up in the entity, I might do something wrong in both case since I can't make it work neither ..Date is not in the buildForm function, I don't know if I should ..
Another way is creating a Form Type Extension:
namespace App\Form\Extension;
// ...
class DefaultValueTypeExtension extends AbstractTypeExtension
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
if (null !== $default = $options['default']) {
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
static function (FormEvent $event) use ($default) {
if (null === $event->getData()) {
$event->setData($default);
}
}
);
}
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefault('default', null);
}
public static function getExtendedTypes(): iterable
{
yield FormType::class;
}
}
Now any possible value can be passed as default to any form field:
$form->add('user', null, ['default' => $this->getUser()]);
$form->add('user_product_id', null, ['default' => 1]);
This method is specially useful when you don't have a chance to hook into the initialization process of the bound object.
What you're trying to do here is creating a security hole: anyone would be able to inject any ID in the user_product_id field and dupe you application. Not mentioning that it's useless to render a field and to not show it.
You can set a default value to user_product_id in your entity:
/**
* #ORM\Annotations...
*/
private $user_product_id = 9000;

Form won't save in Symfony2

I have want to save a form in Symfony2, based on a form type, but the save() method is not found.
The error message is:
Fatal error: Call to undefined method Symfony\Component\Form\Form::save() in C:\xampp\htdocs\Xq\src\Xq\LogBundle\Controller\LogController.php on line 44
The controller that invokes the save method looks as follows:
<?php
namespace Xq\LogBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityRepository;
use Xq\LogBundle\Entity\Call;
use Xq\LogBundle\Form\Type\CallType;
class LogController extends Controller
{
public function callAction(Request $request)
{
#create call object
$call = new Call();
$now = new \DateTime("now");
$call->setTimestamp($now);
$call_form = $this->createForm(new CallType(), $call);
#check form input
$request = $this->get('request');
if ($request->getMethod() == 'POST')
{
$call_form->bindRequest($request);
if ($call_form->isValid())
{
**$saved_call = $call_form->save();**
}
}
return $this->render('XqLogBundle:log:call.html.twig', array('call_form'=>$call_form->createView()));
}
}
?>
The CallType is defined as below:
<?php
namespace Xq\LogBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
class CallType extends AbstractType
{
public function buildForm(Formbuilder $builder, array $options)
{
//here all fields are defined, and they are rendered fine
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Xq\LogBundle\Entity\Call');
}
public function getName()
{
return 'callform';
}
}
?>
And finally there is an entity class "Call" that works fine as well:
<?php
#ORM mapping of a call
namespace Xq\LogBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
*
* Xq\LogBundle\Entity\Call
*
* #ORM\Table(name="calls")
* #ORM\Entity
*/
class Call
{
#id of the call
/**
* #ORM\Id
* #ORM\Column(type="integer", length="7")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
#caller
/**
* #ORM\Column(name="caller", type="integer", length="3", nullable="false")
* #ORM\OneToOne(targetEntity="caller")
* #Assert\type(type="Xq\LogBundle\Entity\Caller")
*/
protected $caller;
// and so on ....
#getters and setters
/**
* Get id
*
* #return integer $id
*/
public function getId()
{
return $this->id;
}
// and so on...
}
Does anybody know why the save method is not found? The bind() method does not trigger an error, so there must be a valid form object I guess.
Forms are not responsible for persisting objects; they are responsible for outputting form fields with values from an object and putting user input into that object on form submit.
Use Doctrine to persist your objects. Here is the code snippet from the Forms and Doctrine section, adapted for your example:
if ($call_form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($call);
$em->flush();
}