Good morning!
I have an ads entity which has a title, a slug and other information, to this ads entity I added an entity named images and the image to just a single caption field which I added to the entity ads as CollectionType. But when I validate my form, only the last image is saved.
Please help me.
This is my entity ads
<?php
namespace AppEntity;
use AppRepositoryAdsListingsRepository;
use DoctrineCommonCollectionsArrayCollection;
use DoctrineCommonCollectionsCollection;
use DoctrineORMMapping as ORM;
use SymfonyComponentStringSluggerAsciiSlugger;
#[ORMEntity(repositoryClass: AdsListingsRepository::class)]
#[ORMHasLifecycleCallbacks()]
class AdsListings
{
const PRICENOTE = [
"" => "",
"Ferme" => "Ferme",
"Négociable" => "Négociable",
"À partir de" => "À partir de",
"Par heure" => "Par heure",
"Par jour" => "Par jour",
"Par semaine" => "Par semaine",
"Par mois" => "Par mois",
"Par an" => "Par an",
];
const DEVISE = [
"XOF/Franc CFA" => "XOF/Franc CFA",
"GHS/ Ghana Cedi" => "GHS/ Ghana Cedi",
"Euro / €" => "Euro / €",
"Dollar USD / $" => "Dollar USD / $",
];
#[ORMId]
#[ORMGeneratedValue]
#[ORMColumn]
private ?int $id = null;
#[ORMColumn(length: 255)]
private ?string $title = null;
#[ORMColumn(length: 255)]
private ?string $slug = null;
#[ORMColumn(nullable: true)]
private ?float $price = null;
#[ORMColumn(length: 100, nullable: true)]
private ?string $devise = null;
#[ORMColumn(length: 100, nullable: true)]
private ?string $priceNote = null;
#[ORMOneToOne(inversedBy: 'adsListings', cascade: ['persist', 'remove'])]
#[ORMJoinColumn(nullable: false)]
private ?References $reference = null;
#[ORMOneToMany(targetEntity: Images::class, mappedBy: 'Ads', cascade: ['persist', 'remove'],orphanRemoval: true)]
private Collection $images;
public function __construct()
{
$this->images = new ArrayCollection();
}
#[ORMPrePersist()]
#[ORMPreUpdate()]
public function initializeSlug()
{
if (empty($this->slug)){
$slugger = new AsciiSlugger();
$this->slug = $slugger->slug($this->title)->lower();
}
return $this;
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): static
{
$this->title = $title;
return $this;
}
public function getSlug(): ?string
{
return $this->slug;
}
public function setSlug(string $slug): static
{
$this->slug = $slug;
return $this;
}
public function getPrice(): ?float
{
return $this->price;
}
public function setPrice(?float $price): static
{
$this->price = $price;
return $this;
}
public function getDevise(): ?string
{
return $this->devise;
}
public function setDevise(?string $devise): static
{
$this->devise = $devise;
return $this;
}
public function getPriceNote(): ?string
{
return $this->priceNote;
}
public function setPriceNote(?string $priceNote): static
{
$this->priceNote = $priceNote;
return $this;
}
public function getReference(): ?References
{
return $this->reference;
}
public function setReference(References $reference): static
{
$this->reference = $reference;
return $this;
}
/**
* @return Collection<int, Images>
*/
public function getImages(): Collection
{
return $this->images;
}
public function addImage(Images $image): static
{
if (!$this->images->contains($image)) {
$this->images->add($image);
$image->setAds($this);
}
return $this;
}
public function removeImage(Images $image): static
{
if ($this->images->removeElement($image)) {
// set the owning side to null (unless already changed)
if ($image->getAds() === $this) {
$image->setAds(null);
}
}
return $this;
}
}
here is my AdsType
<?php
namespace AppForm;
use AppEntityAdsListings;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreTypeChoiceType;
use SymfonyComponentFormExtensionCoreTypeCollectionType;
use SymfonyComponentFormExtensionCoreTypeNumberType;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
class AdsListingsFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title', TextType::class,[
'label' => "Titre de mon annonce :"
])
->add('price', NumberType::class,[
'label' => "Prix / Montant :",
'required' => false,
])
->add('devise', ChoiceType::class,[
'label' => "Devise :",
'choices' => AdsListings::DEVISE,
'required' => false,
])
->add('PriceNote', ChoiceType::class,[
'choices' => AdsListings::PRICENOTE,
'label' => "Note sur le prix :"
])
->add('images', CollectionType::class,[
'entry_type' => ImagesFormType::class,
'allow_add' => true,
'allow_delete' => true,
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => AdsListings::class,
]);
}
}
here is my ImagesEntity
<?php
namespace AppEntity;
use AppRepositoryImagesRepository;
use DoctrineORMMapping as ORM;
#[ORMEntity(repositoryClass: ImagesRepository::class)]
class Images
{
#[ORMId]
#[ORMGeneratedValue]
#[ORMColumn]
private ?int $id = null;
#[ORMColumn(length: 255, nullable: false)]
private ?string $caption = null;
#[ORMManyToOne(inversedBy: 'images')]
private ?AdsListings $Ads = null;
public function getId(): ?int
{
return $this->id;
}
public function getCaption(): ?string
{
return $this->caption;
}
public function setCaption(?string $caption): self
{
$this->caption = $caption;
return $this;
}
public function getAds(): ?AdsListings
{
return $this->Ads;
}
public function setAds(?AdsListings $Ads): self
{
$this->Ads = $Ads;
return $this;
}
}
here is my ImagesType
<?php
namespace AppForm;
use AppEntityAdsListings;
use AppEntityImages;
use SymfonyBridgeDoctrineFormTypeEntityType;
use SymfonyComponentFormAbstractType;
use SymfonyComponentFormExtensionCoreTypeTextType;
use SymfonyComponentFormFormBuilderInterface;
use SymfonyComponentOptionsResolverOptionsResolver;
class ImagesFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('caption', TextType::class, [
'label' => "Caption"
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => Images::class,
]);
}
}
Here is my Twig view
{% extends 'base.html.twig' %}
{% block title %} Louer un appartement {% endblock %}
{% block stylesheets %}
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/summernote-lite.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ asset('assets/leaflet/leaflet.css') }}">
<style>
#detailsMap{
height: 350px;
}
</style>
{% include '_partials/_users/_nav.html.twig' %}
{% form_theme appartementRentalForm _self %}
<nav class="navbar navbar-expand-lg bg-body-light navbar-user-menu">
<div class="container">
<div class="collapse navbar-collapse" id="navbarNavDropdown">
{% include '_partials/_users/_nav-users-menu.html.twig' %}
<ul class="navbar-nav ms-auto">
<li class="nav-item nav-page">
<a class="nav-link nav-item-in" href="{{ path("app_user_profil")}}">
Mes annonces <i class="fa fa-add"></i>
</a>
</li>
</ul>
</div>
</div>
</nav>
{% include "_partials/_flash.html.twig" %}
<div class="container-fluid container-body">
<div class="container py-4 ">
<div class="row">
<div class="col-sm-12">
<strong class="div-h4 h2">Créez votre annonce</strong>
</div>
<div class="col-sm-10 container-fluid mt-4 offset-sm-1 ">
<div class="">
{{ form_start(appartementRentalForm) }}
<fieldset class="pb-5">
<legend>INFORMATION DE BASE</legend>
<div class="container">
<label for="">{{ form_label(appartementRentalForm.title) }}<sup class="text-danger">*</sup></label>
{{ form_widget(appartementRentalForm.title) }}
<p>
<i>
Les mots-clés employés dans votre titre sont importants. Ils indiquent aux acheteurs et surtout, à notre moteur de recherche, le niveau de pertinence de votre annonce. Faites de bons choix !
</i>
</p>
<div class="row">
<div class="col-4">
<label for="">{{ form_label(appartementRentalForm.price) }}<sup class="text-danger">*</sup></label>
{{ form_widget(appartementRentalForm.price) }}
</div>
<div class="col-3">
<label for="">{{ form_label(appartementRentalForm.devise) }}</label>
{{ form_widget(appartementRentalForm.devise) }}
</div>
<div class="col-2">
<label for="">{{ form_label(appartementRentalForm.PriceNote) }}</label>
{{ form_widget(appartementRentalForm.PriceNote) }}
</div>
</div>
</div>
</fieldset>
<fieldset class="pb-5">
<legend>ÉTAT & DISPONIBILITÉ</legend>
<div class="container">
<div class="row">
<div class="col-12 form-check-inline">
<div class="row">
{# <div class="col-2">État : <sup class="text-danger">*</sup></div>
<div class="col-9">{{ form_widget(appartementRentalForm.state, {'label_attr': {'class': 'radio-inline'}}) }}</div> #}
</div>
</div>
<div class="col-12 row mt-3">
{# <div class="col-2">{{ form_label(appartementRentalForm.year) }} <sup class="text-danger">*</sup></div>
<div class="col-2">{{ form_widget(appartementRentalForm.year) }}</div> #}
</div>
<div class="col-12 row mt-3">
{# <div class="col-2">Date de disponibilité :</div>
<div class="col-3">{{ form_widget(appartementRentalForm.disponibility) }}</div> #}
</div>
</div>
</div>
</fieldset>
<fieldset class="pb-5">
<legend>COMMODITÉ & OPTION</legend>
<div class="container">
<div class="row">
{# Nombre de pièces :
<div class="col-12 row mt-3">
<div class="col-2">{{ form_label(appartementRentalForm.disponibility) }} </div>
<div class="col-3">{{ form_widget(appartementRentalForm.disponibility) }}</div>
</div>
#}
<div class="col-12 form-check-inline">
<div class="row">
<div class="col-2">Ameublement :</div>
{# <div class="col-9">{{ form_widget(appartementRentalForm.furniture, {'label_attr': {'class': 'radio-inline'}}) }}</div> #}
</div>
</div>
<div class="col-12 mt-3 form-check-inline">
<div class="row">
<div class="col-2">Animaux acceptés? : </div>
{# <div class="col-9">{{ form_widget(appartementRentalForm.pet, {'label_attr': {'class': 'radio-inline'}}) }}</div> #}
</div>
</div>
<div class="col-12 mt-3">
<div class="row">
{# <div class="col-2">
<label for="">{{ form_label(appartementRentalForm.orientation) }}</label>
</div>
<div class="col-4">{{ form_widget(appartementRentalForm.orientation) }}</div> #}
</div>
</div>
<div class="col-12 mt-3">
<div class="row">
{# <div class="col-2">
<label for="">{{ form_label(appartementRentalForm.homeOptions) }}</label>
</div>
<div class="col-10">{{ form_widget(appartementRentalForm.homeOptions) }}</div> #}
</div>
</div>
<div class="col-12 mt-3">
<div class="row">
{# <div class="col-2">
<label for="">{{ form_label(appartementRentalForm.cookOptions) }}</label>
</div>
<div class="col-10">{{ form_widget(appartementRentalForm.cookOptions) }}</div> #}
</div>
</div>
</div>
{#
*
* autre options relation
#}
</div>
</fieldset>
<fieldset class="pb-5">
<legend>PHOTOS</legend>
<div class="container">
<div class="row">
{{ form_widget(appartementRentalForm.images) }}
</div>
</div>
</fieldset>
<fieldset class="pb-5">
<legend>LIEU DE MON ANNONCE</legend>
<div class="container">
{# <div class="row">
<div class="col-sm-6">
<div class="row mt-4">
<div class="col-4">
<label for="">{{ form_label(appartementRentalForm.country) }}</label>
{{ form_widget(appartementRentalForm.country) }}
</div>
<div class="col-8">
<label for="">{{ form_label(appartementRentalForm.city) }}</label>
{{ form_widget(appartementRentalForm.city) }}
</div>
</div>
<div class="row">
<div class="col-4">
<label for="">{{ form_label(appartementRentalForm.zip) }}</label>
{{ form_widget(appartementRentalForm.zip) }}
</div>
<div class="col-8">
<label for="">{{ form_label(appartementRentalForm.address) }}</label>
{{ form_widget(appartementRentalForm.address) }}
</div>
</div>
<label for="">{{ form_label(appartementRentalForm.longitude) }}</label>
{{ form_widget(appartementRentalForm.longitude) }}
<label for="">{{ form_label(appartementRentalForm.latitude) }}</label>
{{ form_widget(appartementRentalForm.latitude) }}
</div>
<div class="col-sm-6">
<div id="detailsMap">
</div>
</div>
</div> #}
<p class="mt-2">
<i>
Une annonce immobilière bien localisée peut attirer de nombreux clients potentiels.
</i>
</p>
</div>
</fieldset>
<fieldset class="pb-5">
<legend>DESCRIPTION</legend>
<div class="container">
<div class="row">
{# <div class="col-12"><label for="">{{ form_label(appartementRentalForm.description) }}</label></div>
<div class="col-12">{{ form_widget(appartementRentalForm.description) }}</div> #}
</div>
<button class="btn btn-primary mt-4" type="submit"> Publier mon annonce</button>
</div>
</fieldset>
{{ form_end(appartementRentalForm) }}
</div>
</div>
</div>
</div>
</div>
{% include '_partials/_footer.html.twig' %}
{% endblock %}
{% block _ads_listings_form_images_entry_row %}
<div class="col-2 mb-2">
{{ form_widget(form) }}
</div>
{% endblock %}
{% block _ads_listings_form_images_entry_widget %}
<div class="form-group" id="block_{{ id }}">
<div class="row">
<div class="col-12">
<div class="row">
{#<div class="col-12">
<input class="form-control" type="file" id="fileInput_{{ id }}" name="{{ full_name }}[imageFile]" accept="image/*" onchange="previewImage(this, '{{ id }}')">
<img id="preview_{{ id }}" class="img-thumbnail" style="display: none;">
<p id="fileName_{{ id }}" class="mt-2"></p>
</div> #}
<div class="col-12">
{{ form_widget(form.caption) }}
</div>
</div>
</div>
<div class="col-12 mt-1">
<button class="btn btn-danger col-12" data-action="delete" data-target="#block_{{ id }}" type="button">Supprimer</button>
</div>
</div>
</div>
{% endblock %}
{% block _ads_listings_form_images_widget %}
<input type="hidden" id="counter" value="0">
<div class="col-12 mb-2">
<button class="btn btn-info" id="add-image" type="button"> Ajouter une image</button>
</div>
<div class="col-12 row">
{{ form_widget(form)}}
</div>
{% endblock %}
{% block javascripts %}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/summernote-lite.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>
<script src="{{ asset('assets/leaflet/leaflet.js') }}" type="application/javascript"></script>
<script src="{{ asset('assets/leaflet/gogaga-leaflet.js') }}" type="application/javascript"></script>
<script>
$('#apartement_rental_form_homeOptions').select2({
placeholder: 'Selection une option'
});
$('#apartement_rental_form_cookOptions').select2({
placeholder: 'Selection une option de cuisine'
});
$('#apartement_rental_form_description').summernote({
placeholder: 'Description',
tabsize: 2,
height: 120,
toolbar: [
['font', ['bold', 'underline', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
]
});
$("#ads_listings_form_images").addClass("col-12 row")
$('#add-image').click(function() {
const index = +$("#counter").val();
const tmpl = $('#ads_listings_form_images').data('prototype').replace(/___name__/g, index);
$('#ads_listings_form_images').append(tmpl);
$('#counter').val(index +1);
handleDeleteButtons();
desableBtn();
});
function desableBtn(){
const count = +$('#ads_listings_form_images div.col-2.mb-2').length;
if (count >= 6){
$("#add-image").attr("disabled" , true);
}else if(count < 6){
$("#add-image").attr("disabled" , false);
}
}
function handleDeleteButtons() {
$('button[data-action="delete"]').click(function () {
const target = this.dataset.target;
$(target).parent().remove();
updateCounter();
desableBtn();
});
}
function updateCounter() {
const count = +$('#ads_listings_form_images div.col-2.mb-2').length;
$('#counter').val(count);
}
handleDeleteButtons();
updateCounter();
desableBtn();
</script>
<script>
function previewImage(input, id) {
const preview = document.getElementById('preview_' + id);
const fileName = document.getElementById('fileName_' + id);
if (input.files && input.files[0]) {
const reader = new FileReader();
reader.onload = function (e) {
preview.src = e.target.result;
preview.style.display = 'block';
};
reader.readAsDataURL(input.files[0]);
//fileName.textContent = input.files[0].name;
} else {
preview.style.display = null;
preview.src = null;
fileName.textContent = '';
}
}
</script>
{% endblock %}
Here is my Controller
#[Route('/louer-un-appartement', name: 'new')]
public function index(Request $request, EntityManagerInterface $entityManager): Response
{
$ads = new AdsListings();
$appartementRentalForm = $this->createForm(AdsListingsFormType::class, $ads);
$appartementRentalForm->handleRequest($request);
if ($appartementRentalForm->isSubmitted() && $appartementRentalForm->isValid()){
foreach ($ads->getImages() as $image){
$image->setAds($ads);
$entityManager->persist($image);
}
$reference = new References();
$ads->setReference($reference);
$entityManager->persist($ads);
$entityManager->flush();
$this->addFlash('success', "Félicitation, votre annonce a été publier avec succès !</br> Il y a des annonces qui mettent du temps à être affichées sur le site, car elles sont vérifiées par les administrateurs de GoGaGaa. ");
}
return $this->render('propertyRental/appartementRental.html.twig', [
'appartementRentalForm' => $appartementRentalForm->createView()
]);
}
Help me find the error please
New contributor