I am trying to set up some embedded forms using this guide.
I have two models set up in my application, Lesson and Evaluation.
Each Lesson can have multiple Evaluations.
I have a form set up where the user can create a lesson and also as many evaluations as they want within that lesson. When the form is submitted, it creates the lesson record and all of the evaluation records successfully, however the evaluation records that get created are not linked to the parent lesson (the lesson_id field is just left blank).
Can anyone help?
Any advice appreciated.
Thanks.
My Model classes are set up like this:
Evaluation:
class Evaluation
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Lesson", inversedBy="evaluations")
* #ORM\JoinColumn(name="lesson_id", referencedColumnName="id")
*/
protected $lesson;
/**
* Set lesson
*
* #param \LessonBundle\Entity\Lesson $lesson
* #return Evaluation
*/
public function setLesson(\LessonBundle\Entity\Lesson $lesson = null)
{
$this->lesson = $lesson;
return $this;
}
/**
* Get lesson
*
* #return \LessonBundle\Entity\Lesson
*/
public function getLesson()
{
return $this->lesson;
}
}
And the Lesson:
class Lesson
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="Evaluation", mappedBy="lesson", cascade={"persist"})
*/
protected $evaluations;
public function __construct()
{
$this->evaluations = new ArrayCollection();
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Add evaluations
*
* #param \DS\LessonBundle\Entity\Evaluation $evaluations
* #return Lesson
*/
public function addEvaluation(\LessonBundle\Entity\Evaluation $evaluations)
{
$this->evaluations[] = $evaluations;
return $this;
}
/**
* Remove evaluations
*
* #param \DS\LessonBundle\Entity\Evaluation $evaluations
*/
public function removeEvaluation(\LessonBundle\Entity\Evaluation $evaluations)
{
$this->evaluations->removeElement($evaluations);
}
/**
* Get evaluations
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getEvaluations()
{
return $this->evaluations;
}
public function setEvaluations(ArrayCollection $evaluations)
{
foreach ($evaluations as $evaluation) {
$evaluation->setLesson($this);
}
$this->evaluations = $evaluations;
}
}
My Controller method:
public function newAction()
{
$lesson = new Lesson;
$evaluation1 = new Evaluation();
$lesson->getEvaluations()->add($evaluation1);
$form = $this->createForm(new LessonType(), $lesson);
$request = $this->getRequest();
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getEntityManager();
$em->persist($lesson);
$em->flush();
return $this->redirect($this->generateUrl('lesson_list'));
}
}
return $this->render('LessonBundle:Lesson:new.html.twig', array('form' => $form->createView()));
}
And my forms:
class LessonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('evaluations', 'collection', array(
'type' => new EvaluationType(),
'allow_add' => true,
'by_reference' => false,
));
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'LessonBundle\Entity\Lesson',
);
}
public function getName()
{
return 'Lesson';
}
}
And:
class EvaluationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('report');
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'LessonBundle\Entity\Evaluation',
);
}
public function getName()
{
return 'Evaluation';
}
}
And finally, my form twig template:
{% extends '::base.html.twig' %}
{% block content %}
<form class="vertical" action="" method="post" {{ form_enctype(form) }}>
{{ form_errors(form) }}
<ul class="collectionholder" data-prototype="{{ form_widget(form.evaluations.vars.prototype)|e }}">
{% for evaluation in form.evaluations %}
<li>{{ form_row(evaluation) }}</li>
{% endfor %}
</ul>
{{ form_rest(form) }}
<input type="submit" />
</form>
{% endblock %}
In your class Lesson entity add:
/**
* Add evaluations
*
* #param \DS\LessonBundle\Entity\Evaluation $evaluations
* #return Lesson
*/
public function addEvaluation(\LessonBundle\Entity\Evaluation $evaluations)
{
$this->evaluations[] = $evaluations;
$evaluations->setLesson($this);
return $this;
}
Related
I use Symfony 4 and the bundle easy admin to create a form with multiple collection, here is my code :
Produit.yaml
easy_admin:
entities:
Produit:
class: App\Entity\Produit
templates:
new: 'security/custom_produit.html.twig'
edit: 'security/custom_produit.html.twig'
form:
fields:
- nom
- {property: 'fiche_technique_file', label: 'Fiche technique', type: 'vich_file'}
- categorie
- { property: 'modele1', label: 'Modèle 1', type: 'collection', type_options: {entry_type: 'App\Form\Modele1Type', allow_add: true, allow_delete: true, by_reference: false} }
list:
fields: ['nom', {property: 'fiche_technique', label: 'Fiche technique'}, {property: 'categorie', label: 'Catégorie'},
{property: 'modele1', label: 'Modèle 1'}
Modele1Type.php
class Modele1Type extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('intro', CKEditorType::class)
->add('caract_tech', CKEditorType::class, [
'label' => 'Caractéristique technique'
])
->add('applications', TextareaType::class)
->add('info_comp_1', CKEditorType::class, [
'label' => 'Info complémentaire 1'
])
->add('info_comp_2', CKEditorType::class, [
'label' => 'Info complémentaire 2'
])
->add('info_comp_3', CKEditorType::class, [
'label' => 'Info complémentaire 3'
])
->add('galerie_photo', CollectionType::class, [
'entry_type' => MultipleImgModele1Type::class,
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false,
'label' => false,
])
->add('avantages', CollectionType::class, [
'entry_type' => AvantageType::class,
'prototype' => true,
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'required' => false,
'label' => false,
])
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Modele1::class,
]);
}
}
Produit.php
/**
* #ORM\Entity(repositoryClass="App\Repository\ProduitRepository")
* #Vich\Uploadable()
*/
class Produit
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $nom;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $fiche_technique;
/**
* #Assert\File(
* maxSize = "5M",
* mimeTypes = {"application/pdf", "application/x-pdf"},
* mimeTypesMessage = "Veuillez envoyer un fichier PDF valide"
* )
* #Vich\UploadableField(mapping="produit_fiche_tech", fileNameProperty="fiche_technique")
*/
private $fiche_technique_file;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $updated_at;
/**
* #ORM\ManyToOne(targetEntity="Categorie", inversedBy="produits")
*/
private $categorie;
/**
* #ORM\ManyToOne(targetEntity="Modele1", inversedBy="produits", cascade={"persist"})
* #ORM\JoinColumn(name="modele1_id", referencedColumnName="id", nullable=true)
*/
private $modele1;
public function __construct()
{
$this->updated_at = new \DateTime();
}
public function __toString()
{
$res = $this->nom;
if($this->modele1 != null) {
$res .= " - Modèle 1";
} else if($this->modele2 != null) {
$res .= " - Modèle 2";
} else if($this->modele3 != null) {
$res .= " - Modèle 3";
}
return $res;
}
public function getId(): ?int
{
return $this->id;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getFicheTechnique(): ?string
{
return $this->fiche_technique;
}
public function setFicheTechnique(?string $fiche_technique): self
{
$this->fiche_technique = $fiche_technique;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updated_at;
}
public function setUpdatedAt(?\DateTimeInterface $updated_at): self
{
$this->updated_at = $updated_at;
return $this;
}
/**
* #return mixed
*/
public function getFicheTechniqueFile()
{
return $this->fiche_technique_file;
}
/**
* #param mixed $fiche_technique_file
* #throws \Exception
*/
public function setFicheTechniqueFile(?File $fiche_technique_file): void
{
$this->fiche_technique_file = $fiche_technique_file;
if($fiche_technique_file) {
$this->updated_at = new \DateTime();
}
}
/**
* #return mixed
*/
public function getCategorie(): ?Categorie
{
return $this->categorie;
}
/**
* #param mixed $categorie
*/
public function setCategorie(?Categorie $categorie): void
{
$this->categorie = $categorie;
}
/**
* #return mixed
*/
public function getModele1(): ?Modele1
{
return $this->modele1;
}
/**
* #param mixed $modele1
*/
public function setModele1($modele1): self
{
if(empty(array_values($modele1)[0])) {
$this->modele1 = null;
} else {
$this->modele1 = array_values($modele1)[0];
}
return $this;
}
}
Modele1.php
/**
* #ORM\Entity(repositoryClass="App\Repository\Modele1Repository")
*/
class Modele1
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="text")
*/
private $intro;
/**
* #ORM\Column(type="text")
*/
private $caract_tech;
/**
* #ORM\Column(type="text")
*/
private $applications;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $info_comp_1;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $info_comp_2;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $info_comp_3;
/**
* #ORM\OneToMany(targetEntity="Produit", mappedBy="modele1", cascade={"persist"})
*/
private $produits;
/**
* #ORM\OneToMany(targetEntity="MultipleImgModele1", mappedBy="modele1", cascade={"persist", "remove"}, orphanRemoval=true)
*/
private $galerie_photo;
/**
* #ORM\OneToMany(targetEntity="Avantage", mappedBy="modele1")
*/
private $avantages;
public function __construct()
{
$this->produits = new ArrayCollection();
$this->galerie_photo = new ArrayCollection();
$this->avantages = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getIntro(): ?string
{
return $this->intro;
}
public function setIntro(string $intro): self
{
$this->intro = $intro;
return $this;
}
public function getCaractTech(): ?string
{
return $this->caract_tech;
}
public function setCaractTech(string $caract_tech): self
{
$this->caract_tech = $caract_tech;
return $this;
}
public function getApplications(): ?string
{
return $this->applications;
}
public function setApplications(string $applications): self
{
$this->applications = $applications;
return $this;
}
public function getInfoComp1(): ?string
{
return $this->info_comp_1;
}
public function setInfoComp1(?string $info_comp_1): self
{
$this->info_comp_1 = $info_comp_1;
return $this;
}
public function getInfoComp2(): ?string
{
return $this->info_comp_2;
}
public function setInfoComp2(?string $info_comp_2): self
{
$this->info_comp_2 = $info_comp_2;
return $this;
}
public function getInfoComp3(): ?string
{
return $this->info_comp_3;
}
public function setInfoComp3(?string $info_comp_3): self
{
$this->info_comp_3 = $info_comp_3;
return $this;
}
/**
* #return Collection|Produit
*/
public function getProduits(): Collection
{
return $this->produits;
}
public function addProduit(Produit $produits): self
{
if (!$this->produits->contains($produits)) {
$this->produits[] = $produits;
$produits->setModele1($this);
}
return $this;
}
public function removeProduit(Produit $produits): self
{
if ($this->produits->contains($produits)) {
$this->produits->removeElement($produits);
// set the owning side to null (unless already changed)
if ($produits->getModele1() === $this) {
$produits->setModele1(null);
}
}
return $this;
}
/**
* #return Collection|MultipleImgModele1[]
*/
public function getGaleriePhoto(): Collection
{
return $this->galerie_photo;
}
public function addGaleriePhoto(MultipleImgModele1 $galeriePhoto): self
{
if (!$this->galerie_photo->contains($galeriePhoto)) {
$this->galerie_photo[] = $galeriePhoto;
$galeriePhoto->setModele1($this);
}
return $this;
}
public function removeGaleriePhoto(MultipleImgModele1 $galeriePhoto): self
{
if ($this->galerie_photo->contains($galeriePhoto)) {
$this->galerie_photo->removeElement($galeriePhoto);
// set the owning side to null (unless already changed)
if ($galeriePhoto->getModele1() === $this) {
$galeriePhoto->setModele1(null);
}
}
return $this;
}
/**
* #return mixed
*/
public function getAvantages(): Collection
{
return $this->avantages;
}
public function addAvantage(Avantage $avantages): self
{
if (!$this->avantages->contains($avantages)) {
$this->avantages[] = $avantages;
$avantages->setModele1($this);
}
return $this;
}
public function removeAvantage(Avantage $avantages): self
{
if ($this->avantages->contains($avantages)) {
$this->avantages->removeElement($avantages);
// set the owning side to null (unless already changed)
if ($avantages->getModele1() === $this) {
$avantages->setModele1(null);
}
}
return $this;
}
}
custom_produit.html.twig
{% extends '#EasyAdmin/default/edit.html.twig' %}
{% block head_custom_stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
{% block main %}
{% block entity_form %}
{{ form_start(form) }}
<div class="container cont-modele-produit-admin mb-4">
<div class="accordion" id="accordionModeles">
<div class="row mb-4">
{% for model in form.modele1.vars.prototype %}
<div class="col-md-12 mt-3">
{% if model.vars.name == "galerie_photo" %}
<fieldset class="p-4">
<legend>Galerie Photos</legend>
{{ form_row(model) }}
</fieldset>
{% endif %}
{% if model.vars.name == "avantages" %}
<fieldset class="p-4">
<legend>Avantages</legend>
{{ form_row(model) }}
</fieldset>
{% endif %}
{{ form_label(model) }}
{{ form_widget(model) }}
</div>
{% endfor %}
{% do form.modele1.setRendered %}
</div>
</div>
</div>
{{ form_end(form) }}
{% endblock %}
{% endblock %}
{% block body_custom_javascript %}
{{ encore_entry_script_tags('app') }}
{% endblock %}
In the setModele1 function in the Produit entity, I had to do a little update to get the Modele1 entity data because it give me an array like that :
Produit.php on line 180:
array:1 [▼
"__name__" => Modele1^ {#2564 ▼
-id: null
-intro: "<p>test</p>"
-caract_tech: "<p>test</p>"
-applications: "test"
-info_comp_1: null
-info_comp_2: null
-info_comp_3: null
-produits: ArrayCollection^ {#2577 ▶}
-galerie_photo: ArrayCollection^ {#2806 ▶}
-avantages: ArrayCollection^ {#2804 ▶}
}
]
it returned me an error because it should get the Modele1 Entity instead of an array, and now when I try to edit a Produit that have a Modele1 it give me this error :
Expected argument of type "array or (\Traversable and \ArrayAccess)", "Proxies__CG__\App\Entity\Modele1" given
So why do I need to give an array instead of the Modele1 entity to get all the data and put it in the form to edit then ?
Sorry for my bad english I hope you will all undestand what I wanted to do ^^
Thank you in advance for your help !
How can I successfully embed a running image upload form inside a classified form with OneToOne relationship ?
first post ever on Stackoverflow so please be gentle (and I'm french ...^^)
I have a Symfony 3.3 project, running great. I use VichUploaderBundle for upload and LiipImagine for rendering. Currently, NO PROBLEM for a single image File upload !
I have an uploadtest page, I show one image file upload form, I upload the file ... it goes throught the controller and everything is fine. File uploaded and moved in directory, page shows new image, with all filters. Great !
My real problem comes when embedding the form inside another form.
I would like to embed my "image form" inside a classified form. I started by creating the field mainImage inside this classified form. The relation from the classified is OneToOne, and of course it has cascade="persist" & "remove".
But on validation, nothing is triggered and I got the following error on the image_name :
--- An exception occurred while executing 'INSERT INTO vehicle_ad (createdAt, updatedAt, published, publishedAt, friendsPriority,
friendsPriorityStart, purchaseDate, sellPrice, currency, odometerType,
currentOdometer, energy, gearbox, mainColor, nickname, description,
creator_id, vehicle_brand_id, vehicle_model_id, main_image_id,
vehicle_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?)' with params ["2018-01-23 17:15:39", "2018-01-23
17:15:39", 0, null, 0, null, null, 2650, "eur", "kilometers", 17340,
null, null, null, null, null, 1, 3, 1, null, null]:
SQLSTATE[23000]: Integrity constraint violation: 1048 Le champ
'main_image_id' ne peut être vide (null)
File is empty, vichuploader namer not triggered ... and I am stuck with it since a few days. I did a lot of research ... so any help will be greatly appreciated.
I try to give my code as clearly as possible. First, what is OK (single image upload) :
Image class
<?php
namespace Gac\MediasBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Image
*
* #ORM\Table(name="image")
* #ORM\Entity(repositoryClass="Gac\MediasBundle\Repository\ImageRepository")
* #Vich\Uploadable
*/
class Image
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/* ----------------------------------------------------------------------
--------- Connection with other entities
------------------------------------------------------------------------- */
/**
* #ORM\ManyToOne(targetEntity="Gac\UserBundle\Entity\User", inversedBy="images")
* #ORM\JoinColumn(nullable=false)
*/
private $creator;
/**
* #ORM\ManyToOne(targetEntity="Gac\AdsBundle\Entity\VehicleAd", inversedBy="images")
* #ORM\JoinColumn(nullable=true)
*/
private $vehicleAd;
// -------------------------------------------------------------------------------
/**
* #var string
*
* #ORM\Column(name="imageName", type="string", length=255)
*/
private $imageName;
/**
* #Vich\UploadableField(mapping="users_images", fileNameProperty="imageName")
* #var File
*/
private $imageFile;
/**
* #var \DateTime
*
* #ORM\Column(name="createdAt", type="datetime")
* #Assert\DateTime()
*/
private $createdAt;
/**
* #var \DateTime
*
* #ORM\Column(name="updatedAt", type="datetime")
* #Assert\DateTime()
*/
private $updatedAt;
/**
* #var \DateTime
*
* #ORM\Column(name="hiddenAt", type="datetime", nullable=true)
* #Assert\DateTime()
*/
private $hiddenAt;
public function __construct($thisuser)
{
$this->creator = $thisuser;
$this->createdAt = new \Datetime();
$this->updatedAt = new \Datetime();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set creator
*
* #param \Gac\UserBundle\Entity\User $creator
*
* #return Image
*/
public function setCreator(\Gac\UserBundle\Entity\User $creator)
{
$this->creator = $creator;
return $this;
}
/**
* Get creator
*
* #return \Gac\UserBundle\Entity\User
*/
public function getCreator()
{
return $this->creator;
}
/**
* Set imageName
*
* #param string $imageName
*
* #return Image
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* Get imageName
*
* #return string
*/
public function getImageName()
{
return $this->imageName;
}
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
// VERY IMPORTANT:
// It is required that at least one field changes if you are using Doctrine,
// otherwise the event listeners won't be called and the file is lost
if ($image) {
$this->updatedAt = new \DateTime();
}
}
/**
* Get imageFile
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* Set createdAt
*
* #param \DateTime $createdAt
*
* #return Image
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Get createdAt
*
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Set updatedAt
*
* #param \DateTime $updatedAt
*
* #return Image
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set hiddenAt
*
* #param \DateTime $hiddenAt
*
* #return Image
*/
public function setHiddenAt($hiddenAt)
{
$this->hiddenAt = $hiddenAt;
return $this;
}
/**
* Get hiddenAt
*
* #return \DateTime
*/
public function getHiddenAt()
{
return $this->hiddenAt;
}
/**
* Set vehicleAd
*
* #param \Gac\AdsBundle\Entity\VehicleAd $vehicleAd
*
* #return Image
*/
public function setVehicleAd(\Gac\AdsBundle\Entity\VehicleAd $vehicleAd = null)
{
$this->vehicleAd = $vehicleAd;
return $this;
}
/**
* Get vehicleAd
*
* #return \Gac\AdsBundle\Entity\VehicleAd
*/
public function getVehicleAd()
{
return $this->vehicleAd;
}
}
Then the related IMAGE FORM :
<?php
namespace Gac\MediasBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichImageType;
class ImageType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('imageFile', VichImageType::class, [
'label' => false,
'required' => false,
'allow_delete' => false,
]);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Gac\MediasBundle\Entity\Image'
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
//return 'gac_mediasbundle_image';
return 'ImageType';
}
}
And the controller
{% extends 'medias/layout.html.twig' %}
{% block bundle_content %}
<div class="jumbotron text-center">
<h1>Testing user's images UPLOAD</h1>
</div>
<p>-----------------</p>
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form) }}
<button type="submit">Upload</button>
{{ form_end(form) }}
<p>-----------------</p>
{% for picture in pictures %}
<div class="row">
<div class="media">
<div class="media-left media-middle">
<img class="media-object" src="{{ picture.imageName | imagine_filter('tiny_test') }}" alt="{{ 'GearsAndCoffee-'~picture.imageName }}" /><br/>
</div>
<div class="media-body">
<h4 class="media-heading">Filter name :</h4>
<em>tiny_test</em>
</div>
</div>
<div class="media">
<div class="media-left media-middle">
<a href="{{ asset('uploads/images/'~picture.imageName) }}" />
<img class="media-object" src="{{ picture.imageName | imagine_filter('std_display') }}" alt="{{ 'GearsAndCoffee-'~picture.imageName }}" /><br/>
</a>
</div>
<div class="media-body">
<h4 class="media-heading">Filter name :</h4>
<em>std_display</em>
</div>
</div>
<p>
<strong>UPDATE IMAGE</strong>
</p>
<p>
<strong>DELETE IMAGE</strong>
</p>
</div>
<p>-----------------</p>
{% endfor %}
{% endblock bundle_content %}
As an information, the VichUploader Bundle config :
vich_uploader:
db_driver: orm
#templating: true
#twig: true
#form: true
mappings:
users_images:
uri_prefix: '%app.path.users_images%'
upload_destination: '%kernel.root_dir%/../web/uploads/images'
namer: vich_uploader.namer_uniqid
inject_on_load: true
delete_on_update: true
delete_on_remove: true
And now the code of what is NOT RUNNING on my app :
The classified class
<?php
namespace Gac\AdsBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
/**
* VehicleAd
*
* #ORM\Table(name="vehicle_ad")
* #ORM\Entity(repositoryClass="Gac\AdsBundle\Repository\VehicleAdRepository")
* #ORM\HasLifecycleCallbacks
*/
class VehicleAd
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/* ----------------------------------------------------------------------
--------- Connection with other entities
------------------------------------------------------------------------- */
/**
* #ORM\ManyToOne(targetEntity="Gac\UserBundle\Entity\User")
* #ORM\JoinColumn(nullable=false)
*/
private $creator;
/**
* #ORM\ManyToOne(targetEntity="Gac\VehiclesBundle\Entity\VehicleBrand")
* #ORM\JoinColumn(nullable=false)
*/
private $vehicleBrand;
/**
* #ORM\ManyToOne(targetEntity="Gac\VehiclesBundle\Entity\VehicleModel")
* #ORM\JoinColumn(nullable=false)
*/
private $vehicleModel;
/**
* #ORM\OneToOne(targetEntity="Gac\MediasBundle\Entity\Image", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $mainImage;
// -------------------------------------------------------------------------------
// ---------------- General status and dates for the AD -------------
/**
* #var \DateTime
*
* #ORM\Column(name="createdAt", type="datetime")
* #Assert\DateTime()
*/
private $createdAt;
/**
* #var \DateTime
*
* #ORM\Column(name="updatedAt", type="datetime")
* #Assert\DateTime()
*/
private $updatedAt;
// ---------------------- AD main content -----------------------
/**
* #var string
*
* #ORM\Column(name="description", type="text", nullable=true)
* #Assert\Type("string")
*/
private $description;
// ************************* FUNCTIONS ***************************
public function __construct($thisuser)
{
$this->creator = $thisuser;
$this->createdAt = new \Datetime();
$this->updatedAt = new \Datetime();
}
/**
* Change updatedAt field just before ORM update
*
* #ORM\PreUpdate
*/
public function changeUpdatedAt()
{
$this->updatedAt = new \Datetime();
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set createdAt
*
* #param \DateTime $createdAt
*
* #return VehicleAd
*/
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
return $this;
}
/**
* Get createdAt
*
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Set updatedAt
*
* #param \DateTime $updatedAt
*
* #return VehicleAd
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* Get updatedAt
*
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* Set creator
*
* #param \Gac\UserBundle\Entity\User $creator
*
* #return VehicleAd
*/
public function setCreator(\Gac\UserBundle\Entity\User $creator)
{
$this->creator = $creator;
return $this;
}
/**
* Get creator
*
* #return \Gac\UserBundle\Entity\User
*/
public function getCreator()
{
return $this->creator;
}
/**
* Set description
*
* #param string $description
*
* #return VehicleAd
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* Get description
*
* #return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Set vehicleBrand
*
* #param \Gac\VehiclesBundle\Entity\VehicleBrand $vehicleBrand
*
* #return VehicleAd
*/
public function setVehicleBrand(\Gac\VehiclesBundle\Entity\VehicleBrand $vehicleBrand)
{
$this->vehicleBrand = $vehicleBrand;
return $this;
}
/**
* Get vehicleBrand
*
* #return \Gac\VehiclesBundle\Entity\VehicleBrand
*/
public function getVehicleBrand()
{
return $this->vehicleBrand;
}
/**
* Set vehicleModel
*
* #param \Gac\VehiclesBundle\Entity\VehicleModel $vehicleModel
*
* #return VehicleAd
*/
public function setVehicleModel(\Gac\VehiclesBundle\Entity\VehicleModel $vehicleModel)
{
$linkedBrand = $vehicleModel->getModelBrand();
$this->vehicleBrand = $linkedBrand;
$this->vehicleModel = $vehicleModel;
return $this;
}
/**
* Get vehicleModel
*
* #return \Gac\VehiclesBundle\Entity\VehicleModel
*/
public function getVehicleModel()
{
return $this->vehicleModel;
}
/**
* Set mainImage
*
* #param \Gac\MediasBundle\Entity\Image $mainImage
*
* #return VehicleAd
*/
public function setMainImage(\Gac\MediasBundle\Entity\Image $mainImage)
{
$this->mainImage = $mainImage;
return $this;
}
/**
* Get mainImage
*
* #return \Gac\MediasBundle\Entity\Image
*/
public function getMainImage()
{
return $this->mainImage;
}
}
Of course the big one, my Classified Form !
<?php
namespace Gac\AdsBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
use Symfony\Component\Form\Extension\Core\Type\DateType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Gac\MediasBundle\Form\ImageType;
use Gac\VehiclesBundle\Entity\VehicleBrand;
use Gac\VehiclesBundle\Entity\VehicleModel;
class VehicleAdType extends AbstractType
{
/**
* {#inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$globalDatas = $options['globalDatas'];
$builder->add('vehicleBrand', EntityType::class, array(
'class' => 'GacVehiclesBundle:VehicleBrand',
'choice_label' => 'brandName',
'placeholder' => 'Please select a brand :',
))
->add('vehicleModel', EntityType::class, array(
'class' => 'GacVehiclesBundle:VehicleModel',
'choice_label' => 'modelName',
))
->add('description', TextareaType::class, array('required' => false));
$builder->add('mainImage', ImageType::class);
$formModifier = function (FormInterface $form, VehicleBrand $vehicleBrand = null) {
$models = null === $vehicleBrand ? array() : $vehicleBrand->getVehicleModels();
$form->add('vehicleModel', EntityType::class, array(
'class' => 'GacVehiclesBundle:VehicleModel',
'choice_label' => 'modelName',
'choices' => $models,
));
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. Brand
$data = $event->getData();
$formModifier($event->getForm(), $data->getVehicleBrand());
}
);
$builder->get('vehicleBrand')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$vehicleBrand = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $vehicleBrand);
}
);
}
/**
* {#inheritdoc}
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Gac\AdsBundle\Entity\VehicleAd',
'globalDatas' => null,
));
}
/**
* {#inheritdoc}
*/
public function getBlockPrefix()
{
return 'gac_adsbundle_vehiclead';
}
}
The classified creation controller
<?php
namespace Gac\AdsBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Gac\MediasBundle\Entity\Image;
use Gac\MediasBundle\Form\ImageType;
use Gac\AdsBundle\Entity\VehicleAd;
use Gac\AdsBundle\Form\VehicleAdType;
use Gac\VehiclesBundle\Entity\Vehicle;
use Gac\VehiclesBundle\Form\VehicleType;
use Gac\VehiclesBundle\Entity\VehicleModel;
use Gac\VehiclesBundle\Entity\VehicleBrand;
class ManageController extends Controller
{
public function sellvehicleAction(Request $request)
{
$this->denyAccessUnlessGranted('ROLE_USER');
$thisuser = $this->get('security.token_storage')->getToken()->getUser();
$em = $this->getDoctrine()->getManager();
// Create new ad and load a "mainImage"
$classified = new VehicleAd($thisuser);
$oneImage = new Image($thisuser);
$classified->setMainImage($oneImage);
$globalDatas = $this->container->get('gac_general.globaldatas'); // Always pass globalDatas VAR to any created form in the application
$form = $this->createForm(VehicleAdType::class, $classified, array(
'globalDatas' => $globalDatas,
));
$form->handleRequest($request);
if ($request->isMethod('POST') && $form->isValid()) {
$em->persist($classified);
$em->flush();
$id = $classified->getId();
return $this->redirectToRoute('gac_ads_previewvehiclead', array('id' => $id));
}
return $this->render('ads/manage/sellvehicle.html.twig', array(
'form' => $form->createView()
));
}
}
And finally the view of my classified form
{% extends 'ads/layout.html.twig' %}
{% block bundle_content %}
<form action="{{ path('gac_ads_sellvehicle') }}" method="post">
<div class="row justify-content-center">
<div class="col-md-8">
{{ form_errors(form) }}
<div class="card mb-3">
<div class="card-body">
<p class="text-primary"><strong>{{ 'mandatory informations'|trans|capitalize }} :</strong></p>
{{ form_row(form.vehicleBrand, {'label': 'Vehicle Brand :'}) }}
{{ form_row(form.vehicleModel, {'label': 'Vehicle Model :'}) }}
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<p class="text-primary">{{ 'image'|trans|capitalize }} :</p>
<div id="toto">
{{ form_row(form.mainImage.imageFile) }}
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-body">
<p class="text-primary">{{ 'other parameters'|trans|capitalize }} ({{ 'optional'|trans }}) :</p>
<p class="text-primary">{{ 'tell us a few words about your vehicle'|trans|capitalize }} ({{ 'optional'|trans }}) :</p>
{{ form_row(form.description, {'label': 'Description :'}) }}
</div>
</div>
</div>
<div class="col-md-4">
<div class="card mb-3">
<div class="card-body">
{{ form_rest(form) }}
</div>
</div>
<button type="submit" class="btn btn-block btn-primary">{{ 'preview your ad'|capitalize }}</button>
</div>
</div>
</form>
{% endblock bundle_content %}
I must say that before asking, I did a lot of searching by myself (tests ...) and tons of google search. I know that there is some tricky stuff with Vich Uploader but I managed everything because ... it works for one file.
So the question is : can you help me make my upload and my form work when embedded in another one ?
Thanks a lot !
Regards
Try to do this in "classified" form: ...add('description', textAreaType::class) ->add('mainImage', ImageType::class); Maybe,using twice FormBuilder instance "$builder" causes problemes. In any case: see here Formulaires Imbriqués. I did so and it works great with embedded forms using vich
I have 2 entities, Reply and Post.
These are linked as ManyToMany with Post being the owner.
I have created a form for Reply to add new replies to the post, but the for some reason the replies are not showing up in the for loop in Twig.
In the database the new replies are listed and saved yet it's not displaying?
I've setup fixtures for linking replies to posts and it displays just fine in the for loop, just not for the new replies created in the form?
What am I missing here?
ReplyForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('author')
->add('body')
->add('post', 'submit');
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\DemoBundle\Entity\Reply'
));
}
public function getName()
{
return 'acme_demobundle_reply';
}
Twig
{% for reply in post.replies %}
<hr>
<p><small>Reply from <em>{{ reply.author.name }}</em> on {{ reply.createdAt|date }}</small></p>
<p>{{ reply.body }}</p>
{% endfor %}
Controller
public function createReplyAction(Request $request, $slug)
{
$post = $this->getDoctrine()->getRepository('AcmeDemoBundle:Post')
->findOneBy(array(
'slug' => $slug
));
if (null == $post) {
throw $this->createNotFoundException('Post was not found');
}
$reply = new Reply();
$reply->addPost($post);
$form = $this->createForm(new ReplyType(), $reply);
$form->handleRequest($request);
if ($form->isValid()) {
$this->getDoctrine()->getManager()->persist($reply);
$this->getDoctrine()->getManager()->flush();
return $this->redirect($this->generateUrl('acme_core_post_show', array(
'slug' => $slug
)));
}
return array(
'post' => $post,
'form' => $form->createView()
);
}
Reply entity
/**
* #ORM\ManyToMany(targetEntity="Post", mappedBy="replies")
*/
protected $post;
/**
* Constructor
*/
public function __construct()
{
$this->post = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add post
*
* #param \Acme\DemoBundle\Entity\Post $post
* #return Reply
*/
public function addPost(\Acme\DemoBundle\Entity\Post $post)
{
$this->post[] = $post;
return $this;
}
/**
* Remove post
*
* #param \Acme\DemoBundle\Entity\Post $post
*/
public function removePost(\Acme\DemoBundle\Entity\Post $post)
{
$this->post->removeElement($post);
}
/**
* Get post
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getPost()
{
return $this->post;
}
Post entity
/**
* #return Array Collection
*
* #ORM\ManyToMany(targetEntity="Reply", inversedBy="post")
* #JoinTable(name="posts_replies",
* joinColumns={#JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="reply_id", referencedColumnName="id")}
* )
*/
protected $replies;
/**
* Constructor
*/
public function __construct()
{
$this->replies = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add replies
*
* #param \Acme\DemoBundle\Entity\Reply $replies
* #return Post
*/
public function addReply(\Acme\DemoBundle\Entity\Reply $replies)
{
$replies->addPost($this);
$this->replies[] = $replies;
return $this;
}
/**
* Remove replies
*
* #param \Acme\DemoBundle\Entity\Reply $replies
*/
public function removeReply(\Acme\DemoBundle\Entity\Reply $replies)
{
$this->replies->removeElement($replies);
}
/**
* Get replies
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getReplies()
{
return $this->replies;
}
Remove the following:
$replies->addPost($this); from Post entity
and add in
$post->addReply($this); under addPost for Reply entity.
Shouldn't you construct your $replies property as an ArrayCollection in your Post Entity?
public function __construct()
{
$this->replies = new \Doctrine\Common\Collections\ArrayCollection();
}
the entity Post's annotation is wrong, try this one:
/**
* #ORM\ManyToMany(targetEntity="Reply", inversedBy="post")
* #ORM\JoinTable(name="posts_replies",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="reply_id", referencedColumnName="id")}
* )
*/
private $replies;
I am running Symfony 2.3 with Doctrine and these three (relevant) entities: Publication, Author and AuthorPublication.
Both, Author and Publication have a Many-to-One relationship to AuthorPublication (so it is basically a Many-to-Many relation between Author and Publication but I need the AuthorPublication Entity to order the authors of a publication)
I want to have a form where a user can create a new publication and choose as many authors for that publication as he wants.
I studied this article: How to Embed a Collection of Forms but I do not understand how to apply that to my problem because of the AuthorPublication entity which lies in between.
Relevant Code:
Publication
<?php
namespace ind\PubBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="ind\PubBundle\Repository\PublicationRepository")
* #ORM\Table("publications")
*/
class Publication {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $pid;
/**
* #ORM\OneToMany(targetEntity="AuthorPublication", mappedBy="publication")
* #ORM\OrderBy({"order_id" = "ASC"})
*/
protected $publicationAuthors;
//some more attributes + getters/seters
?>
Author
<?php
namespace ind\PubBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table("aid_author")
*/
class Author {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $aid;
/**
* #ORM\Column(type="string", length=255)
*/
protected $author_surname;
/**
* #ORM\Column(type="string", length=255)
*/
protected $author_forename;
/**
* #ORM\OneToMany(targetEntity="AuthorPublication", mappedBy="author")
*/
protected $authorPublication;
?>
AuthorPublication
<?php
namespace ind\PubBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table("aid_pid")
*/
class AuthorPublication {
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
protected $aid;
/**
* #ORM\Id
* #ORM\Column(type="integer")
*/
protected $pid;
/**
* #ORM\Column(type="integer")
*/
protected $order_id;
/**
* #ORM\ManyToOne(targetEntity="Publication", inversedBy="publicationAuthors")
* #ORM\JoinColumn(name="pid", referencedColumnName="pid")
*/
protected $publication;
/**
* #ORM\ManyToOne(targetEntity="Author", inversedBy="authorPublication")
* #ORM\JoinColumn(name="aid", referencedColumnName="aid")
*/
protected $author;
?>
You have to make an AuthorPublicationType form. You put field author as an 'entity' and your others fields...
You make your PublicationType including AuthorPublication (Embedded Forms).
Then you can add new AuthorPublication with prototype and very simple javascript.
Note that when you have to save your entity Publication with an authorPublication attribut null first. And after update Publication with authorPublication defined by using temporary ArrayCollection in Publication and LifecycleCallbacks for example.
EDIT : Example.
My entities are Sport OneToMany SportParam ManyToOne ConfigParam. SportParam is composed of a Sport, a ConfigParam and a value.
I want to create a new Sport with multiple ConfigParam :
SportParamType :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('ConfigParam', 'entity', array(
'class' => 'PPHBSportScoringBundle:ConfigParam',
'property' => 'nom',
'multiple' => false,
'label'=>'Paramètre'
))
->add('valeur','number',array('precision'=>2))
;
}
SportType
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('nom')
->add('SportParams', 'collection', array(
'type'=> new SportParamType(),
'allow_add' => true,
'allow_delete' => true,
'by_reference'=> false
))
;
}
My form.html.twig :
{{ form_start(form) }}
{{ form_errors(form) }}
{{ form_row(form.nom) }}
<ul class=SportParams data-prototype="{{ form_widget(form.SportParams.vars.prototype)|e }}">
{% for param in form.SportParams %}
<li>
{{ form_errors(param.ConfigParam) }}
{{ form_widget(param.ConfigParam) }}
{{ form_errors(param.valeur) }}
{{ form_widget(param.valeur) }}
</li>
{% endfor %}
</ul>
<input type="submit" class="btn btn-primary" />
{{ form_end(form) }}
My Javascript refine because it contains more code (AJAX call). It maybe contains some mistake. If it's not clear have a look to the documentation :
<script type="text/javascript">
var $container = $('ul.SportParams');
// button to add a new SportParam
var $addSportParamLink = $('Ajouter un paramètre');
var $newLinkLi = $('<li></li>').append($addSportParamLink);
$(document).ready(function() {
//delete button on each existing SportParam
$container.find('li').each(function() {
addParamFormDeleteLink($(this));
});
//add button
$container.append($newLinkLi);
// adding a new form when cliking Add button
$addSportParamLink.on('click',function(e) {
e.preventDefault(); // évite qu'un #apparaisse dans l'URL
var index = $container.children().length-1;
addParamForm($container,$newLinkLi);
var bAffiche;
return false;
});
// adding a new form SportParam
function addParamForm($container, $newLinkLi) {
var $prototype = $container.attr('data-prototype');
var newForm = $prototype.replace(/__name__/g, $container.children().length-1);
var $newFormLi = $('<li></li>').append(newForm);
$newLinkLi.before($newFormLi);
addParamFormDeleteLink($newFormLi);
}
function addParamFormDeleteLink($paramFormLi){
var $removeFormA = $('Supprimer');
$paramFormLi.append($removeFormA);
$removeFormA.on('click', function(e) {
e.preventDefault();
$paramFormLi.remove();
});
}
});
</script>
Sport Entity call's back :
/**
* sport
*
* #ORM\Table()
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\Entity(repositoryClass="SportRepository")
*/
class Sport
{
...Entity attribut...
/**
* #var ArrayCollection
*
* To save Sport without SportParams
*/
private $SportParamsTMP;
...getter and setter ...
/**
* #ORM\PrePersist
*/
public function saveSportParams()
{
$this->SportParamsTMP = $this->SportParams;
$this->SportParams = null;
}
/**
* #ORM\PostPersist
*/
public function restoreSportParams()
{
if ($this->SportParamsTMP == !null) {
$this->SportParams = $this->SportParamsTMP;
}
$this->SportParamsTMP = null;
}
}
Finally the controller 's function to add a new Sport :
public function addAction()
{
$sport = new Sport();
$form = $this->createForm(new SportType(), $sport);
$request = $this->getRequest();
if ($request->getMethod() == "POST") {
$form->bind($request);
if($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($sport);
//saving sport without parameter
$em->flush();
//updating sport with parameter
$em->flush();
return $this->redirect($this->generateUrl('pphb_sport_liste'));
}
}
return $this->render('PPHBSportScoringBundle:Championnat/Sport:add.html.twig', array(
'form' => $form->createView(),
));
}
I hope it help.
Don't know if it's the best way to do it, but it's working for me. If something to improve let me know please.
I have user model + user type, register model + register type... When i Execute it id doesn't validate user (inner data) model. Code below...
User model:
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Security\Core\User\UserInterface;
use Serializable;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Site\MainBundle\Entity\Repository\UserRepository")
*/
class User implements UserInterface, Serializable
{
/**
* #return array|\Symfony\Component\Security\Core\User\Role[]
*/
public function getRoles()
{
return array('ROLE_USER');
}
/**
*
*/
public function eraseCredentials()
{
}
/**
* #return string
*/
public function serialize()
{
return serialize(array($this->id));
}
/**
* #param string $serialized
*/
public function unserialize($serialized)
{
list ($this->id,) = unserialize($serialized);
}
/**
*
*/
public function __construct()
{
$this->isActive = false;
$this->salt = md5(uniqid(null, true));
}
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
public function getID()
{
return $this->id;
}
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255, nullable=false)
* #Assert\NotBlank(message="User name cannot be blank.")
*/
private $username;
public function setUserName($userName)
{
$this->username = $userName;
return $this;
}
public function getUserName()
{
return $this->username;
}
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=255, nullable=false)
* #Assert\NotBlank(message="Password cannot be blank.")
*/
private $password;
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function getPassword()
{
return $this->password;
}
/**
* #var string
*
* #ORM\Column(name="salt", type="string", length=255, nullable=false)
*/
private $salt;
public function setSalt($salt)
{
$this->salt = $salt;
return $this;
}
public function getSalt()
{
return $this->salt;
}
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=255, nullable=false)
* #Assert\NotBlank(message="E-Mail cannot be blank.")
* #Assert\Email(message="Invalid email address.")
*/
private $email;
public function setEmail($email)
{
$this->email = $email;
return $this;
}
public function getEmail()
{
return $this->email;
}
/**
* #var bool
*
* #ORM\Column(name="isActive", type="boolean", nullable=false)
*/
private $isActive;
public function setIsActive($isActive)
{
$this->isActive = $isActive;
return $this;
}
public function getIsActive()
{
return $this->isActive;
}
}
User Type:
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('username', 'text', array('label' => 'User name'))
->add(
'password',
'repeated',
array(
'label' => 'Password',
'first_name' => 'password',
'second_name' => 'confirm',
'type' => 'password'
)
)
->add('email', 'text', array('label' => 'E-Mail'));
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Site\MainBundle\Entity\User',
'required' => false
)
);
}
/**
* #return string
*/
public function getName()
{
return 'main_user';
}
}
Register model:
use Symfony\Component\Validator\Constraints as Assert;
use Site\MainBundle\Entity\User;
/**
* Register
*/
class Register
{
/**
* #var Site\MainBundle\Entity\User
*
* #Assert\Type(type="Site\MainBundle\Entity\User")
* #Assert\Valid()
*/
protected $user;
public function setUser(User $user)
{
$this->user = $user;
return $this;
}
public function getUser()
{
return $this->user;
}
/**
* #var boolean
*
* #Assert\NotBlank(message="No terms accepted.")
* #Assert\True(message="You have to accept terms to be registered.")
*/
protected $terms;
public function setTerms($terms)
{
$this->terms = (Boolean)$terms;
return $this;
}
public function getTerms()
{
return $this->terms;
}
}
Register Type:
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Site\MainBundle\Form\UserType;
class RegisterType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('user', new UserType())
->add('terms', 'checkbox');
}
/**
* #param OptionsResolverInterface $resolver
*/
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(
array(
'data_class' => 'Site\MainBundle\Entity\Register',
'required' => false
)
);
}
/**
* #return string
*/
public function getName()
{
return 'main_register';
}
}
And controller with twig. Part of twig:
{% block page_content %}
<form method="post" action="{{ path('main_register') }}">
{{ form_errors(formRegister.user.username) }}
{{ form_row(formRegister.user.username) }}
{{ form_errors(formRegister.user.password.password) }}
{{ form_row(formRegister.user.password.password) }}
{{ form_errors(formRegister.user.password.confirm) }}
{{ form_row(formRegister.user.password.confirm) }}
{{ form_errors(formRegister.user.email) }}
{{ form_row(formRegister.user.email) }}
{{ form_errors(formRegister.terms) }}
{{ form_row(formRegister.terms) }}
<button type="submit">Register</button>
</form>
{% endblock %}
Register Controller:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Site\MainBundle\Form\RegisterType;
use Site\MainBundle\Entity\Register;
/**
* RegisterController
*/
class RegisterController extends Controller
{
public function defaultAction()
{
$formRegister = $this->createForm(new RegisterType(), new Register());
if ($this->getRequest()->isMethod('post')) {
$formRegister->handleRequest($this->getRequest());
if ($formRegister->isValid()) {
echo 'form is valid';
}
}
$twig = 'SiteMainBundle:register:default.html.twig';
$data = array(
'formRegister' => $formRegister->createView()
);
return $this->render($twig, $data);
}
}
Where can be problem? Help please with it.
Updated
echo "<pre>";
echo $formRegister->getErrorsAsString();
echo '</pre>';
says:
user:
username:
No errors
password:
password:
No errors
confirm:
No errors
email:
No errors
terms:
ERROR: No terms accepted.
ERROR: You have to accept terms to be registered.
As you can see model User is not validated AT ALL... =\
Found, finally.
Bug is in this part. I wanted to disable this way browser validation...
'required' => false
BUT. This "guys" (I mean developers),compare two things in one setting. So if we set 'required' to false our fields won't be validated. And if true it will be validate BUT ALSO WITH browser validator.. =\ Maybe someone know how to avoid it (browser validation) NOT by editing twig file (some options or other setting)?
PS: To avoid browser i changed twig.
<form method="post" action="{{ path('main_register') }}" novalidate>
If you wanna do it by code, in your Controller, your code should be like:
class RegisterController extends Controller
{
public function defaultAction()
{
$formRegister = $this->createForm(new RegisterType(), new Register(), array(
'attr'=> array('novalidate'=>'novalidate'),
));
With the therd parámeter of "createForm", you can modified the form (for example: acction, class, etc).
I normaly use:
$form = $this->createForm(new RegistroType(), $usuario, array(
'action' => $this->generateUrl('jgo_registro'),
'method' => 'POST',
'attr'=> array('novalidate'=>'novalidate'),
));