Setup parent entity from child entity form - php

I am using Symfony2 for a PHP project, but I have a question about Entity inheritance.
I have a database model that requires to make the difference between different types of employees.
Here is an extract of the modelisation :
Employee
civility
name
firstname
hired_at
Secretary extends Employee
employee [Employee entity]
roles [another entity]
Seller extends Employee
employee [Employee entity]
section [another entity]
I have to separate it because I have another entity, called Message, that each employees can send to other ones.
Message
author [Employee entity]
recipient [Employee entity]
title
content
sent_at
In my application, I would like to be able to create a new "Secretary" for instance, and set up its "Employee" properties in the same form, rather than creating the Employee entity then link it to the new Secretary one...
What is the proper way to do it with Symfony2 ?
I know that I could add the properties to the form and set the entities manually, but I really think there should be a cleaner way to do that...
Is it possible to use the FormBuilder ?

Quite easy in fact, I did not know it was possible to add another FormType as a field type in Symfony2.
The working way, just in case :
namespace MyAdminBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class SecretaryType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('employee', new EmployeeType(), array('label' => 'Employee', 'required' => true))
->add('password', 'password', array('label' => 'Password', 'required' => true))
;
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'MyCoreBundle\Entity\Secretary'
));
}
/**
* #return string
*/
public function getName()
{
return 'my_adminbundle_secretary';
}
}

Related

Symfony form common fields inheritance/composition

I have 2 entities A and B that share common fields, I used a trait to setup those common fields based on (Doctrine inheritance for entities common fields) because I don't want to use a MappedSuperClass.
Setting up a restful post route for entity B, I instantiate a FormBType which data_class maps to B::class, that extends FormCType (contains common fields and 'data_class' maps to nothing).
I tried to use the inherit_data approach with https://symfony.com/doc/current/form/inherit_data_option.html but I don't want that extra key/nested layer in my form (I want a flattened one).
My problem is that validation for the common fields which are in the trait using Assert aren't taken into account and form passes validation with empty strings.
class B {
use CTrait;
}
//trait that has the common fields with ORM mapping and Assert
trait CTrait {
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string")
* #ORM\Assert\Length(min="2")
*/
private $name;
}
//Common fields formType
class CType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('name');
}
}
//Form using the common fields formType
class BType extends CType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => B::class,
'csrf_protection' => false,
]);
}
}
After further checking on Length I realized empty strings are considered valid values and it was still passing through validation using name: "" even though I had Assert\Length(min=2), after adding NotBlank the validation worked.

Custom choices on form with data_class

I have created a FormType that get's a data_class set in the defaults in the configureOptions class. It looks a bit like this
<?php
namespace tzfrs\AppBundle\Form\Type;
use tzfrs\AppBundle\Model\Car;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class CarType
* #package tzfrs\AppBundle\Form\Type
*/
class CarType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('specification', ChoiceType::class, [
'choices' => [1 => 1, 2 => 2, 3 => 3]
]);
}
/**
* #inheritdoc
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Car::class
]);
}
}
When I now create the form like this
$carForm = $this->createForm(CarType::class, $car);
and want to use it I get the following error:
The value of type "object" cannot be converted to a valid array key.
I think the reason for this, is because my Car class has a property specification with getters/setters and therefore returns a Specification object. However, my Specification object has a __toString() method.
Car.php
/**
* #var Specification
*/
protected $specification;
public function getSpecification(): Specification
{
return $this->specification;
}
public function setSpecification(Specification $specification): Car
{
$this->specification = $specfification;
return $this;
}
Specification.php
/**
* Returns a string implementation of the current class
*
* #return string
*/
public function __toString(): string
{
return sprintf(
'HP: %s,CCM: %s',
$this->getHorsePower(),
$this->getCapacity()
);
}
How would I achieve overriding my choices for a form field which uses a custom Model? I can't use the EntityType because it's not a Doctrine Model.
When I build my form like this
$carForm = $this->createForm(Car::class);
then it works, but I need the Car object inside the Form, because of another field I'm adding in the buildForm method

