I have the AppBundle\Entity\Comments entity which I want to persist to the database when the user fill the form I can't find information exactly how to save the form data to the database:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="Comments")
*/
class Comments
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Employer")
* #ORM\JoinColumn(name="employer_id", referencedColumnName="id")
*/
private $employer;
/**
* #ORM\ManyToOne(targetEntity="Application\Sonata\UserBundle\Entity\User")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id")
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="Commenttypes")
* #ORM\JoinColumn(name="comment_id", referencedColumnName="id")
*/
private $comment;
/**
* #ORM\Column(type="date")
*/
private $from_date;
/**
* #ORM\Column(type="date")
*/
private $to_date;
?>
Here is the Form Type:
<?
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class WorkerType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('comment', 'entity', array(
'class' => 'AppBundle\Entity\CommentTypes',
'label' => 'Comment:'
))
->add('from_date', 'Symfony\Component\Form\Extension\Core\Type\DateType', array(
'label' => 'From date:',
'input' => 'datetime',
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
'attr' => [ 'class' => 'datepicker' ]
))
->add('to_date', 'Symfony\Component\Form\Extension\Core\Type\DateType', array(
'label' => 'To date:',
'input' => 'datetime',
'widget' => 'single_text',
'format' => 'dd-MM-yyyy',
'attr' => [ 'class' => 'datepicker' ]
))
->add('submit', 'Symfony\Component\Form\Extension\Core\Type\SubmitType', array(
'label' => 'Submit',
'attr' => [ 'class' => 'buttonColor' ]
));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Comments',
));
}
}
?>
So by now, I'm receiving the data for the fields $from_date and $to_date and $comment (the ID of the comment from the dropdown) from the Form, what I need to to in my controller in order to persist the comment object to the database. Here is my controller:
<?php
namespace AppBundle\Controller;
use AppBundle\Entity\Comments;
use AppBundle\Form\Type\WorkerType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class Worker extends Controller
{
/**
* #Route("/worker/{id}", name="worker")
*/
public function indexAction($id, Request $request)
{
$comment = new Comments();
$form = $this->createForm('AppBundle\Form\Type\WorkerType', $comment);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
{{{{ CODE HERE }}}}
}
return $this->render('default/worker.html.twig',array(
'form' => $form->createView()
));
}
}
I exactly want to know what I need to do to persist the comment object if the form is valid, I'm trying with this:
if ($form->isSubmitted() && $form->isValid()) {
$comment->setEmployer($id);
$comment->setCompany($this->getUser());
$comment->setComment($form->get('comment')->getData());
$comment->setfrom_date($form->get('from_date')->getData());
$comment->setto_date($form->get('to_date')->getData());
}
But I get this error:
Catchable Fatal Error: Argument 1 passed to AppBundle\Entity\Comments::setEmployer() must be an instance of AppBundle\Entity\Employer, string given, called in /home1/rabotnici/src/AppBundle/Controller/Worker.php on line 42 and defined
Related
I have a document
/**
* #ODM\Document
*/
class Result
{
/**
* #var int $id
* #ODM\Id
*/
protected $id;
/**
* #var string $name
* #ODM\Field(type="string")
*/
protected $name;
/**
* #var UserComment[] $userComments
* #ODM\EmbedMany(targetDocument="UserComment")
*/
protected $userComments;
}
/** #ODM\EmbeddedDocument() */
class UserComment {
public $addedBy;
public $createdAt;
public $comment;
}
I want to create a form which allows me to add new user comments. But it would only have the $comment as TextAreaType. The other 2 fields should be added automatically.
I have used the collectiontype like this:
class ResultForm extends AbstractType
{
/**
* buildForm
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('userComments', CollectionType::class, [
'entry_type' => UserCommentType::class,
'required' => false,
'allow_add' => true,
'label' => false,
'delete_empty' => true,
'prototype' => true,
'entry_options' => [
'attr' => [
'class' => 'user-comment-widget'
],
'label' => false,
]
]);
$builder->add('submit', SubmitType::class);
}
And added also a UserCommentType:
class UserCommentType extends AbstractType
{
/**
* #var TokenStorage
*/
private $tokenStorage;
public function __construct(TokenStorage $tokenStorage)
{
$this->tokenStorage = $tokenStorage;
}
/**
* buildForm
* #param FormBuilderInterface $builder
* #param array $options
* #throws \Exception
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$now = new \DateTime('now');
$builder->add('comment', TextareaType::class, [
'label' => false,
])
->add('addedBy', HiddenType::class, [
'data' => $this->tokenStorage->getToken()->getUser()->getUsername(),
])
->add('createdAt', HiddenType::class, [
'data' => $now->format('Y-m-d H:i:s')
]);
}
After having a bunch of exception/errors etc I figured there must be an easier way to do this. This does not work as the UserCommentType has no access to the original data for some reason even though I use this to initialize the ResultForm:
$form = $this->createForm(ResultForm::class, $result);
To access the values inside a collection form, you have to use listeners. By adding an PRE_SET_DATA listener to the form, you could change those values.
Documentation
$builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData'));
Now the function called by the listener. This will iterate through each element in the collection, so you can add your code there.
public function onPreSetData(FormEvent $event) {
$entity = $event->getData();
$form = $event->getForm();
if ($entity) {
$form->add('comment', TextareaType::class, [
'label' => false,
])
->add('addedBy', HiddenType::class, [
'data' => $this->tokenStorage->getToken()->getUser()->getUsername(),
])
->add('createdAt', HiddenType::class, [
'data' => $now->format('Y-m-d H:i:s')
]);
}
}
Make sure to add the if clause, if required, as the first iteration will be to create the collection form prototype and you might don't want it to have presetted values.
In a Symfony 4 application that I've been asked to work on I am attempting to enforce a uniqueness constraint on the name of my program (a course of instruction, not software) within a given company. Despite the attempted constraint, the app happily lets me create a program with the same name as one that already exists in the given company.
I've found various contradictory examples of how to set up a composite constraint, and I've read through the many StackOverflow questions on this topic to no avail.
The relevant code for my entity, program.php:
<?php
namespace Domain\CoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Domain\AdminBundle\Service\Helper\RouteListHelper;
use Domain\CoreBundle\Repository\ProgramRepository as ProgramRepo;
use Gedmo\Mapping\Annotation as Gedmo;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use JsonSerializable;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* Program
* #ORM\Entity(repositoryClass="Domain\CoreBundle\Repository\ProgramRepository")
* #ORM\Table(name="programs")
* #UniqueEntity(
* fields={"name","company"},
* errorPath = "name",
* message="A program by that name already exists for this company."
* )
* #ORM\HasLifecycleCallbacks()
*/
class Program implements JsonSerializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #Assert\NotBlank(message="Program Name should not be empty")
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Company")
* #ORM\JoinColumn(name="company_id", referencedColumnName="id", nullable=false, onDelete="CASCADE")
*/
protected $company;
...
and my addProgramType.php:
<?php
namespace Domain\AdminBundle\Form;
use Domain\CoreBundle\Repository\UserRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
/**
* Class AddProgramType
*/
class AddProgramType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$params = array(
'name' => array(
'label' => 'Program name:',
'attr' => array('class' => 'base-box'),
),
'isEnabled' => array(
'label' => false,
'attr' => array(
'checked' => 'checked',
),
),
'isRoiCalculating' => array(
'label' => false,
),
'duration' => array(
'label' => 'Duration:',
'class' => 'DomainCoreBundle:Duration',
'query_builder' => function (EntityRepository $er) use ($options) {
return $er->getDurationsQb($options['company']);
},
'choice_label' => 'uniqueName',
'attr' => array(
'class' => 'base-box',
),
),
'sessionTypes' => array(
'class' => 'DomainCoreBundle:SessionType',
'query_builder' => function (EntityRepository $er) use($options) {
return $er->getAllSessionTypesQb($options['company']);
},
'choice_label' => 'name',
'multiple' => true,
'label' => 'Session Types:',
'attr' => array(
'class' => 'multiselect-dropdown multiselect-dropdown-session-types',
'required' => 'required',
'multiple' => 'multiple',
),
),
'users' => array(
'required' => false,
'class' => 'DomainCoreBundle:User',
'choices' => $options['userRepo']->findByRoles(
array(UserRepository::ROLE_ADMIN,UserRepository::ROLE_COMPANY_ADMIN),
$options['company'],
false),
'choice_label' => 'getFullName',
'multiple' => true,
'label' => 'Access to admins:',
'attr' => array(
'class' => 'multiselect-dropdown multiselect-dropdown-users',
'multiple' => 'multiple',
),
),
);
$builder
->add('name', null, $params['name'])
->add('isEnabled', CheckboxType::class, $params['isEnabled'])
->add('isRoiCalculating', CheckboxType::class, $params['isRoiCalculating'])
->add('duration', EntityType::class, $params['duration'])
->add('sessionTypes', EntityType::class, $params['sessionTypes'])
->add('users', EntityType::class, $params['users']);
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array('data_class' => 'Domain\CoreBundle\Entity\Program'));
$resolver->setRequired(array('company', 'userRepo'));
}
/**
* Return form name
*
* #return string
*/
public function getBlockPrefix()
{
return 'add_program';
}
}
While the application enforces the NotBlank constraint on the name correctly, it doesn't enforce the uniqueness of name + company.
Any suggestions?
[UPDATE] Looks like I set company after the isValid() call, thanks BoShurik for the catch. Here's the relevant controller code showing my mistake:
/**
* Add new program
*
* #param Request $request
*
* #return Response
*/
public function addNewAction(Request $request)
{
$form = $this->createForm(AddProgramType::class, null, array('company'=>$this->getCurrentCompany(),
'userRepo' =>$this->em->getRepository('DomainCoreBundle:User')));
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$company = $this->getCurrentCompany();
$program = $form->getData();
$program->setCreatedDate(new \DateTime());
$program->setCompany($company);
...
If you want to add the same check at database level you should use the #UniqueConstraint annotation in the Table() declaration and give a name to the new index.
Something like:
/**
* Program
* #ORM\Entity(repositoryClass="Domain\CoreBundle\Repository\ProgramRepository")
* #ORM\Table(name="programs", uniqueConstraints={#ORM\UniqueConstraint(name="IDX_PROGRAM_COMPANY", columns={"name", "company_id"})})
* #UniqueEntity(
* fields={"name","company"},
* errorPath = "name",
* message="A program by that name already exists for this company."
* )
* #ORM\HasLifecycleCallbacks()
*/
class Program implements JsonSerializable
```
As a company field is not manager by your form, you need to set its value before form validation:
public function addNewAction(Request $request)
{
$program = new Program();
$program->setCompany($this->getCurrentCompany());
$form = $this->createForm(AddProgramType::class, $program, array('company' => $this->getCurrentCompany(),
'userRepo' => $this->em->getRepository('DomainCoreBundle:User')));
if ($request->getMethod() === 'POST') {
$form->handleRequest($request);
if ($form->isValid()) {
$program = $form->getData();
$program->setCreatedDate(new \DateTime());
}
}
}
Got some question about passing custom variables to a custom FormType
I have a custom FormType named KontoType:
I pass some custom variables to it and this works like as expected, if i override the method buildForm and dump the passed $options array, the mandant exists and is a entity.
But how the heck can i now pass this custom variable to the function getChoices() which loads the choices based on the mandant in this custom FormType?
Even if i did reset the $options in the override buildForm function like $options['choices'] = $this->getChoices($options['mandant']) the select box is empty if i render this form.
<?php
namespace App\Form\Type;
use App\Entity\Core\Finanzen\Konto;
use App\Entity\Core\Organisation\Mandant;
use App\Services\LocaleService;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Translation\TranslatorInterface;
class KontoType extends AbstractType
{
/**
* #var ObjectManager
*/
private $manager;
/**
* #var TranslatorInterface
*/
private $translator;
/**
* #var LocaleService
*/
private $localeService;
public function __construct(ObjectManager $manager, TranslatorInterface $translator, LocaleService $localeService)
{
$this->manager = $manager;
$this->translator = $translator;
$this->localeService = $localeService;
}
private function getChoices(Mandant $mandant=null)
{
return $this->manager->getRepository(Konto::class)->findBuchbar(true, $mandant);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'label' => 'konto.name',
'help' => 'konto.help',
'choices' => null,
'attr' => array(
'class' => 'bs-select',
'aria-hidden' => 'true',
'ref' => 'input',
'multiple' => false,
'tabindex' => 1,
'data-live-search' => true,
'data-size' => 6
),
'choice_label' => function ($choiceValue, $key, $value) {
return $choiceValue->getKonto()." ".$this->localeService->doTranslate($choiceValue);
},
'choice_value' => function(Konto $konto = null) {
return $konto ? $konto->getId() : '' ;
},
'required' => true,
'multiple' => false,
'empty_data' => null,
'label_attr' => array(
'class' => 'control-label'
),
'placeholder' => 'message.bitte wählen',
'mandant' => null
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$options['choices'] = $this->getChoices($options['mandant']);
parent::buildForm($builder, $options); // TODO: Change the autogenerated stub
}
public function getParent() {
return ChoiceType::class;
}
}
I use symfony 3.4 and easycorp/easyadmin-bundle 1.17
This is my class "Quotation", the "artisan" field is an "under registration" of the "Person" entity (person.type = 1) :
class Quotation
{
/** others fields... */
/**
* 1 => 'artisan', 2 => 'customer'
*/
private $type;
/**
* #ORM\ManyToOne(targetEntity="Person", inversedBy="artisanQuotations", cascade= { "persist" })
* #ORM\JoinColumn(name="artisan_id", referencedColumnName="id")
*/
private $artisan;
/** getters and setters ... */
I have a problem with a form field using a custom field type
form:
fields:
...
- { property: 'artisan', label: '', type: 'AppBundle\Form\Field\ArtisanType' }
I created this form field type to be able to filter the list thanks to the query_builder :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('artisan', EntityType::class, array(
'class' => 'AppBundle:Person',
'label' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('person')
->where('person.type = 1');
},
'attr' => array('data-widget' => 'select2'),
'multiple' => false,
'expanded'=> false
));
}
my form is displayed perfectly well but when i submit this form i have an error :
Expected argument of type "AppBundle\Entity\Person", "array" given
Thank you in advance for your help
Instead of using the buildForm method you should use configureOptions here. That way your form is not extended by another subform which results in the array.
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Form\AbstractType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ArtisanType extends AbstractType
{
/**
* #param \Symfony\Component\OptionsResolver\OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => 'AppBundle:Person',
'label' => false,
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('person')
->where('person.type = 1');
},
'attr' => array('data-widget' => 'select2'),
'multiple' => false,
'expanded'=> false,
]);
}
/**
* #return string|null
*/
public function getParent()
{
return EntityType::class;
}
}
I have everything configured as in documentation and everything works perfect if I upload all files.
But when I want change only other elements in form without changing photo i have received following error message:
You must pass an instance of FileInfoInterface or a valid array for entity of class
public function updateAction(Request $request, $id)
{
$em = $this->getDoctrine()->getManager();
$entity = $em->getRepository('CmUserBundle:User')->find($id);
if (!$entity) {
throw $this->createNotFoundException('Unable to find User entity.');
}
$deleteForm = $this->createDeleteForm($id);
$editForm = $this->createEditForm($entity);
$editForm->handleRequest($request);
$container = $this->container;
if ($editForm->isValid()) {
$uploadableManager = $this->get('stof_doctrine_extensions.uploadable.manager');
$uploadableManager->markEntityToUpload($entity, $entity->getPath());
$em->flush();
return $this->redirect($this->generateUrl('user_edit', array('id' => $id)));
}
return array(
'entity' => $entity,
'edit_form' => $editForm->createView(),
'delete_form' => $deleteForm->createView(),
);
}
Form clas:
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class UserType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', null, array('attr' => array('class' => 'form-control')))
->add('username', null, array('attr' => array('class' => 'form-control')))
->add('path', 'file', array(
'data_class' => null
))
->add('firstName', null, array('attr' => array('class' => 'form-control')))
->add('lastName', null, array('attr' => array('class' => 'form-control')))
->add('localization', null, array('attr' => array('class' => 'form-control')))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'CmUserBundle\Entity\User',
));
}
/**
* #return string
*/
public function getName()
{
return 'appbundle_user';
}
}
How to prevent from update entity by empty input fields? during editForm->handleRequest($request);
Any ideas?
Try $form->submit($request->request->all(), false) instead of $form->handleRequest($request). This will prevent from clearing entity's properties which are not present in incoming POST data.