I have a problem. At the beginning, this is my action which create form:
public function wyswietlDostepneTerminyAction($kategoria) {
$zlecenie = new Zlecenia();
$form = $this->createForm(new ZleceniaAddType(), $zlecenie);
return array ('form' => $form->createView(), 'kategoria'=>$kategoria);
}
'Zlecenie' object has 'Kategoria' field of 'Kategorie' type (its from relation).
Method which persist entity:
public function noweZlecenieAction(Request $request) {
$entity = new Zlecenia();
$form = $this->createForm(new ZleceniaAddType(), $entity);
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('pokaz-zlecenie', array('id' => $entity->getId())));
}
return array(
'entity' => $entity,
'form' => $form->createView(),
);
}
And form class:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('opis')
->add('klient', new KlientType())
//->add('kategoria')
;
}
Normally, I can add field like:
->add('kategoria','entity', array('class'=>'Acme\MyBundle\Entity\Zlecenia')
and select Kategoria from list.
But the problem is: I don't want to choose Kategoria from select list or checkbox list. I want to use predefined $kategoria object. Of course (if must exist) 'Kategoria' field must be hidden. How can I do that?
You can create a data transformer that will transform the data entered by the user in a form into something else.
In your case you will transform a Kategoria ID submited by the user into Kategoria object. The best option for you here is define that your "Kategoria" property in your form will be of hidden form type.
When the form is rendered you will have a input hidden that will store the ID of your Kategoria object. When the form is submited the transformer will reverse that ID to the correspondent Kategoria Object. As you want to define a default Kategoria, in your controller after you create the Klient object you should set the Kategoria.
If you follow this source http://symfony.com/doc/master/cookbook/form/data_transformers.html , everything will went fine. Any questions just say
Try this, tweaking to your directory structure: Formtype:
public function buildForm( FormBuilderInterface $builder, array $options ) {
if ( isset( $options['attr']['kategoria'] ) ) {
$kategoria = $options['attr']['kategoria'];
}
else {$kategoria=null;}
$builder
->add( 'opis' )
->add( 'klient', new KlientType() );
if ( $kategoria ) {
$transformer = new KategoriaTransformer( $em );
$builder->add(
$builder->create( 'kategoria'
->add( 'kategoria', 'hidden', array()
)
)
->addModelTransformer( $transformer );
} else {
->add( 'kategoria', 'entity'
, array( 'class'=>'Acme\MyBundle\Entity\Zlecenia' )
}
;
}
And the transformer:
namespace Acme\MyBundle\Entity\Zlecenia\Transformer;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class kategoriaTransformer implements DataTransformerInterface
{
/**
* #var ObjectManager
*/
private $em;
/**
* #param ObjectManager $em
*/
public function __construct(ObjectManager $em)
{
$this->em = $em;
}
/**
* Transforms an object (kategoria) to a string (id).
*
* #param Issue|null $kategoria
* #return string
*/
public function transform($kategoria)
{
if (null === $kategoria) {return "";}
if (is_object($kategoria) &&
method_exists($kategoria, "toArray")){
$kategoria=$kategoria->map(function ($ob){
return $ob->getId();});
return implode(",",$kategoria->toArray());
}
return $kategoria->getId();
}
/**
* Transforms a string (id) to an object (kategoria).
*
* #param string $id
* #return Issue|null
* #throws TransformationFailedException
* if object (kategoria) is not found.
*/
public function reverseTransform($id)
{
if (!$id) {
if($this->multi) {return array();}
return null;
}
if (strpos($id,',') !== false) {
$id=explode(',',$id);
}
$qb=$this->em->getRepository(
'Acme\MyBundle\Entity\Zlecenia\Repository\kategoria'
)
->createQueryBuilder('k');
$qb->andWhere($qb->expr()->in('i.id', $id));
if (is_array($id) || $this->multi){
$kategoria=$qb->getQuery()
->getResult();
} else {
$kategoria=$qb->getQuery()
->getSingleResult();
}
if (null === $kategoria) {
throw new TransformationFailedException(sprintf(
'A kategoria with id "%s" does not exist!',
$id
));
}
return $kategoria;
}
}
Related
I am registering a doctor in my api through my symfony controller. And for that I created a form for the doctor, and then I have to persist these data in my database. And that's where I can not get the data from the form, that's what I tried on my postman, but it returns me a 500 error saying:
Child \"username\" does not exist.
I'm trying to recover my form data through this query:
$user->setUsername($form['username']);
Controller
/**
* #Route("/api/inscription/medecin")
* #Rest\View(statusCode=Response::HTTP_CREATED)
* #Method("POST")
*/
public function postDocAction(Request $request){
$medecin = new Medecin();
$user = new User();
$user->setSalt('');
$form = $this->createForm('Doctix\MedecinBundle\Form\MedecinType', $medecin);
$form->submit($request->request->all()); // Validation des données
if ($form->isValid()){
$encoder = $this->get('security.password_encoder');
$encoded = $encoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($encoded);
$user->setUsername($form['username']);
$user->setRoles(array('ROLE_MEDECIN'));
$user->setNom($form['nom']);
$user->setPrenom($form['prenom']);
$user->setNumTel($form['numTel']);
$user->setAdresse($form['adresse']);
$medecin->setUser($user);
$medecin->setSexe($form['sexe']);
$medecin->setQuartier($form['quartier']);
$medecin->setNumOrdre($form['numordre']);
$medecin->setSpecialite($form['specialite']);
$medecin->setClinique($form['clinique']);
$em = $this->getDoctrine()->getManager();
$em->persist($medecin);
$em->flush();
}
}
Form
class MedecinType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user',UserType::class)
->add('sexe', TextType::class)
->add('specialite',SpecialiteType::class)
->add('clinique',CliniqueType::class)
->add('quartier', TextType::class)
->add('numordre', TextType::class)
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Doctix\MedecinBundle\Entity\Medecin',
'csrf_protection' => false
));
}
}
Test in Postman
After you do $form->handleRequest($request);,
$form->getData() holds the submitted values.
But, the original $medecin variable has also been updated. So non need to set all the fields again.
https://symfony.com/doc/current/forms.html#handling-form-submissions
short answer :
According to the json, user seems to be an object so to access username, you should probably write:
$form["user"]["username"];
You should apply the same logic to all elements inside user.
complete answer :
There are multiple incoherences in your code. The advantage of passing the object into the createForm() method, is that the object will automaticaly be updated once the form is submited and valid. This avoid you to go throught all the setters later as you are doing now.
So if you want the user object to be correcly updated into the medecin's object, you should set the user before creating the form :
<?php
/**
* #Route("/api/inscription/medecin")
* #Rest\View(statusCode=Response::HTTP_CREATED)
* #Method("POST")
*/
public function postDocAction(Request $request){
$medecin = new Medecin();
$user = new User();
$user->setSalt('');
$user->setRoles(['ROLE_MEDECIN']);
$medecin->setUser($user);
$form = $this->createForm('Doctix\MedecinBundle\Form\MedecinType', $medecin);
$form->submit($request->request->all()); // Validation des données
if ($form->isValid()) {
$encoder = $this->get('security.password_encoder');
$encoded = $encoder->encodePassword($user, $user->getPlainPassword());
$user->setPassword($encoded);
$em = $this->getDoctrine()->getManager();
$em->persist($user); // Safety, as i don't know how you configured your entities
$em->persist($medecin);
$em->flush();
}
}
I learned Symfony 3 and I want create form class to upload File, so i created ImageType, cutom form type to handle image uploaded in NewsType (form with some description and this field):
class ImageType extends AbstractType
{
private $path;
public function __construct($path)
{
$this->path = $path;
}
public function getParent()
{
return FileType::class;
}
public function getName()
{
return 'image';
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'image_name' => ''
));
}
/**
* #param FormView $view
* #param FormInterface $form
* #param array $options
*/
public function buildView(FormView $view, FormInterface $form, array $options)
{
$view->vars['image_name'] = $options['image_name'];
}
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->setAttribute('image_name', $options['image_name'])
->addModelTransformer(new ImageTransformer($this->path))
}
}
I use ImageTransformer to transform file name like 124324235342.jpg to instance File class. Form work fine when i created and saved date to database, but how manage entity in edit mode ?
public function editAction(Request $request, News $news)
{
$path = $this->getParameter('upload_directory') . $news->getImage();
$image = $news->getImage();
$form = $this->createForm(NewsType::class, $news, ['image_name' => $image]);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid())
{
$this->get('app.image_uploader')->uploadNews($news, $image);
$em = $this->getDoctrine()->getManager();
$em->persist($news);
$em->flush();
return $this->redirectToRoute('admin_news_index');
}
return $this->render('admin/news/form.html.twig', [
'form' => $form->createView(),
'news' => $news
]);
}
I want handle case to use same form to edit database entity. I populated form, but when user not upload image I don't want change this field and live old value. How accomplish this ?
The simplest method for your code:
In setter of your News type:
function setImage($image) {
if(strlen($image)) {
$this->image = $image;
}
}
This allow you to do not worry about case of lacking image, when user edits other fields. In this case $this->fileName in News will be not overwritten.
Then in service app.image_uploader you should check if file with given name exist. If not then you should not overwrite this file.
But there are some other poroblems:
1) You should validate extension of file.
2) You should use unique names for your files in your server different than names from users. Yo can use both names, your unique for string files in hard drive, and name form user in user interface, but you should not use name of file form user to store file in your hard drive.
I recommend to read about these problems in docs:
https://symfony.com/doc/current/controller/upload_file.html
Or on stack overflow:
Symfony2 file upload step by step
Having read through the Symfony forms collection/entity types I'm trying to generate a form that only involves these two attributes of an entity (Person) but uses all instances of Person in the database. The purpose of the form is to provide a activation tickbox for every person on a single page so that multiple status' can be flushed to the database on form submission. Eg, the form should look something like:
☑ John Smith
☐ Jane Doe
☑ ...etc
☑ ...etc
My attempts below are not working as they just return an empty page, although I can see that $allpersons is populated
My Person entity:
class Person
{
/**
* #ORM\COLUMN(type="boolean")
*/
protected $active = true;
/**
* #ORM\COLUMN(type="string", length=100)
*/
protected $fullname ;
/* ...many other attributes... */
}
My Controller:
class DefaultController extends BaseController {
public function activePersonsAction(Request $request) {
$em = $this->getDoctrine()->getManager();
$persons = $em->getRepository('AppBundle:Person')->findAll();
$form = $this->createForm(AllPersonsType::class, $persons);
$form->handleRequest($request);
if ($form->isSubmitted() && ($form->isValid())) {
$em = $this->getDoctrine()->getManager();
$persons = $form->getData();
foreach ($persons as $person) {
$em->persist($person);
}
$em->flush();
return $this->redirectToRoute('home_user');
}
return $this->render('activatePersons.html.twig', array(
'page_title' => 'Active Persons',
'form' => $form->createView(),
));
}
}
My FormTypes:
class AllPersonsType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('persons', CollectionType::class, array(
'entry_type' => ActivatePersonType::class
));
}
public function getName()
{
return 'person';
}
}
class ActivatePersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('active',CheckboxType::class)
->add('fullname', TextType::class);
}
public function configureOptions(OptionsResolver $resolver) {
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Person',
));
}
public function getName()
{
return 'person';
}
}
You pass $persons collection as form data to AllPersonsType, while the type expects that you pass an array with persons key and persons collection as value.
$form = $this->createForm(AllPersonsType::class, ['persons' => $persons]);
I tried to call the repository of my entity Category in the class form of my entity BonCommande, but this error ocuured:
Notice: Undefined property: Application\VehiculeBundle\Form\BonCommandeType::$em in C:\wamp\www\Symfony_test\src\Application\VehiculeBundle\Form\BonCommandeType.php line 74
This is my code:
The class BonCommandeType.php:
namespace Application\VehiculeBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
use Application\VehiculeBundle\Entity\Category;
class BonCommandeType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
// Name of the user
$builder->add('observation', 'text');
/* Add additional fields... */
$builder->add('save', 'submit');
// Add listeners
$builder->addEventListener(FormEvents::PRE_SET_DATA, array($this, 'onPreSetData'));
$builder->addEventListener(FormEvents::PRE_SUBMIT, array($this, 'onPreSubmit'));
}
protected function addElements(FormInterface $form, Category $categorie = null) {
// Remove the submit button, we will place this at the end of the form later
$submit = $form->get('save');
$form->remove('save');
// Add the province element
$form->add('categorie', 'entity', array(
'data' => $categorie,
'empty_value' => '-- Choose --',
'class' => 'ApplicationVehiculeBundle:Category',
'property' => 'intitule',
'mapped' => false)
);
// Cities are empty, unless we actually supplied a province
$vehicules = array();
if ($categorie) {
// Fetch the cities from specified province
$repo = $this->em->getRepository('ApplicationVehiculeBundle:Vehicule');
$cities = $repo->findByCategory($categorie);
}
// Add the city element
$form->add('vehicule', 'entity', array(
'empty_value' => '-- Select a categorie first --',
'class' => 'ApplicationVehiculeBundle:Vehicule',
'choices' => $vehicules,
));
// Add submit button again, this time, it's back at the end of the form
$form->add($submit);
}
function onPreSubmit(FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
// Note that the data is not yet hydrated into the entity.
$categorie = $this->em->getRepository('ApplicationVehiculeBundle:Category')->find($data['categorie']);
$this->addElements($form, $categorie);
}
function onPreSetData(FormEvent $event) {
$account = $event->getData();
$form = $event->getForm();
// We might have an empty account (when we insert a new account, for instance)
$categorie = $account->getVehicule() ? $account->getVehicule()->getCategorie() : null;
$this->addElements($form, $categorie);
}
...
}
This is the instruction that causes the error:
$categorie = $this->em->getRepository('ApplicationVehiculeBundle:Category')->find($data['categorie']);
FormComponent is an independent component and it doesn't provide any entityManager to use. You have to inject it or pass it by $options if you want to use it..
In your case it would be correct if you directly pass it to the type's __construct or pass by $options array or declare your type as a service and inject entity manager to it:
class BonCommandeType extends AbstractType
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
...
}
or
$this->createForm(TYPE, DATA, ['em' => $em]);
From your code I assume you are missing this:
//Somewhere at the begging of your BonCommandeType
protected $em;
...
public function __construct(EntityManager $em)
{
$this->em = $em;
}
Keep in mind that when you create a new form object you should use smth like :
BonCommandeType($this->getDoctrine()->getManager()) // if inside a controller
all.
I would like to ask if it is possible to have multiple forms (now per select option) on one page instead of multiple select field.
The situation is: I have User with #ManyToMany bi-relation to Services and 'user_services' relation storage table, but extended with additional fields like min_price, max_price, etc. with UserService Doctrine Entity class.
I think that the better user experience in my particular case is to have a table layout with checkboxes, service names and price fields with one save button, but I can't get how to create multiple forms in which each form corresponds to one option from select list for example and followed by additional fields for this option.
Thanks.
In this case, I think you should use the collection type, which can be used to handle many to many relations.
You want to handle a list of user_services for a given user, if I understand well.
You then have to make something like this:
1, create a UserServiceType that will represent the relation between a user and a service (plus its min_price, max_price, etc):
class UserServiceType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('min_price', 'currency')
->add('max_price', 'currency')
;
}
public function getDefaultOptions()
{
return array('data_class' => 'Entity\UserService');
}
}
2, register it as a form_type:
// app/config/config.yml (for example)
services:
user_service_type:
class: UserServiceType
tags:
- { name: form.type, alias: user_service }
3, configure your UserType to handle this collection:
class UserType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder
->add('services', 'collection', array(
'type' => 'user_service', // this is the id of the form type registered above
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
))
;
}
public function getDefaultOptions()
{
return array('data_class' => 'Entity\User');
}
}
Imagine that you have a bunch of services and you want to display them
What would you do?
My Solution:
Controller:
class DefaultController extends Controller
{
public function indexAction(Request $request)
{
/** #var $em \Doctrine\ORM\EntityManager */
$em = $this->get('doctrine.orm.entity_manager');
$services = $em->getRepository('ThestudioscheduleProfileBundlesServiceBundle:Service')->findAll();
$user = $this->get('security.context')->getToken()->getUser();
$form = $this->createForm(new UserServiceType($services, $user->getServiceDetails()));
if ('POST' == $request->getMethod()) {
$form->bindRequest($request);
if ($form->isValid()) {
$data = $form->getData();
$em->getConnection()->beginTransaction();
try {
foreach ($data['services'] as $serviceDetails) {
// if service is selected
if ($serviceDetails['id']) {
$serviceDetails['details']->setUser($user);
$serviceDetails['details']->setService($em->getRepository('ThestudioscheduleProfileBundlesServiceBundle:Service')->find($serviceDetails['service']));
$serviceDetails['details'] = $em->merge($serviceDetails['details']);
} else {
// if the entity is exist but user unchecked it - delete it
if ($serviceDetails['details']->getId()) {
$serviceDetails['details'] = $em->merge($serviceDetails['details']);
$em->remove($serviceDetails['details']);
}
}
}
$em->flush();
$em->getConnection()->commit();
// TODO: display success message to user
return $this->redirect($this->generateUrl('ThestudioscheduleProfileBundlesServiceBundle_homepage'));
} catch (\Exception $e) {
$em->getConnection()->rollback();
$em->close();
var_export($e->getMessage());die;
// TODO: log exception
// TODO: display something to user about error
}
}
}
return $this->render('ThestudioscheduleProfileBundlesServiceBundle:Default:index.html.twig', array(
'form' => $form->createView()
));
}
}
UserServiceType:
class UserServiceType extends AbstractType
{
private $services;
private $userServiceDetails;
public function __construct($services, $userServiceDetails)
{
$this->services = $services;
$this->userServiceDetails = $userServiceDetails;
}
/**
* #param \Symfony\Component\Form\FormBuilder $builder
* #param array $options
* #return void
*/
public function buildForm(FormBuilder $builder, array $options)
{
// all application services
$builder->add('services', 'collection');
foreach ($this->services as $key => $service) {
$serviceType = new ServiceType($service, null);
foreach ($this->userServiceDetails as $details) {
if ($service == $details->getService()) {
$serviceType->setUserServiceDetails($details);
}
}
$builder->get('services')->add('service_' . $key, $serviceType);
}
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'profile_user_service';
}
}
ServiceType:
class ServiceType extends AbstractType
{
private $service;
private $userServiceDetails;
public function __construct($service, $userServiceDetails)
{
$this->service = $service;
$this->userServiceDetails = $userServiceDetails;
}
/**
* #param \Symfony\Component\Form\FormBuilder $builder
* #param array $options
* #return void
*/
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('id', 'checkbox', array(
'label' => $this->service->getName(),
'required' => false))
->add('service', 'hidden')
->add('details', new ServiceDetailsType($this->userServiceDetails));
$values = array('service' => $this->service->getId());
if (null !== $this->userServiceDetails) {
$values = array_merge($values, array('id' => true));
}
$builder->setData($values);
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
public function getName()
{
return 'profile_service';
}
public function setUserServiceDetails($details)
{
$this->userServiceDetails = $details;
}
}
ServiceDetailsType:
class ServiceDetailsType extends AbstractType
{
private $details;
public function __construct($details)
{
$this->details = $details;
}
/**
* #param \Symfony\Component\Form\FormBuilder $builder
* #param array $options
* #return void
*/
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('id', 'hidden')
->add('minPrice', 'money', array('required' => false))
->add('maxPrice', 'money', array('required' => false))
->add('unit', 'choice', array(
'choices' => array(
'hour' => 'Hours',
'photo' => 'Photos'
),
'required' => false
))
->add('unitsAmount', null, array('required' => false));
if (!empty($this->details)) {
$builder->setData($this->details);
}
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Thestudioschedule\ProfileBundles\ServiceBundle\Entity\UserService',
'csrf_protection' => false
);
}
/**
* Returns the name of this type.
*
* #return string The name of this type
*/
function getName()
{
return 'profile_service_details';
}
}
The most amazing thing that all this works! Many thanks to Florian for spending his time on me and for keeping me thinking about the solution and my apologise for unclear question (if it is). I think, Symfony docs should be updated with more different form embedding/collection examples like this.
Cheers,
Dima.