Create subform and append it in exisiting form in symfony 3

I have this configuration :
ModuleModelType
| - title
| - created
| - subform
| - DevField1
| - DevField2
| - etc...
What I want to have is something like this :
ModuleModelType
| - title
| - created
| - subform
| | - DevField1
| | - DevField2
| | - etc...
But the problem is that i don't know what users will add to the form, as I'm creating a reusable bundle and I don't want name conflicts.
This is my code, how I can make this easily ?
I want a developer can create a class and append his own form in an existing form.The following existing code append the dev's form into the first one and can replace some existing keys if keys are the same.
Actually, I want to append all dev's fields into and subform to avoid this.
<?php
// Into a controller
/** #var ModuleInterface $instance */
$instance = ... ;
$form = $this->createForm(ModuleModelType::class);
$subform= $this->createForm(new FormType());
$instance->buildForm($subform);
$form->add('subform',$subform); // I want to make something like that
interface ModuleInterface
{
public function buildForm(Form &$form);
}
class Foo implements ModuleInterface
{
public function buildForm(Form &$builder)
{
$builder->add("DevField1", /* type */);
$builder->add("DevField2", /* type */);
}
}
class ModuleModelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('created')
->add('subform')
;
}
}
We will assume some facts :
You want to have an entity with "data" field
This "data" field must be an array
A form must be changed to handle this "data" field
Obviously, in the entity, we can load and save "data" informations, as an array
This behavior can be easily done passing the subform by constructor.
Your problem happen because you're fighting against Symfony, please considere a standard way :
Have an entity with "array" field type
Have two forms Type : First one to handle your entity, second one to handle the embedded form.
Use dependency injection to embed the right form. (Assume two services by Module : first one for the embedded form, second one for you.
Your entity : (Think to generate getters & setters with generate:doctrine:entities)
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Foo
*
* #ORM\Table(name="foo")
* #ORM\Entity(repositoryClass="AppBundle\Repository\FooRepository")
*/
class Foo
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="some_informations", type="string", length=255)
*/
private $someInformations;
/**
* #var string
*
* #ORM\Column(name="Data", type="string", length=255)
*/
private $data;
/**
* #var string
*
* #ORM\Column(name="data", type="array")
*/
private $data;
Your form :
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class FooType extends AbstractType
{
protected $dataFormName;
public function __construct($dataFormName)
{
$this->dataFormName = $dataFormName;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('someInformations')
->add('data', $this->dataFormName)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'AppBundle\Entity\Foo'
));
}
}
Your subform :
<?php
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SubformType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('content')
->add('some_data')
;
}
}
At this point, you should be able to generate your form with :
$foo = new Foo();
$form = $this->createForm('service_name_for_the_form_with_second_embedded', $foo);
Old answer :
In this case, you should assume that ModuleInterface extends FormTypeInterface.
To be used as FormType in a $form->add, your class should look like a form.
At this point, use ModuleInterface seems to be useless.
I can purpose that way for you :
<?php
// Into a controller
$instance = new Foo();
$form = $this->createForm(new ModuleModelType($instance));
class Foo extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add("DevField1");
$builder->add("DevField2");
}
}
class ModuleModelType extends AbstractType
{
protected $subform;
function __construct(FormTypeInterface $subform)
{
$this->subform = $subform;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('created')
->add('subform', $this->subform)
;
}
}
You can also use dependency injection to inject $instance through __construct.
Take care about naming convention for "Foo" class, should be FooType ;)
If you do something like that, your expected form will be OK.
Have a nice day,
Gaƫl
I hope you already read this careful: http://symfony.com/doc/current/book/forms.html
In your case, you can do by:
use path/of/your/formType/to/embed;
class ModuleModelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title')
->add('created')
->add('subform', Foo::class)
;
}
}
Don't worry about interface. Interface just must to implement, I hope you already know that.
I dont know why you want add in controller, because you have already form class, you just need modify to make clean your code.
I you want subform as add by user, you can do by collection form embed: http://symfony.com/doc/current/cookbook/form/form_collections.html
Or you want by data (show form if submitted) : http://symfony.com/doc/current/cookbook/form/dynamic_form_modification.html
NOTE: Symfony ver 3.0 are different with 2.~.

