I'm looking to create an CRUD with multiple Entites and so multiple forms. A want to create a Site, each site on creation need to have 1 adresse, each adress need 1 City, each city need 1 Country
So I have a Controller where I called my Type
/**
* #Route("admin/sites/new", name="admin.sites.new")
* #param Request $request
* #return RedirectResponse|Response
*/
public function new (Request $request)
{
$site = new Site();
$adresse = new Adresse();
$ville = new Ville();
$pays = new Pays();
$form = $this->createForm(SiteType::class, $site);
$form2 = $this->createForm(AdresseType::class, $adresse);
$form3 = $this->createForm(VilleType::class, $ville);
$form4 = $this->createForm(PaysType::class, $pays);
$form->handleRequest($request);
$form2->handleRequest($request);
$form3->handleRequest($request);
$form4->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()
&& $form2->isSubmitted() && $form2->isValid()
&& $form3->isSubmitted() && $form3->isValid()
&& $form4->isSubmitted() && $form4->isValid()){
$this->em->persist($site);
$this->em->persist($adresse);
$this->em->persist($ville);
$this->em->persist($pays);
$this->em->flush();
$this->addFlash('success', 'Site crée avec succès');
return $this->redirectToRoute('admin.sites.index');
}
return $this->render('admin/sites/create.html.twig', [
'site' => $site,
'adresse' => $adresse,
'ville' => $ville,
'pays' => $pays,
'form' => $form->createView(),
'form2' => $form2->createView(),
'form3' => $form3->createView(),
'form4' => $form4->createView(),
]);
}
and a Twig to generate the view.
<div class="row">
<div class="col s12 m12 l12">
<div class="card-panel ">
<div class="row">
{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_start(form2) }}
{{ form_widget(form2) }}
{{ form_start(form3) }}
{{ form_widget(form3) }}
{{ form_start(form4) }}
{{ form_widget(form4) }}
<button class="btn s12 m6 l3">{{ button|default('Enregister') }}</button>
{{ form_end(form) }}{{ form_end(form2) }}{{ form_end(form3) }}{{ form_end(form4) }}
</div>
</div>
</div>
</div>
My question is the next, how when i will create my Site, I could link the site_id and the address_id and so the adress_id and the city_id, the city_di and the country _id ? without separate my forms ?
I mean when I press the button the relation will be create correctly.
Thanks for you help.
This is how you should and could achieve this :
You should use embeded form How to Embed Forms
You simply create form types for your entities : SiteType, AdresseType, VilleType, PaysType and embed them like the following
// in SiteType
//....
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...
->add('adresse', AdresseType::class)
;
}
// in AdresseType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...
->add('ville', VilleType::class)
;
}
// in VilleType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
//...
->add('pays', PaysType::class)
;
}
Finally in your controller, all you have to do is to create
/**
* #Route("admin/sites/new", name="admin.sites.new")
* #param Request $request
* #return RedirectResponse|Response
*/
public function new (Request $request)
{
$site = new Site();
$form = $this->createForm(SiteType::class, $site);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()
$this->em->persist($site);
$this->em->flush();
$this->addFlash('success', 'Site crée avec succès');
return $this->redirectToRoute('admin.sites.index');
}
return $this->render('admin/sites/create.html.twig', [
'site' => $site,
'adresse' => $adresse,
'ville' => $ville,
'pays' => $pays,
'form' => $form->createView(),
]);
}
And the corresponding twig is :
<div class="row">
<div class="col s12 m12 l12">
<div class="card-panel ">
<div class="row">
{{ form_start(form) }}
{{ form_widget(form) }}
<button class="btn s12 m6 l3">{{ button|default('Enregister') }}</button>
{{ form_end(form) }}
</div>
</div>
</div>
</div>
Note that in order for it to work, you need to use cascade inside your entity doctrine metadata, like so :
/**
* #ORM\OneToOne(targetEntity="AppBundle:Adresse", cascade={"all"})
*/
private $adresse;
You should use embed forms : https://symfony.com/doc/current/form/embedded.html.
Related
Here is what i'm trying to do :
I have a classic Folder entity which contains Sku entities in a OneToMany relation as an ArrayCollection. So far so good.
I want the FolderType to dynamically create as many Skus as I want in it. For that, I followed the Symfony fast track method to generate prototypes in javascript. Works great.
Now, in this folder form, "skus" are an array collection of entities. In the SkuType, I have files to uploads. Here are the forms (Folder and Sku) :
$builder
->add('norlogReference', TextType::class, [
'label' => 'Référence du dossier'
])
->add('skus', CollectionType::class, [
'entry_type' => SkuType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'prototype' => 'skus'
]);
As for the SkuType, here are my upload fields :
->add('picture_1', FileType::class, [
'label' => 'Image 1 du produit',
'mapped' => false,
'required' => false,
'attr' => ['accept' => 'image/*']
])
->add('picture_2', FileType::class, [
'label' => 'Image 2 du produit',
'mapped' => false,
'required' => false,
'attr' => ['accept' => 'image/*']
])
Here is the controller recieving the form :
/**
* #Route("/new", name="folder_new")
*/
public function new(Request $request): Response
{
$entityManager = $this->getDoctrine()->getManager();
$norlogFolder = new NorlogFolder();
$form = $this->createForm(NorlogFolderType::class, $norlogFolder);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
foreach ($norlogFolder->getSkus() as $sku) {
I want to get both file objects here, pass them in handleUploadedFile(), and use $sku->setPicture1() and setPicture2 with the $newfileName returned by the private method.
$sku->setFolder($norlogFolder);
$sku->setSKU($norlogFolder->getNorlogReference() . '-' . $sku->getSKU());
}
$entityManager->persist($norlogFolder);
$entityManager->flush();
return $this->redirectToRoute('folder_edit', ['id' => $norlogFolder->getId()]);
}
return $this->render('folder/new.html.twig', [
'form' => $form->createView(),
]);
}
and the little private method I want to use in order to handle the file :
private function handleUploadedFile(UploadedFile $uploadedFile): string
{
$destination = $this->getParameter('kernel.project_dir').'/public/uploads/sku_medias';
$originalFilename = pathinfo($uploadedFile->getClientOriginalName(), PATHINFO_FILENAME);
$newFilename = $originalFilename . '-' . uniqid() . '.' . $uploadedFile->guessExtension();
$uploadedFile->move(
$destination,
$newFilename
);
return $newFilename;
}
And the folder form view :
{{ form_start(form, {
attr: {
class: 'sku-form'
}
}) }}
<div class="form-group">
{{ form_row(form.norlogReference, {
attr: {
placeholder: 'ex: XOTP-25',
class: 'sku-form-input'
}
}) }}
</div>
<div class="form-group sku-row skus" data-prototype="{{ form_widget(form.skus.vars.prototype)|e('html_attr') }}">
{% if form.skus|length > 0 %}
{% for sku in form.skus %}
<div class="row sku-bloc bg-light">
<div class="field-title">
<p class="display-3 text-center">Produit #{{ loop.index }}</p>
<a href="{{ path('sku_delete', {id: sku.vars.data.id}) }}"
class="btn bg-danger btn-sku-remove"><i class="fas fa-trash-alt"></i></a>
</div>
<span class="btn btn-secondary form-extend-btn">Voir</span>
<div class="col-12 sku-fields-bloc">
<div class="form-control">
{{ form_row(sku.SKU, {
attr: {
placeholder: 'ex: XVF-25663',
class: 'sku-form-input'
}
}) }}
</div>
<div class="container">
<div class="row">
{{ form_label(sku.marque) }}
{{ form_widget(sku.marque, {
attr: {
class: 'sku-form-input'
}
}) }}
{{ form_label(sku.taille) }}
{{ form_widget(sku.taille, {
attr: {
class: 'sku-form-input'
}
}) }}
{{ form_label(sku.designation) }}
{{ form_widget(sku.designation, {
attr: {
class: 'sku-form-input'
}
}) }}
{{ form_label(sku.couleur) }}
{{ form_widget(sku.couleur, {
attr: {
class: 'sku-form-input'
}
}) }}
{{ form_label(sku.etat) }}
{{ form_widget(sku.etat, {
attr: {
class: 'sku-form-input'
}
}) }}
{{ form_label(sku.composition) }}
{{ form_widget(sku.composition, {
attr: {
class: 'sku-form-input'
}
}) }}
</div>
</div>
<div class="form-control mt-3">
{{ form_row(sku.picture_1, {
attr: {
placeholder: 'Image 1',
class: 'sku-form-input'
}
}) }}
</div>
<div class="form-control mt-3">
{{ form_row(sku.picture_2, {
attr: {
placeholder: 'Image 2',
class: 'sku-form-input'
}
}) }}
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="row">
<div class="col-12 mt-4">
<p class="p-3 bg-info">Pas encore de {{ form_label(form.skus) }} ajoutés</p>
{{ form_widget(form.skus) }}
</div>
</div>
{% endif %}
</div>
<div class="row">
<div class="col-4">
<button type="button" class="btn btn-primary btn-nav w-100 mt-2 mb-2 add_item_link"
data-collection-holder-class="skus">
Ajouter Sku
</button>
</div>
<div class="col-4">
<button type="submit"
class="btn btn-success btn-nav w-100 mt-2 mb-2">{{ button_label|default('Enregistrer') }}</button>
</div>
<div class="col-4">
<a href="{{ path('folder_list') }}"
class="btn btn-primary btn-nav w-100 mt-2 mb-2">Retour liste</a>
</div>
</div>
{{ form_end(form) }}
So the question is : how to get these picture_1 and 2 objects (and not path which I already have by remapping them to true), by Sku, in the controller side, in order to handle the files as usual ?
I tried :
$request->files->get() (result = null, and I have the enctype right in the form)
$sku->getPicture_1() (result = temporary linux path, but is there a simple way to retrieve the file from that, and on every OS ?)
Trying to access the $form from inside the sku loop (result = nada)
$request->get('picture_1') and that + ->getData()
Various weird tries I forgot which gave me nothing
I might miss something obvious here, but I can't think properly on this project anymore.
Please don't mind the necessary refactoring for now =) Ty !
With 'mapped' => false, you need to handle the collection of uploads like this:
if ($form->isSubmitted() && $form->isValid()) {
foreach ($form->get('skus') as $formChild)
{
// Get the unmapped picture fields
$uploadedPicture1 = $formChild->get('picture_1')->getData();
$uploadedPicture2 = $formChild->get('picture_2')->getData();
// Get the sku
$sku = $formChild->getData();
// Upload the pictures
$picture1Filename = $this->handleUploadedFile($uploadedPicture1);
$picture2Filename = $this->handleUploadedFile($uploadedPicture2);
// Set the new filenames onto the sku
$sku->setPicture1($picture1Filename);
$sku->setPicture2($picture2Filename);
// Your original code
$sku->setFolder($norlogFolder);
$sku->setSKU($norlogFolder->getNorlogReference() . '-' . $sku->getSKU());
}
}
$entityManager->persist($norlogFolder);
$entityManager->flush();
return $this->redirectToRoute('folder_edit', ['id' => $norlogFolder->getId()]);
}
Edit: do check if a picture was actually uploaded, as in the other answer. :-)
Your picuter_1 and picture_2 are mapped: false.
That means, you have to use ->getData() (or maybe ->getNormData()) on each SkuType or your NorlogFolderType
instead of looping through SKUs entities collection, loop through sku formtypes and get the data directly from 'picture_1' and 'picture_2' field
if ($form->isSubmitted() && $form->isValid())
{
if ($form->has('skus'))
{
foreach ($form->get('skus') as $skuForm)
{
// always check if field named 'picture_1' is there
if ($skuForm->has('picture_1'))
{
/** #var \Symfony\Component\HttpFoundation\File\UploadedFile $firstPic */
$firstPic = $skuForm->get('picture_1')->getData();
//todo: maybe check if null
$picOne = $this->handleUploadedFile($firstPic);
}
// do the same with picture_2 (and others if any)
}
}
}
In Easy Admin, I already have a list/edit form of users. I want to add an extra form to change password of any member user. (password, repeat password, submit)
In the documentation custom forms are told to be entity specific. For example, to create a custom product form, you create a custom controller:
easy_admin:
entities:
# ...
Product:
controller: AppBundle\Controller\ProductController
# ...
But this solution doesn't fit to my problem. I already set a user form and use that form.
I can set an event listener and manage saving the password but I'm stuck with adding this simple form.
Firstly, you need a controller to handle requests associated with your users (create one if you don't have it already)
/**
* #Route("/user/change-password", name="change_password")
*/
public function changePassword(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$changePasswordModel = new ChangePassword();
$form = $this->createForm(ChangePasswordType::class, $changePasswordModel);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager = $this->getDoctrine()->getManager();
$user = $entityManager->find(User::class, $this->getUser()->getId());
$user->setPassword(
$passwordEncoder->encodePassword(
$user,
$form->get('newPassword')->getData()
)
);
$entityManager->persist($user);
$entityManager->flush();
return $this->redirect('/?entity=User&action=show&id='. $this->getUser()->getId());
}
return $this->render('admin/theme/changePassword/change_password.html.twig', array(
'changePasswordForm' => $form->createView(),
));
}
Then I created a form type that looks like this:
class ChangePasswordType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('oldPassword', PasswordType::class, [
'required' => true,
'label' => 'Type your current password',
])
->add('newPassword', RepeatedType::class, [
'type' => PasswordType::class,
'invalid_message' => 'Passwords do not match.',
'first_options' => ['label' => 'Type your new password'],
'second_options' => ['label' => 'Retype your new password']
]);
}
public function setDefaultOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => ChangePassword::class,
));
}
public function getName()
{
return 'change_passwd';
}
}
I created a form model with custom validation, you can edit this however you like
class ChangePassword
{
/**
* #SecurityAssert\UserPassword(
* message = "Wrong value for your current password!"
* )
*/
public $oldPassword;
/**
* #Assert\Length(
* min = 6,
* minMessage = "Password must be at least 6 characters long!"
* )
*/
public $newPassword;
}
Lastly, extend your base twig with someting like this
{% extends 'base.html.twig' %}
{% block title %}Change password
{% endblock %}
{% block stylesheets %}
{{ parent() }}
{{ encore_entry_link_tags('change-password') }}
{% endblock %}
{% block body %}
{{ parent() }}
<body id="{% block body_id %}{% endblock %}">
<div class="container-fluid h-100">
<div class="row justify-content-center align-items-center h-100">
<div class="col col-sm-8 col-md-8 col-lg-6 col-xl-4">
{{ form_start(changePasswordForm, {'attr':{'class':'form-signin'}}) }}
{{ form_row(changePasswordForm.oldPassword, {'attr': {'class':'form-control mb-2'} }) }}
{{ form_row(changePasswordForm.newPassword.first, {'attr': {'class':'form-control mb-2'} }) }}
{{ form_row(changePasswordForm.newPassword.second, {'attr': {'class':'form-control mb-2'} }) }}
<button class="btn btn-dark btn-lg btn-block mt-3" type="submit">Change password</button>
{{ form_end(changePasswordForm) }}
</div>
</div>
</div>
</body>
{% endblock %}
{% block javascripts %}
{{ encore_entry_script_tags('change-password') }}
{% endblock %}
I created a button on my show user action that takes you to /user/change-password and that is pretty much it.
I would like to use the Symfony 4 Validator Component to validate my forms which I send via AJAX to my Controller.
The Form is rendered with this method in the Controller:
/**
* #Route("/profile", name="profile")
*/
public function profile(Request $request){
$user = $this->getUser();
$form = $this->createFormBuilder($user)
->add('email', EmailType::class)
->add('name', TextType::class)
->getForm();
return $this->render('user/user-profile.html.twig', [
#'user' => $user,
'form' => $form->createView(),
]);
}
Then I have another method for handling the post request sent via AJAX:
/**
* Update user profile data
*
* #Route("/api/users/updateprofile")
* #Security("is_granted('USERS_LIST')")
*/
public function apiProfileUpdate()
{
$request = Request::createFromGlobals();
$user = $this->getUser();
/** #var User $user */
// Is this needed?
$form = $this->createFormBuilder($user)
->add('email', EmailType::class)
->add('name', TextType::class)
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted()) {
if($form->isValid()) {
$user->setName($request->request->get('name'));
$user->setEmail($request->request->get('email'));
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
return new Response('valid');
} else {
return new Response('not valid');
}
}
}
The JavaScript:
$.post('/api/users/' + method, formdata, function (response) {
$('#updateProfileAlertTitle').text("Success!");
$('#updateProfileAlertMessage').text(response);
$('#updateProfileAlert').removeClass('hidden');
$('#updateProfileAlert').removeClass('alert-danger');
$('#updateProfileAlert').addClass('alert-success');
$('.btn-save').button('reset');
$('.btn-cancel').prop('disabled', false);
});
The Twig:
{% block body %}
<section id="sectionProfile">
<div class="box">
<div class="box-body">
<div id="updateProfileAlert" class="alert alert-success alert-dismissible hidden">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
<h4 id="updateProfileAlertTitle"><i class="icon fa fa-check"></i> Success!</h4>
<p id="updateProfileAlertMessage">Success!</p>
</div>
{{ form_start(form, {attr: {class: 'form-horizontal', id: 'formEditProfile', autocomplete: 'disabled'}}) }}
<div class="form-group">
{{ form_label(form.email, 'E-Mail', {label_attr: {class: 'col-sm-2 control-label'}}) }}
<div class="col-sm-10 col-lg-6">
{{ form_widget(form.email, {id: 'email', full_name: 'email', attr: {class: 'form-control', autocomplete: 'disabled'}}) }}
</div>
</div>
<div class="form-group">
{{ form_label(form.name, 'Name', {label_attr: {class: 'col-sm-2 control-label'}}) }}
<div class="col-sm-10 col-lg-6">
{{ form_widget(form.name, {id: 'name',full_name: 'name', attr: {class: 'form-control', autocomplete: 'disabled'}}) }}
</div>
</div>
<div class="module-buttons">
<button type="button" id="updateUserProfile" class="btn btn-primary btn-save" data-loading-text="<i class='fa fa-spinner fa-spin '></i> Saving">Save</button>
</div>
{{ form_end(form) }}
</div>
</div>
</section>
{% endblock %}
Now I have some problems when using the Symfony Validator:
Either Symfony says I must return something (it only returns a response if $form->isSubmitted() and/or isValid() )
or it says that the handleRequest method expects a string (but in my case it gets NULL as value for $request).
Do I have to use the handleRequest method in order to use the Symfony Validator and its Validation methods isValid and isSubmitted?
Or what is the way to go? Thank you in advance and sorry for my bad english
Symfony controller actions always need to return something, put
return new Response('not submitted');
at the end of the action.
The request object needs to be given to the action properly. Try this:
use Symfony\Component\HttpFoundation\Request;
public function apiProfileUpdate(Request $request)
{
// delete this: $request = Request::createFromGlobals();
To validate an entity you don't necessarily need to use a form, you can use the validator directly. Using a form is the standard way because usually when creating or editing an entity a form is used anyway.
https://symfony.com/doc/current/validation.html
I need to embed an entity form inside it's parent form. I followed the official documentation to embed a form inside another with one-to-many relationship.
The problem is that though I manage to display correctly the forms, when need to persist another child entity, it works properly with the first 2 object, when I try to create the third object, it deletes the second object in the array and create the third one.
Example: I have a CV entity that has one-to-many relationship to WorkExperience entity
Code:
Entities:
class Curriculum
{
/**
* #ORM\OneToMany(targetEntity="appBundle\Entity\WorkExperience", mappedBy="curriculum", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $workExperience;
public function __construct()
{
$this->workExperience = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add workExperience
*
* #param appBundle\Entity\WorkExperience $workExperience
* #return Curriculum
*/
public function addWorkExperience(appBundle\Entity\WorkExperience $workExperience)
{
$this->workExperience->add($workExperience);
$workExperience->setCurriculum($this);
return $this;
}
/**
* Remove workExperience
*
* #param appBundle\Entity\WorkExperience $workExperience
*/
public function removeWorkExperience(appBundle\Entity\WorkExperience $workExperience)
{
$this->workExperience->removeElement($workExperience);
}
/**
* Get workExperience
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getWorkExperience()
{
return $this->workExperience;
}
}
WorkExperience:
class WorkExperience
{
/**
* #ORM\ManyToOne(targetEntity="appBundle\Entity\Curriculum", inversedBy="workExperience")
* #ORM\JoinColumn(name="curriculum", referencedColumnName="id")
*/
private $curriculum;
/**
* Set curriculum
*
* #param string $curriculum
* #return WorkExperience
*/
public function setCurriculum($curriculum)
{
$this->curriculum = $curriculum;
return $this;
}
/**
* Get curriculum
*
* #return string
*/
public function getCurriculum()
{
return $this->curriculum;
}
}
Then, the formType (I only created the workExperience form since it's what I need in my collectionType field)
WorkExperienceType:
<?php
namespace appBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
use appBundle\Entity\WorkExperience;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Intl\Intl;
use Symfony\Component\Form\Extension\Core\Type\DateType;
class WorkExperienceType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('fromDate', DateType::class, array(
'widget' => 'single_text',
'html5' => false,
'attr' => ['class' => 'form-control js-datepicker'],
'format' => 'dd-MM-yyyy',
'label' => 'From')
)
->add('toDate', DateType::class, array(
'widget' => 'single_text',
'html5' => false,
'attr' => ['class' => 'form-control js-datepicker'],
'format' => 'dd-MM-yyyy',
'label' => 'To',
'required' => false,
)
)
->add('ongoing', 'checkbox', array('required' => false,))
->add('jobProfile', EntityType::class, array(
'class' => 'appBundle:JobProfile',
'query_builder' => function (EntityRepository $er) use ($options) {
return $er->createQueryBuilder('j')
->where('j.company = :company')
->setParameter('company', $options['company']);
},
'choice_label' => 'name',
'required' => false,
'placeholder' => 'Job Profile'
))
->add('employerName', 'text', array('label' => "Name"))
->add('employerCity', 'text', array('label' => "City"))
->add('activities', 'textarea', array('label' => "Activities and responsibilities"));
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefined('company');
$resolver->setDefaults(array(
'data_class' => WorkExperience::class,
));
}
}
Curriculum Controller:
I only show you the form management since its where I have the problem (If you wondered I don't pass an id because the connected user can only have one Curriculum)
<?php
namespace appBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use appBundle\Entity\Curriculum;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use appBundle\Form\Type\WorkExperienceType;
use appBundle\Form\Type\EducationCvType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Intl\Intl;
use Doctrine\Common\Collections\ArrayCollection;
use appBundle\Entity\WorkExperience;
class CurriculumController extends Controller
{
public function curriculumEditAction(Request $request)
{
if (!$this->getUser() || !$this->get('session')) {
$response = $this->generateUrl('homepage');
return new RedirectResponse($response);
}
$em = $this->getDoctrine()->getManager();
$curriculum = $this->getUser()->getEmployee()->getCurriculum();
$originalWorkExperience = new ArrayCollection();
foreach ($curriculum->getWorkExperience() as $workExperience) {
$originalWorkExperience->add($workExperience);
}
$originalEducation = new ArrayCollection();
foreach ($curriculum->getEducationTraining() as $educationTraining) {
$originalEducation->add($educationTraining);
}
$countries = Intl::getRegionBundle()->getCountryNames();
$formCurriculum = $this->createFormBuilder($curriculum)
->add('workExperience', CollectionType::class, array(
'entry_type' => WorkExperienceType::class,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false,
'type' => new WorkExperience(),
'entry_options' => array(
'company' => $this->getUser()->getEmployee()->getCompany(),
),
))
->add('save', 'submit', array('label' => 'Save changes', 'attr' => array('class' => 'btn btn-blue')))
->getForm();
if ('POST' === $request->getMethod()) {
$formCurriculum->bind($request);
if ($formCurriculum->isValid()) {
$curriculum = $formCurriculum->getData();
try {
foreach ($curriculum->getWorkExperience() as $workExperience) {
$em->persist($workExperience);
$em->flush();
}
// remove the relationship between the tag and the Task
foreach ($originalWorkExperience as $workExperience) {
if (false === $curriculum->getWorkExperience()->contains($workExperience)) {
// remove the Task from the Tag
$workExperience->getCurriculum()->removeWorkExperience($workExperience);
// if you wanted to delete the Tag entirely, you can also do that
$em->remove($workExperience);
}
}
$em->persist($curriculum);
$em->flush();
$request->getSession()
->getFlashBag()
->add('success', 'edit_curriculum_ok')
;
} catch (Exception $e) {
$request->getSession()
->getFlashBag()
->add('error', 'edit_curriculum_ko')
;
}
return $this->render('appBundle:Curriculum:curriculum_edit.html.twig', array('curriculum' => $curriculum, 'formCurriculum' => $formCurriculum->createView(),));
}
}
return $this->render('appBundle:Curriculum:curriculum_edit.html.twig', array('curriculum' => $curriculum, 'formCurriculum' => $formCurriculum->createView(),));
}
}
Template:
{% form_theme formCurriculum _self %}
{% block work_experience_widget %}
<div class="form-group row">
<div class="col-sm-6">
<label for="">Nombre Empresa</label>
{{ form_widget(form.employerName, {'attr': {'class': 'form-control'}}) }}
</div>
<div class="col-sm-5">
<label for="">Categoría Profesional</label>
{{ form_widget(form.jobProfile, {'attr': {'class': 'form-control'}}) }}
</div>
<div class="col-sm-1">
<i class="glyphicon glyphicon-remove"></i>
</div>
</div>
<div class="form-group row">
<div class="col-sm-3">
<label for="">Fecha desde</label>
<div class="input-group">
{{ form_widget(form.fromDate) }}
<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>
</div>
<div class="col-sm-5">
<label for="">Fecha hasta</label>
<div class="input-group">
<span class="input-group-addon">
{{ form_widget(form.ongoing) }}
</span>
{{ form_widget(form.toDate) }}
<span class="input-group-addon"><i class="glyphicon glyphicon-calendar"></i></span>
</div>
</div>
<div class="col-sm-4">
<label for="">Ciudad</label>
{{ form_widget(form.employerCity, {'attr': {'class': 'form-control'}}) }}
</div>
</div>
<div class="form-group">
<label for="">Actividades y Responsabilidades</label>
{{ form_widget(form.activities, {'attr': {'class': 'form-control'}}) }}
{{ form_rest(form) }}
{{ form_errors(form)}}
</div>
<hr />
{% endblock %}
{{ form_start(formCurriculum) }}
<div class="col-lg-8 col-xs-12">
<h3>Experiencia Laboral</h3>
<div class="workExperience" data-prototype="{{ form_widget(formCurriculum.workExperience.vars.prototype)|e('html_attr') }}">
{% for workExperience in formCurriculum.workExperience %}
<div class="workExperienceUnit">
{{ form(workExperience)}}
</div>
{% endfor %}
</div>
<div class="form-group text-right">
{{ form_widget(formCurriculum.save, {'attr': {'class': 'btn btn-blue'}}) }}
</div>
<div class="clearfix"></div>
</div>
{{ form_rest(formCurriculum) }}
{{ form_end(formCurriculum) }}
Ajax and js for the form collection:
{% block javascripts %}
{{ parent() }}
<script type="text/javascript">
var $collectionHolder;
// setup an "add a tag" link
var $addWorkExperienceLink = $('<button type="button" class="btn btn-primary width100x100 add_workexperience_link"><span class="badge"><i class="glyphicon glyphicon-plus"></i></span> Agregar nueva experiencia laboral</button>');
var $newLinkLi = $('<div class="form-group row"><div class="col-sm-12"></div></div>').append($addWorkExperienceLink);
jQuery(document).ready(function() {
$collectionHolder = $('div.workExperience');
// add the "add a tag" anchor and li to the tags ul
$collectionHolder.append($newLinkLi);
// count the current form inputs we have (e.g. 2), use that as the new
// index when inserting a new item (e.g. 2)
$collectionHolder.data('index', $collectionHolder.find(':input').length);
$addWorkExperienceLink.on('click', function(e) {
// prevent the link from creating a "#" on the URL
e.preventDefault();
// add a new tag form (see next code block)
addWorkExperienceForm($collectionHolder, $newLinkLi);
});
});
function addWorkExperienceForm($collectionHolder, $newLinkLi) {
// Get the data-prototype explained earlier
var prototype = $collectionHolder.data('prototype');
// get the new index
var index = $collectionHolder.data('index');
$collectionHolder.data('index', index + 1);
// Replace '$$name$$' in the prototype's HTML to
// instead be a number based on how many items we have
var newForm = prototype.replace(/__name__/g, index);
// Display the form in the page in an li, before the "Add a tag" link li
var $newFormLi = $('<div class="workExperienceUnit"></div>').append(newForm);
$newLinkLi.before($newFormLi);
$('.remove-tag').click(function(e) {
e.preventDefault();
$(this).parent().parent().parent().remove();
return false;
});
}
// handle the removal, just for this example
$('.remove-tag').click(function(e) {
e.preventDefault();
$(this).parent().parent().parent().remove();
return false;
});
</script>
{% endblock %}
And here are the screenshots, the thing is that it deletes the object correctly, the onlye problem is when creating new objects
Severals things seem to be stranged.
Use $form->handleRequest($request); instead of bind()
Why did you use 'type' => new WorkExperience()? I think you don't have to use thois in CollectionType Field because the type of entity is defined in WorkexperienceTpe.
When you post your form with handlerequest function, you don't need to retrieve all data from form because all workexperience have been added to $formCurriculum automatically
i am a newbee in symfony2.
I am working on a searching functionality and here is my code in my indexAction:
/**
* #Route("/admin/users/", name="userspage")
*/
public function indexAction(Request $request)
{
$repo = $this->getDoctrine()->getRepository('AppBundle:User');
$users = $repo->getUsers();
//create
$form = $this->createForm(new SearchType());
if ($form->handleRequest($request)->isSubmitted()) {
if($form->isValid()){
return new Response("Valid");
} else {
return new Response("Not Valid");
}
}
return $this->render('AppBundle:User:index.html.twig', array(
'base_dir' => realpath($this->container->getParameter('kernel.root_dir').'/..'),
'active_nav' => 'users',
'users' => $users,
'form' => $form->createView(),
));
}
Here is my FormType:
class SearchType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
parent::buildForm($builder, $options);
$builder->add('term', 'text' , array('attr'=> array('placeholder'=>
'Enter name to search', 'class' => 'form-control'),
'label' => 'Search: '
));
}
public function getName()
{
return 'user_search';
}
}
Here is how i render the form:
<div class="row row-padding no-gutter">
<form action="{{ path('userspage') }}" method="GET">
<div class="col-lg-1">
{{ form_label(form.term) }}
</div>
<div class="col-lg-4">
{{ form_widget(form.term) }}
</div>
<div class="col-lg-4">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i> Search</button>
</div>
{{ form_rest(form) }}
</form>
</div>
My problem is, the form never gets valid and never gets submitted. Why is that so?
Thanks.
The method of a form is POST by default. You can use ->setMethod('GET') or add method => 'GET' to the options to make your form use the GET method.
It's also advised to use {{ form_start(form) }} and {{ form_end(form) }} instead of hardcoded html tags, since this will make sure your method is alright.
See http://symfony.com/doc/current/book/forms.html#changing-the-action-and-method-of-a-form for more information.