Form won't save in Symfony2 - forms

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();
}

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.

Symfony 2.8 form entity type custom property

I'm working on a form in a Symfony 2.8 application.
I have an entity Object and that entity can have one or more SubObjects.
Those SubObjects are identified by the property id, but also by the property key.
By default the value from the id property is used in the HTML (subObject.__toString()). I want to use the property key in the .
I can't seem to find how to do this...
PS: I can't use the __toString() method of the SubObject, because that's already in use for some other purposes...
Ideas would be greatly appreciated.
<?php
namespace My\Bundle\ObjectBundle\Form\Type\Object;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
class ObjectType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('code', TextType::class, [
'required' => true,
])
->add('subObjects', EntityType::class, [
'class' => 'My\Bundle\ObjectBundle\Entity\SubObject',
'multiple' => true,
])
}
}
I chucked up a quick pseudocode of how I'd do this in a listener, hopefully I understood what you're after. It's a general approach anyway.
class ResolveSubObjectSubscriber implements EventSubscriberInterface {
/** #var EntityManager */
private $entityManager;
public function __construct(FormFactoryInterface $factory, EntityManager $entityManager) {
$this->factory = $factory;
$this->entityManager = $entityManager;
}
public static function getSubscribedEvents() {
return array(FormEvents::POST_SET_DATA => 'resolveSubObject');
}
/**
* Resolve sub objects based on key
*
* #param FormEvent $event
*/
public function resolveSubObject(FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
// don't care if it's not a sub object
if (!$data instanceof SubObject) {
return;
}
/** #var SubObject $subObject */
$subObject = $data;
// here you do whatever you need to do to populate the object into the sub object based on key
$subObjectByKey = $this->entityManager->getRepository('SomeRepository')->findMySubObject($subObject->getKey());
$subObject->setObject($subObjectByKey);
}
}

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

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?

Symfony2 embed form with file into another form

I have an Item entity and an Image entity, which a OneToOne relation between them.
I also have an ItemType and ImageType forms.
Until now when I have a situation like this one I use the two forms separately (rendering them into a single html form) and setting the relation inside the controller or the form handler. Is there a -symfony- way to embed the ImageType form into the ItemType one?
A little code maybe can help.
Item:
namespace Company\ItemBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use NewZaarly\ImageBundle\Entity\Image;
/**
* #ORM\Table()
* #ORM\Entity()
*/
class Item
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $title;
/**
* #var \NewZaarly\ImageBundle\Entity\ImageItem
* #ORM\OneToOne(targetEntity="NewZaarly\ImageBundle\Entity\ImageItem", mappedBy="item", cascade={"persist", "merge", "remove"})
*/
private $image;
//other fields. Setters and getters
}
Image:
<?php
namespace Company\ImageBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Table()
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks
*/
class Image
{
/**
* #Assert\File(maxSize = "2M", mimeTypes = {"image/png", "image/jpg", "image/jpeg"})
*/
protected $file;
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="image", type="string", length=255, nullable=true)
*/
protected $image;
//other fields, setter and getters
}
ImageType
<?php
namespace Company\ImageBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class ImageType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('file', 'file', array('required' => true));
}
public function getName()
{
return 'image';
}
}
And the ItemType form
<?php
namespace Company\ItemBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Company\ImageBundle\Form\Type\ImageType;
class ItemType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title', 'text');
$builder->add('image', new ImageType()); // <-- this is what I'd like to do
}
public function getDefaultOptions(array $options)
{
return array('data_class' => 'Company\ItemBundle\Entity\Item');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array('data_class' => 'Company\ItemBundle\Entity\Item'));
}
public function getName()
{
return 'item';
}
}
Doing it like this I get an error when binding the request because the image is an array an not an Image object, what is expected in the setImage method in the Item class.
My handle function is pretty simple:
public function handle(FormInterface $form, Request $request)
{
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$item = $form->getData();
$this->itemManager->saveItem($item);
return true;
}
}
return false;
}
Any idea? I don't like the first way I specified for doing it.
I had the same problem and I solved by adding data_class to embedded field, in my case I define the upload form as service (app_document_upload)
//Document Class
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//other fields here ...
->add('image', 'app_document_upload', array(
'data_class' => 'App\DocumentBundle\Entity\Document'
))
}

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;