Symfony2 Embedded forms How to assign values of certain form fields to child form field

I have entity User and entity Company.
Relations are Company (one) to User (many).
When I signup as a company I need both these objects to be created.
Both objects have same properties: $phone, $firstName, $lastName;
(for company those fields mean main contact person).
My registration form consists of 2 forms: user form and embedded company form.
Problem: Now when I render signup form I have duplicated fields:
phone, firstName, lastName rendered for each object.
Is there a way to combine them somehow and ask for those values from user only once but still to save them into database for both entities?
I've managed to solve this problem using event listeners on PRE_SUBMIT event:
namespace AppBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
/**
* Class CompanyRegFormType
* #package AppBundle\Form
*/
class UserFormType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('firstName', 'text')
->add('lastName', 'text')
->add('phone', 'text')
->add('company', new CompanyFormType())
->addEventListener(FormEvents::PRE_SUBMIT, function (FormEvent $event){
$data = $event->getData();
$data['company']['phone'] = $data['phone'];
$data['company']['firstName'] = $data['firstName'];
$data['company']['lastName'] = $data['lastName'];
$event->setData($data);
}
);
}
/**
* #return string
*/
public function getName()
{
return 'app_user_registration';
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => 'AppBundle\Entity\User',
]);
}
}

Symfony 2 : Save json and display value

I'm actually developping a personal project with Symfony2.
And i want to to do something but i don't know how to do that.
I have an entity Recette and in this entity i have a property ingredients
This ingredients property is a json_array type.
<?php
namespace sf2\RecetteBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Recette
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="sf2\RecetteBundle\Entity\RecetteRepository")
*/
class Recette
{
// ...
/**
* #var array
*
* #ORM\Column(name="ingredients", type="json_array")
*/
private $ingredients;
// ...
}
?>
In this json_array i just want to save a couple of information.
Ex :
["name":"potatoes","quantity":"5kg"]
Here you can find my Entity FormType :
class RecetteType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name','text',array('label' => "test","attr"=>array('class'=>'test')))
->add('completionTime')
->add('ingredients',
'collection',
array(
'type'=>'text',
'prototype'=>true,
'allow_add'=>true,
'allow_delete'=>true,
'options'=>array(
)
)
)
->add('preparation')
->add('recetteCategories')
->add('Ok','submit')
;
}
}
In my form i can add with jquery with any problem add an ingredient, but my problem is that i can't save the quantity information. i don't know how to display in my form two field instead one for an ingredient.
Currently when i save an ingredient i have this data in database :
["Potatoes"]
How i can display in my form two fields for an ingredient and how to save it in this format ?
["name":"potatoes","quantity":"5kg"]
Thanks.
Here is an example from doc How to Embed a Collection of Forms
Firstly you must create a Custom Form Field Type named IngredientType:
IngredientType
namespace Acme\RecetteBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
class IngredientType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('quantity')
;
}
public function getName()
{
return 'ingredient';
}
}
services.yml
# src/Acme/RecetteBundle/Resources/config/services.yml
services:
acme_demo.form.type.ingredient:
class: Acme\RecetteBundle\Form\Type\IngredientType
tags:
- { name: form.type, alias: ingredient }
And change The field type in your collection to ingredient type.
RecetteType
->add('ingredients',
'collection',
array(
'type'=>'ingredient',
'prototype'=>true,
'allow_add'=>true,
'allow_delete'=>true,
'options'=>array(
)
)
)

Categories