I have an entity "Support"on witch i want to link a file(PDF, DOC,...). I've followed the documentations and some videos to help, but i alway have this error :
SQLSTATE[23000]: Integrity constraint violation: 1048 The field 'filename' can't be empty (null)
and i don't have any persistance in my database.
here is my entity "Support" : `
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass="App\Repository\SupportRepository")
* #Vich\Uploadable
*/
class Support
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #var string|null
* #ORM\Column(type="string", length=255)
*
*/
private $filename;
/**
* #Vich\UploadableField(mapping="support_file", fileNameProperty="filename", size="fileSize")
* #var File|null
*/
private $supportFile;
/**
* #ORM\Column(type="integer")
*
* #var int|null
*/
private $fileSize;
/**
* #ORM\Column(type="datetime")
*
* #var \DateTimeInterface|null
*/
private $updatedAt;
/**
* #ORM\Column(type="string", length=255)
*/
private $titre;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $desciption;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Salle", mappedBy="supports")
*/
private $salles;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\TypedeSupport", inversedBy="supports")
* #ORM\JoinColumn(nullable=false)
*/
private $typedeSupport;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Matiere", inversedBy="supports")
*/
private $matiere;
public function __construct()
{
$this->salles = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitre(): ?string
{
return $this->titre;
}
public function setTitre(string $titre): self
{
$this->titre = $titre;
return $this;
}
public function getUrl(): ?string
{
return $this->url;
}
public function setUrl(string $url): self
{
$this->url = $url;
return $this;
}
public function getDesciption(): ?string
{
return $this->desciption;
}
public function setDesciption(?string $desciption): self
{
$this->desciption = $desciption;
return $this;
}
/**
* #return Collection|Salle[]
*/
public function getSalles(): Collection
{
return $this->salles;
}
public function addSalle(Salle $salle): self
{
if (!$this->salles->contains($salle)) {
$this->salles[] = $salle;
$salle->addSupport($this);
}
return $this;
}
public function removeSalle(Salle $salle): self
{
if ($this->salles->contains($salle)) {
$this->salles->removeElement($salle);
$salle->removeSupport($this);
}
return $this;
}
public function getTypedeSupport(): ?TypedeSupport
{
return $this->typedeSupport;
}
public function setTypedeSupport(?TypedeSupport $typedeSupport): self
{
$this->typedeSupport = $typedeSupport;
return $this;
}
public function getMatiere(): ?Matiere
{
return $this->matiere;
}
public function setMatiere(?Matiere $matiere): self
{
$this->matiere = $matiere;
return $this;
}
public function getFilename(): ?string
{
return $this->filename;
}
public function setFilename(?string $filename): Support
{
$this->fileName = $filename;
return $this;
}
/**
* #return null|File
*/
public function getSupportFile(): ?File
{
return $this->supportFile;
}
public function setSupportFile(?File $supportFile = null): Support
{
$this->supportFile = $supportFile;
if (null !== $supportFile) {
// 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
$this->updatedAt = new \DateTimeImmutable();
}
return $this;
}
public function setFileSize(?int $fileSize): void
{
$this->fileSize = $fileSize ;
}
public function getFileSize(): ?int
{
return $this->fileSize;
}
}
here my "newAction" to create a new "Support" from the controller: `
public function new(Request $request, $type, $salle, $matiere): Response
{
$user = $this->getUser();
if ($user->getRoles()==array('ROLE_PROF')) {
$support = new Support();
$type_de_support = $this->typedeSupportRepository->findOneBy(['intitule'=>$type]);
$salle_de_support = $this->salleRepository->findOneBy(['nom'=>$salle]);
$matiere_de_support = $this->matiereRepository->findOneBy(['intitule'=>$matiere]);
$support->setTypedeSupport($type_de_support)
->addSalle($salle_de_support)
->setMatiere($matiere_de_support);
$form = $this->createForm(SupportType::class, $support);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
dump($support);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($support);
$entityManager->flush();
return $this->redirectToRoute('support_index',["type"=>$type,"salle"=>$salle ,"matiere"=>$matiere]);
}`
here is my 'SupportType' to generate the Form: `
namespace App\Form;
use App\Entity\Support;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class SupportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('titre')
->add('supportFile', FileType::class)
->add('desciption')
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Support::class,
]);
}
}
and then the configuration File :
vich_uploader:
db_driver: orm
mappings:
support_file:
uri_prefix: /supports/teste
upload_destination: '%kernel.project_dir%/public/supports/teste'
namer: vich_uploader.namer_uniqid
the view :
<div class="col-md-4">
<div class="card text-white bg-primary mb-3">
<div class="card-header text-center" style="font-size: 2.3rem;">Modifier le support</div>
<div class="card-body">
<p class="card-text">
{% if error is defined and error %}
<div class=" alert alert-danger" style="font-size: 1.3rem;">
{{error}}
</div>
{% endif %}
{% for message in app.flashes('success') %}
<div class=" alert alert-success" style="font-size: 1.7rem;">
{{message}}
</div>
{% endfor %}</p>
<form method="post" action="" accept-charset="UTF-8">
<input type="hidden" name="action" value="users/send-password-reset-email">
<div class="form-group">
{{form_row(form.titre)}}
</div>
<div class="form-group">
{{form_row(form.supportFile)}}
</div>
<div class="form-group">
{{form_row(form.desciption)}}
</div>
{{ form_rest(form) }}
<input type="submit" value="{{ button_label|default('Save') }}" class="btn btn-success col-12">
</form>
<br><br>
</div>
</div>
</div>
despite the videos and forum i used, i've not found any solution. i need your Help
thanks :-)
The input "supportFile" need to be VichFileType::class and not a FileType::class
class SupportType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('titre')
->add('supportFile', VichFileType::class)
->add('desciption')
;
}
...
}
doc : https://github.com/dustin10/VichUploaderBundle/blob/master/docs/form/vich_file_type.md
Related
I've installed and configured the VichUploader bundle on my Symfony (V5.2) project and yet, I cannot display the image I've uploaded and stored in my "$imageName" field of my DB. So, Could you give me some solutions, explanations or even some tracks to help me, please?
I give you my code to make easier the understanding :
vich_uploader.yaml :
vich_uploader:
db_driver: orm
mappings:
announcement_image:
uri_prefix: /images/announcements
upload_destination: '%kernel.project_dir%/public/images/announcements'
namer: Vich\UploaderBundle\Naming\SmartUniqueNamer
inject_on_load: false
delete_on_update: true
delete_on_remove: true
AnnoucementController (Well, I upload my image with the new method and everything work fine. But, when I execute the method siwOne which give all the of one specific announcement, the image is not displayed) :
<?php
namespace App\Controller;
use App\Entity\Announcement;
use App\Form\AnnouncementType;
use App\Repository\AnnouncementRepository;
use App\Repository\UsersRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class AnnouncementController extends AbstractController
{
/**
* #Route("/announcement/show/list", name="show_list_announcement")
* #param AnnouncementRepository $ar
* #return Response
*/
public function showList(AnnouncementRepository $ar): Response
{
$announces = $ar->findAll();
return $this->render("post/announcement/showList.html.twig", ["announces" => $announces]);
}
/**
* #Route("/announcement/show/one/{id}", name="show_one_announcement", requirements={"id"="\d+"})
* #param $id
* #param AnnouncementRepository $ar
* #return Response
*/
public function showOne($id, AnnouncementRepository $ar): Response
{
$announcement = $ar->findOneBy(['id' => $id]);
return $this->render("post/announcement/showOne.html.twig", ['announcement' => $announcement]);
}
/**
* #Route("/announcement/new", name="new_announcement")
* #param Request $request
* #param UsersRepository $ur
* #return Response
*/
public function new(Request $request, UsersRepository $ur): Response
{
$user = $this->getUser();
$announcement = new Announcement();
$form = $this->createForm(AnnouncementType::class, $announcement);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$announcement->setUser($user);
$em = $this->getDoctrine()->getManager();
$em->persist($announcement);
$em->flush();
return $this->redirectToRoute("home");
}
return $this->render("post/announcement/create.html.twig",
["AnnouncementForm" => $form->createView()]);
}
}
When I execute the "dd()" method in the showOne function, it displays the array below with the imageFile field set to null (Is there the problem which I looking for to solve ?) :
AnnouncementController.php on line 37:
App\Entity\Announcement {#4499 ▼
-id: 10
-title: "Ma 1ère annonces"
-content: """
Le lorem ipsum (également appelé faux-texte, lipsum, ou bolo bolo1) est, en imprimerie, une suite de mots sans signification utilisée à titre provisoire pour ca ▶
Généralement, on utilise un texte en faux latin (le texte ne veut rien dire, il a été modifié), le Lorem ipsum ou Lipsum. L'avantage du latin est que l'opérateu ▶
"""
-imageFile: null
-imageName: "330px-horseshoe-falls-2-mt-field-national-park-61165eca3ce95432200637.jpg"
-user: Proxies\__CG__\App\Entity\User {#579 ▶}
-created_At: DateTime #1628856009 {#755 ▼
date: 2021-08-13 12:00:09.0 UTC (+00:00)
}
-updated_At: DateTime #1628856010 {#4887 ▼
date: 2021-08-13 12:00:10.0 UTC (+00:00)
}
}
AnnouncementType.php :
<?php
namespace App\Form;
use App\Entity\Announcement;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Vich\UploaderBundle\Form\Type\VichImageType;
class AnnouncementType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('title', TextType::class,['required' => true, 'label' => 'Titre'])
->add('content', TextareaType::class,['required' => true, 'label' => 'Contenu'])
->add('imageFile', VichImageType::class, ['required' => true, 'label' => 'Illustration']);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Announcement::class,
]);
}
}
Announcement.php (this class extends from post and has no attribute because a post could be an announcement but also a recipe or an article. Hence, I have to make an announcement class separately to make sense and respect the DB schema I've made before starting coding) :
<?php
namespace App\Entity;
use App\Repository\AnnouncementRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=AnnouncementRepository::class)
*/
class Announcement extends Post
{
}
Post.php :
<?php
namespace App\Entity;
use App\Repository\PostRepository;
use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostRepository", repositoryClass=PostRepository::class)
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="post_type", type="string")
* #ORM\DiscriminatorMap({"post"="Post", "announcement"="Announcement"})
* #Vich\Uploadable()
*/
class Post
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Assert\NotBlank
*/
private $title;
/**
* #ORM\Column(type="text")
* #Assert\NotBlank
*/
private $content;
/**
* #Vich\UploadableField(mapping="announcement_image", fileNameProperty="imageName")
*
* #var File|null
*/
private $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string|null
*/
private $imageName;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="posts")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $user;
/**
* #ORM\Column(type="datetime")
*/
private $created_At;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $updated_At;
/**
* Post constructor.
*/
public function __construct()
{
$this->created_At = new DateTime();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): self
{
$this->title = $title;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
/**
* #return File|null
*/
public function getImageFile(): ?File
{
return $this->imageFile;
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|null $imageFile
*/
public function setImageFile(?File $imageFile = null): void
{
$this->imageFile = $imageFile;
if (null !== $imageFile) {
// 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
$this->updated_At = new \DateTimeImmutable();
}
}
/**
* #return string|null
*/
public function getImageName(): ?string
{
return $this->imageName;
}
/**
* #param string|null $imageName
* #return Post
*/
public function setImageName(?string $imageName): Post
{
$this->imageName = $imageName;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->created_At;
}
public function setCreatedAt(\DateTimeInterface $Created_At): self
{
$this->created_At = $Created_At;
return $this;
}
/**
* #return mixed
*/
public function getUpdatedAt()
{
return $this->updated_At;
}
/**
* #param mixed $updated_At
*/
public function setUpdatedAt($updated_At): void
{
$this->updated_At = $updated_At;
}
}
showOne.html.twig :
{% extends 'base.html.twig' %}
{% block body %}
<div class="container mt-5 ml-4">
<h3>{{ announcement.title }}</h3>
<p>Publié le {{ announcement.createdAt | date("d/m/Y") }}</p>
<img src="{{ vich_uploader_asset(announcement, 'imageFile') }}" alt="{{ announcement.imageName }}" style="width:100%; height: auto;" >
<p>{{ announcement.content }}</p>
</div>
{% endblock %}
{% block title %}
Annonce - {{ announcement.title }}
{% endblock %}
As I said, any help would be much appreciated!
Regards.
YT
I'm trying for many hours to persist my embed collections in the DB. It doesn't work and even do something different.
I have a entity User OneToMany with the entity Address.
I successfully have the embed form but I cannot save it in the DB.
Even worse, when I add the property by_reference => false to my embed field, it delete the existing address I had :/ .
I don't know what I do wrong.
I tried to add the setAdress to my User entity but it's not helping.
I tried to add the Cascade persist but also not working.
It's probably an issue with my controller (edit_profil) but I don't see which one.
I'm now getting the error Could not determine access type for property "address" in class "App\Entity\User".
UPDATE1
with the by_reference set to false, I have this error now : Expected argument of type "App\Entity\Address or null", "instance of Doctrine\ORM\PersistentCollection" given at property path "address".
UPDATE2
I removed the setter because I have the add and remove method. I also used plural and singural for address as it's done with tag in the documentation.
Now I see the form address is not sent.(it even delete the existing one)
(I remove in the code what's not necessary)
Here are my files :
RegistrationFormType
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\CallbackTransformer;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('roles',ChoiceType::class,[
'choices'=>[
'Specialist'=>'Role_Specialist',
'Parent'=>'Role_Parent',
'Center'=>'Role_Center',],
'label'=>"Je m'inscris en tant que"])
->add('name')
//...//
->add('addresses',CollectionType::class,[
'entry_type' => AddressType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' =>false,
])
;
User Entity :
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
//...//
/**
* #ORM\OneToMany(targetEntity=Address::class, mappedBy="user", orphanRemoval=true, cascade={"persist"})
*/
private $addresses;
public function __construct()
{
$this->category = new ArrayCollection();
$this->schedules = new ArrayCollection();
$this->bookings = new ArrayCollection();
$this->addresses = new ArrayCollection();
}
//...//
/**
* #return Collection|Addresses[]
*/
public function getAddresses(): Collection
{
return $this->addresses;
}
public function addAddress(Address $address): self
{
$address->setUser($this);
$this->adresse->add($address);
// if (!$this->addresses->contains($address)) {
// $this->addresses[] = $address;
// $address->setUser($this);
// }
return $this;
}
public function removeAddress(Address $address): self
{
if ($this->addresses->removeElement($address)) {
// set the owning side to null (unless already changed)
if ($address->getUser() === $this) {
$address->setUser(null);
}
}
return $this;
}
}
Registration Controller
<?php
namespace App\Controller;
use App\Entity\Address;
use App\Entity\User;
use App\Form\AddressType;
use App\Form\RegistrationFormType;
use App\Security\EmailVerifier;
use App\Security\LoginFormAuthenticator;
use Doctrine\ORM\EntityManager;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
class RegistrationController extends AbstractController
{
private $emailVerifier;
public function __construct(EmailVerifier $emailVerifier)
{
$this->emailVerifier = $emailVerifier;
}
//...//
/**
* #Route("/profil/{id}", name="edit_account")
*/
public function editUser(Request $request,int $id)
{
$entityManager = $this->getDoctrine()->getManager();
//Récupération de l'entité User avec l'ID passé et création du formulaire
$repoUser = $this->getDoctrine()
->getRepository(User::class);
$user = $repoUser->findOneBy([
'id'=>$id,
]);
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash('success', 'User Created! Knowledge is power!');
return $this->redirectToRoute('edit_account',[
'id'=>$id,
]);
}
return $this->render('profil/edit_profil.html.twig', [
'registrationForm' => $form->createView(),
]);
}
/**
* #Route("/verify/email", name="app_verify_email")
*/
public function verifyUserEmail(Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
// validate email confirmation link, sets User::isVerified=true and persists
try {
$this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
} catch (VerifyEmailExceptionInterface $exception) {
$this->addFlash('verify_email_error', $exception->getReason());
return $this->redirectToRoute('app_register');
}
// #TODO Change the redirect on success and handle or remove the flash message in your templates
$this->addFlash('success', 'Your email address has been verified.');
return $this->redirectToRoute('app_register');
}
}
Address Entity
<?php
namespace App\Entity;
use App\Repository\AddressRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=AddressRepository::class)
*/
class Address
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $type;
/**
* #ORM\Column(type="integer")
*/
private $housenumber;
/**
* #ORM\Column(type="string", length=10, nullable=true)
*/
private $additional_info;
/**
* #ORM\Column(type="string", length=255)
*/
private $street;
/**
* #ORM\OneToMany(targetEntity=Schedule::class, mappedBy="address_fk")
*/
private $schedules;
/**
* #ORM\Column(type="float", nullable=true)
*/
private $latitude;
/**
* #ORM\Column(type="float", nullable=true)
*/
private $longitude;
/**
* #ORM\Column(type="string", length=255)
*/
private $city;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $region;
/**
* #ORM\Column(type="string", length=255)
*/
private $country;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $postalcode;
/**
* #ORM\ManyToOne(targetEntity=User::class, inversedBy="addresses")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
public function __construct()
{
$this->schedules = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
public function getHousenumber(): ?int
{
return $this->housenumber;
}
public function setHousenumber(int $housenumber): self
{
$this->housenumber = $housenumber;
return $this;
}
public function getAdditionalInfo(): ?string
{
return $this->additional_info;
}
public function setAdditionalInfo(?string $additional_info): self
{
$this->additional_info = $additional_info;
return $this;
}
public function getStreet(): ?string
{
return $this->street;
}
public function setStreet(string $street): self
{
$this->street = $street;
return $this;
}
/**
* #return Collection|Schedule[]
*/
public function getSchedules(): Collection
{
return $this->schedules;
}
public function addSchedule(Schedule $schedule): self
{
if (!$this->schedules->contains($schedule)) {
$this->schedules[] = $schedule;
$schedule->setAddressFk($this);
}
return $this;
}
public function removeSchedule(Schedule $schedule): self
{
if ($this->schedules->removeElement($schedule)) {
// set the owning side to null (unless already changed)
if ($schedule->getAddressFk() === $this) {
$schedule->setAddressFk(null);
}
}
return $this;
}
public function getLatitude(): ?float
{
return $this->latitude;
}
public function setLatitude(?float $latitude): self
{
$this->latitude = $latitude;
return $this;
}
public function getLongitude(): ?float
{
return $this->longitude;
}
public function setLongitude(?float $longitude): self
{
$this->longitude = $longitude;
return $this;
}
public function getCity(): ?string
{
return $this->city;
}
public function setCity(string $city): self
{
$this->city = $city;
return $this;
}
public function getRegion(): ?string
{
return $this->region;
}
public function setRegion(?string $region): self
{
$this->region = $region;
return $this;
}
public function getCountry(): ?string
{
return $this->country;
}
public function setCountry(string $country): self
{
$this->country = $country;
return $this;
}
public function getPostalcode(): ?string
{
return $this->postalcode;
}
public function setPostalcode(?string $postalcode): self
{
$this->postalcode = $postalcode;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
}
Edit_profil Twig
{% extends 'base.html.twig' %}
{#{% macro niceForm(address) %}#}
{# <div class="form-group col-md-4">{{ form_row(address.type) }}</div>#}
{# <div class="form-row">#}
{# <div class="form-group col-md-4">{{ form_row(address.street) }}</div>#}
{# <div class="form-group col-md-4">{{ form_row(address.housenumber) }}</div>#}
{# <div class="form-group col-md-4">{{ form_row(address.additional_info) }}</div>#}
{# </div>#}
{# <div class="form-row">#}
{# <div class="form-group col-md-3">{{ form_row(address.postalcode) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(address.city) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(address.region) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(address.country) }}</div>#}
{# </div>#}
{#{% endmacro %}#}
{#{% import _self as formMacros %}#}
{% block title %}Edit Profil{% endblock %}
{% block body %}
{% for flashError in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flashError }}</div>
{% endfor %}
<div class="container">
{{ form_start(registrationForm) }}
<div class="form-group">{{ form_row(registrationForm.roles) }}</div>
<div class="form-row">
<div class="form-group col-md-6">{{ form_row(registrationForm.name) }}</div>
<div class="form-group col-md-6">{{ form_row(registrationForm.firstname) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">{{ form_row(registrationForm.email) }}</div>
<div class="form-group col-md-6">{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-2">{{ form_row(registrationForm.prefix) }}</div>
<div class="form-group col-md-3">{{ form_row(registrationForm.phone) }}</div>
<div class="form-group col-md-3">{{ form_row(registrationForm.NISS, {label: 'NISS'}) }}</div>
<div class="form-group col-md-4">{{ form_row(registrationForm.BCE, {label: 'BCE'}) }}</div>
</div>
</div>
{# #TODO formatter pour qu'on differencie bien les addresses entre elles par block. Cf block en dessous#}
<div class="container">
<h3>Addresses</h3>
<ul class="addresses" data-index="{{ registrationForm.addresses|length > 0 ? registrationForm.addresses|last.vars.name + 1 : 0 }}" data-prototype="{{ form_widget(registrationForm.addresses.vars.prototype)|e('html_attr') }}">
{% for address in registrationForm.addresses %}
<li>{{ form_row(address.type) }}</li>
<li>{{ form_row(address.street) }}</li>
<li>{{ form_row(address.housenumber) }}</li>
<li>{{ form_row(address.additional_info) }}</li>
<li>{{ form_row(address.postalcode) }}</li>
<li>{{ form_row(address.city) }}</li>
<li>{{ form_row(address.region) }}</li>
<li>{{ form_row(address.country) }}</li>
{% endfor %}
</ul>
<button type="button" class="add_item_link" data-collection-holder-class="addresses">Add an address</button>
<button type="submit" id="submitRegister" class="btn">Register</button>
{{ form_end(registrationForm) }}
</div>
<script>
const addFormToCollection = (e) => {
const collectionHolder = document.querySelector('.' + e.currentTarget.dataset.collectionHolderClass);
const item = document.createElement('li');
item.innerHTML = collectionHolder
.dataset
.prototype
.replace(
/__name__/g,
collectionHolder.dataset.index
);
collectionHolder.appendChild(item);
collectionHolder.dataset.index++;
};
document
.querySelectorAll('.add_item_link')
.forEach(btn => btn.addEventListener("click", addFormToCollection));
</script>
I hope one of you will able to see what issue I have :)
Thank you
Problem solved.
Thanks for your help Will B. I was able to solve my problem which was coming from my controller. I used what was explained in the doc (the 11th reading was the right one).
In https://symfony.com/doc/current/form/form_collections.html, the persistence doctrine point was very useful (and it's not just for the remove, nice lesson), I just had to add a setUser for my addresses in the loop.
Mea culpa and thanks again !
Here is my controller:
/**
* #Route("/profil/{id}", name="edit_account")
*/
public function editUser(Request $request,int $id)
{
$entityManager = $this->getDoctrine()->getManager();
//Récupération de l'entité User avec l'ID passé et création du formulaire
$repoUser = $this->getDoctrine()
->getRepository(User::class);
$user = $repoUser->findOneBy([
'id'=>$id,
]);
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
$originalAddresses = new ArrayCollection();
// Create an ArrayCollection of the current Tag objects in the database
foreach ($user->getAddresses() as $address) {
$address->setUser($user);
$originalAddresses->add($address);
}
if ($form->isSubmitted() && $form->isValid()) {
foreach ($originalAddresses as $address) {
if (false === $user->getAddresses()->contains($address)) {
// remove the Task from the Tag
$address->getUser()->removeElement($user);
// if it was a many-to-one relationship, remove the relationship like this
// $tag->setTask(null);
$entityManager->persist($address);
}
}
}
$entityManager->persist($user);
$entityManager->flush();
return $this->render('profil/edit_profil.html.twig', [
'registrationForm' => $form->createView(),
]);
}
Thanks again !
I have an issue with an Entity.
The goal is to display a Form which the user can use to change his personal information like the Email or password. I created a form for that, but when I created the /Edit Route I get the following error:
"App\Entity\Users object not found by the #ParamConverter annotation."
Here's my Controller:
<?php
namespace App\Controller;
use App\Entity\Users;
use App\Form\EditProfileType;
use App\Form\UsersType;
use App\Repository\UsersRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/users")
*/
class UsersController extends AbstractController
{
/**
* #Route("/", name="users_index", methods={"GET"})
*/
public function index(): Response
{
return $this->render('users/index.html.twig');
}
/**
* #Route("/{id}", name="users_show", methods={"GET"})
*/
public function show(Users $user): Response
{
return $this->render('users/show.html.twig', [
'user' => $user,
]);
}
/**
* #Route("/edit", name="users_edit")
*/
public function editProfile(Request $request): Response
{
$user = $this->getUser();
$form = $this->createForm(EditProfileType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$this->addflash('message', 'Profile mis à jour');
return $this->redirectToRoute('users');
}
return $this->render('users/editprofile.html.twig', [
'form' => $form->createView(),
]);
}
Here's the form:
<?php
namespace App\Form;
use App\Entity\Users;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class EditProfileType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email')
->add('pseudo', TextType::class)
->add('Valider', SubmitType::class)
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Users::class,
]);
}
}
Here's the Entity Users:
<?php
namespace App\Entity;
use App\Repository\UsersRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=UsersRepository::class)
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class Users implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\OneToMany(targetEntity=Commentaires::class, mappedBy="auteur", orphanRemoval=true)
*/
private $commentaires;
/**
* #ORM\OneToMany(targetEntity=Notes::class, mappedBy="user", orphanRemoval=true)
*/
private $note;
/**
* #ORM\Column(type="string", length=100)
*/
private $pseudo;
public function __construct()
{
$this->commentaires = new ArrayCollection();
$this->note = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->email;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* #see UserInterface
*/
public function getSalt()
{
// not needed when using the "bcrypt" algorithm in security.yaml
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
/**
* #return Collection|Commentaires[]
*/
public function getCommentaires(): Collection
{
return $this->commentaires;
}
public function addCommentaire(Commentaires $commentaire): self
{
if (!$this->commentaires->contains($commentaire)) {
$this->commentaires[] = $commentaire;
$commentaire->setAuteur($this);
}
return $this;
}
public function removeCommentaire(Commentaires $commentaire): self
{
if ($this->commentaires->contains($commentaire)) {
$this->commentaires->removeElement($commentaire);
// set the owning side to null (unless already changed)
if ($commentaire->getAuteur() === $this) {
$commentaire->setAuteur(null);
}
}
return $this;
}
/**
* #return Collection|Notes[]
*/
public function getNote(): Collection
{
return $this->note;
}
public function addNote(Notes $note): self
{
if (!$this->note->contains($note)) {
$this->note[] = $note;
$note->setUser($this);
}
return $this;
}
public function removeNote(Notes $note): self
{
if ($this->note->contains($note)) {
$this->note->removeElement($note);
// set the owning side to null (unless already changed)
if ($note->getUser() === $this) {
$note->setUser(null);
}
}
return $this;
}
public function getPseudo(): ?string
{
return $this->pseudo;
}
public function setPseudo(string $pseudo): self
{
$this->pseudo = $pseudo;
return $this;
}
}
Here's the View Editprofile.html.twig:
{% extends 'basefront.html.twig' %}
{% block title %} Profile de {{ app.user.pseudo }} {% endblock %}
{% block body %}
<h1 class="h1 text-center">Modification du profile de {{ app.user.pseudo }}</h1>
{{ form(form) }}
{% endblock %}
order matters. especially when routing without requirements. you have the following routes (update: added prefix, thanks #Cerad!):
/users/ users_index
/users/{id} users_show
/users/edit users_edit
now, when you request the uri /users/edit the UrlMatcher will iterate over your routes to see if and which matches, stopping on the first match.
/users/ obviously doesn't match
/users/{id} does match, since {id} is allowed to be any alpha-numeric string (+ few extra chars), and edit matches that, leading to the users_show route, on which the User object cannot be determined by the id "edit`. And thus, the error message appears. One way to fix this would be to add requirements to the route matching - assuming the id is numerical only:
/**
* #Route("/{id}", ..., requirements={"id":"\d+"})
*/
I also agree with #Cerad that Users is a very bad name for an entity. They're usually in singular form.
I was not able to find any solution matching my case scenario, so I decided to ask here:
Basically what I need to achieve is to render a form with several select boxes namely the Company, ProductsCategory, and Products. So depending on which category the user chooses, I want to filter and show only the products of that chosen category. I tried to follow the Symfony documentation as mentioned here , but I cannot get it to work. the front-end products select box remains blank even after a category has been set and also the ajax return with status 500 with error:
Return value of App\Entity\ProductsCategory::getProducts() must be an instance of App\Entity\Products or null, instance of Doctrine\ORM\PersistentCollection returned
here are the codes:
My Exportables entity
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ExportablesRepository")
*/
class Exportables
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ExportCompany", inversedBy="exportables")
* #ORM\JoinColumn(nullable=false)
*/
private $company;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Products", inversedBy="exportables")
* #ORM\JoinColumn(nullable=false)
*/
private $product;
/**
* #ORM\Column(type="boolean")
*/
private $isActive;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ProductsCategory")
* #ORM\JoinColumn(nullable=false)
*/
private $category;
public function getId(): ?int
{
return $this->id;
}
public function getCompany(): ?ExportCompany
{
return $this->company;
}
public function setCompany(?ExportCompany $company): self
{
$this->company = $company;
return $this;
}
public function getProduct(): ?Products
{
return $this->product;
}
public function setProduct(?Products $product): self
{
$this->product = $product;
return $this;
}
public function getIsActive(): ?bool
{
return $this->isActive;
}
public function setIsActive(bool $isActive): self
{
$this->isActive = $isActive;
return $this;
}
public function getCategory(): ?ProductsCategory
{
return $this->category;
}
public function setCategory(?ProductsCategory $category): self
{
$this->category = $category;
return $this;
}
}
ProductsCategory entity:
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductsCategoryRepository")
*/
class ProductsCategory
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=50)
*/
private $categoryTitle;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $categoryDescription;
/**
* #ORM\Column(type="boolean")
*/
private $isActive;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Products", mappedBy="category", cascade={"persist", "remove"})
*/
private $products;
public function __construct()
{
$this->product = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getCategoryTitle(): ?string
{
return $this->categoryTitle;
}
public function setCategoryTitle(string $categoryTitle): self
{
$this->categoryTitle = $categoryTitle;
return $this;
}
public function getCategoryDescription(): ?string
{
return $this->categoryDescription;
}
public function setCategoryDescription(?string $categoryDescription): self
{
$this->categoryDescription = $categoryDescription;
return $this;
}
public function getIsActive(): ?bool
{
return $this->isActive;
}
public function setIsActive(bool $isActive): self
{
$this->isActive = $isActive;
return $this;
}
public function getProducts(): ?Products
{
return $this->products;
}
public function setProducts(Products $products): self
{
$this->products = $products;
// set the owning side of the relation if necessary
if ($products->getCategory() !== $this) {
$products->setCategory($this);
}
return $this;
}
public function __toString()
{
return $this->categoryTitle;
}
}
Products entity:
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProductsRepository")
*/
class Products
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=50)
*/
private $productTitle;
/**
* #ORM\Column(type="text")
*/
private $productDescription;
/**
* #ORM\Column(type="boolean")
*/
private $isActive;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\ProductsCategory", inversedBy="products", cascade={"persist", "remove"})
* #ORM\JoinColumn(nullable=false)
*/
private $category;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Exportables", mappedBy="product")
*/
private $exportables;
public function __construct()
{
$this->exportables = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getProductTitle(): ?string
{
return $this->productTitle;
}
public function setProductTitle(string $productTitle): self
{
$this->productTitle = $productTitle;
return $this;
}
public function getProductDescription(): ?string
{
return $this->productDescription;
}
public function setProductDescription(string $productDescription): self
{
$this->productDescription = $productDescription;
return $this;
}
public function getIsActive(): ?bool
{
return $this->isActive;
}
public function setIsActive(bool $isActive): self
{
$this->isActive = $isActive;
return $this;
}
public function getCategory(): ?ProductsCategory
{
return $this->category;
}
public function setCategory(ProductsCategory $category): self
{
$this->category = $category;
return $this;
}
/**
* #return Collection|Exportables[]
*/
public function getExportables(): Collection
{
return $this->exportables;
}
public function addExportable(Exportables $exportable): self
{
if (!$this->exportables->contains($exportable)) {
$this->exportables[] = $exportable;
$exportable->setProduct($this);
}
return $this;
}
public function removeExportable(Exportables $exportable): self
{
if ($this->exportables->contains($exportable)) {
$this->exportables->removeElement($exportable);
// set the owning side to null (unless already changed)
if ($exportable->getProduct() === $this) {
$exportable->setProduct(null);
}
}
return $this;
}
public function __toString(){
return $this->productTitle;
}
}
Exportables Type:
namespace App\Form;
use App\Entity\Products;
use App\Entity\Exportables;
use App\Entity\ProductsCategory;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
class ExportablesType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('company')
->add('category', EntityType::class, array(
'class' => ProductsCategory::class,
'placeholder' => 'Select a Category...',
))
->add('isActive')
;
$formModifier = function (FormInterface $form, ProductsCategory $cat = null) {
$products = null === $cat ? [] : $cat->getProducts();
dump($products);
$form->add('product', EntityType::class, [
'class' => 'App\Entity\Products',
'placeholder' => '',
'choices' => $products,
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
// this would be your entity, i.e. SportMeetup
$data = $event->getData();
$formModifier($event->getForm(), $data->getCategory());
}
);
$builder->get('category')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
// It's important here to fetch $event->getForm()->getData(), as
// $event->getData() will get you the client data (that is, the ID)
$cat = $event->getForm()->getData();
// since we've added the listener to the child, we'll have to pass on
// the parent to the callback functions!
$formModifier($event->getForm()->getParent(), $cat);
}
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Exportables::class,
]);
}
}
ExportablesController:
namespace App\Controller;
use App\Entity\Exportables;
use App\Form\ExportablesType;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class ExportablesController extends AbstractController
{
/**
* #Route("/admin-panel/exportables/new", name="exportables_create")
* #Route("/admin-panel/exportables/{id}/edit", name="exportables_edit")
*/
public function exportables_create_and_edit(Request $request, EntityManagerInterface $em, Exportables $exportables = null)
{
if(!$exportables){
$exportables = new Exportables();
}
$form = $this->createForm(ExportablesType::class, $exportables);
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$em->persist($exportables);
$em->flush();
}
return $this->render('/admin-panel/exportables_create.html.twig', [
'exForm' => $form->createView(),
'editMode' => $exportables->getId() !== null
]);
}
}
Finally, the twig file rendering the form:
{% extends '/admin-panel/base-admin.html.twig' %}
{% block body %}
{{ form_start(exForm) }}
<div class="form-group">
{{ form_row(exForm.company, {'attr':{'class':"form-control"}}) }}
</div>
<div class="form-group">
{{ form_row(exForm.category, {'attr':{'class':"form-control"}}) }}
</div>
{% if exForm.product is defined %}
<div class="form-group">
{{ form_row(exForm.product, {'label': "Product..", 'attr':{'class':"form-control"}}) }}
</div>
{% endif %}
<div class="form-group">
<div class="custom-control custom-checkbox">
{{ form_widget(exForm.isActive, {'attr': {'class': "custom-control-input", 'checked': "checked"}}) }}
<label class="custom-control-label" for="exportables_isActive">Visible on the website?</label>
</div>
</div>
<button type="submit" class="btn btn-success">Create</button>
{{ form_end(exForm) }}
{% endblock %}
{% block javascripts %}
<script>
{# //for some reasons this line doesnt work $(document).ready(function() { #}
jQuery(document).ready(function($){
var $cat = $('#exportables_category');
// When cat gets selected ...
$cat.change(function() {
// ... retrieve the corresponding form.
var $form = $(this).closest('form');
// Simulate form data, but only include the selected cat value.
var data = {};
data[$cat.attr('name')] = $cat.val();
console.log("cat val " + $cat.val());
//console.log($form.attr('method'));
const url = "{{ path('exportables_create')|escape('js') }}";
//console.log(data);
//console.log(url);
//why undefined?? console.log($form.attr('action'));
// Submit data via AJAX to the form's action path.
$.ajax({
//url : $form.attr('action'),
url : url,
type: $form.attr('method'),
data : data,
success: function(html) {
// Replace current position field ...
$('#exportables_product').replaceWith(
// ... with the returned one from the AJAX response.
//$(html).find('#exportables_product')
array(1,2,3)
);
// Position field now displays the appropriate positions.
}
});
});
});
</script>
{% endblock %}
Any help is greatly appreciated.
I didn't take the time to analyse all your code, so I'm not sure it will solve the whole problem, but it may help.
I guess there is something weird inside the ProductsCategory entity class. Indeed the products property is annotated as a OneToMany relation:
/**
* #ORM\OneToMany(targetEntity="App\Entity\Products", mappedBy="category", cascade={"persist", "remove"})
*/
private $products;
This implies that this property refers to a collection (one category can have many products). But the getters/setters for this property are defined later as if it was a OneToOne relation:
public function getProducts(): ?Products
{
return $this->products;
}
public function setProducts(Products $products): self
{
$this->products = $products;
// set the owning side of the relation if necessary
if ($products->getCategory() !== $this) {
$products->setCategory($this);
}
return $this;
}
It looks like you modified the annotation without modifying the getters/setters, which typically should offer the following methods for a OneToMany relation:
public function getProducts(): Collection;
public function addProducts(Product $product): self;
public function removeProducts(Product $product): self;
A last point: you should rename your entity Products to Product, it will greatly improve the readability of your code: indeed, the entity Products actually represent only one product, not several ones.
I created a DataTable with symfony:
src/Entity/User.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Table(name="app_users")
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=25, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
*/
private $password;
/**
* #ORM\Column(type="string", length=191, unique=true)
*/
private $email;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
public function __construct()
{
$this->isActive = true;
// may not be needed, see section on salt below
// $this->salt = md5(uniqid('', true));
}
public function getUsername()
{
return $this->username;
}
public function getSalt()
{
// you *may* need a real salt depending on your encoder
// see section on salt below
return null;
}
public function getPassword()
{
return $this->password;
}
public function getRoles()
{
return array('ROLE_USER');
}
public function eraseCredentials()
{
}
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt,
));
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt
) = unserialize($serialized, ['allowed_classes' => false]);
}
}
templates/homepage.html.twig
{% extends 'base.html.twig' %}
{% block title %}Symfony{% endblock %}
{% block body %}
<div class="wrapper">
{{ include('inc/navbar.html.twig') }}
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper" style="min-height: 866px;">
<section class="content">
<div class="row">
<div class="col-xs-12">
<div class="box">
<!-- /.box-header -->
<div class="box-body">
<table id="users">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>email</th>
</tr>
</thead>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /.content-wrapper -->
</div>
<!-- ./wrapper -->
{% endblock %}
src/DataTables/UsersDataTable.php
<?php
namespace App\DataTables;
use DataTables\DataTableHandlerInterface;
use DataTables\DataTableQuery;
use DataTables\DataTableResults;
use Symfony\Bridge\Doctrine\RegistryInterface;
class UsersDataTable implements DataTableHandlerInterface
{
protected $doctrine;
/**
* Dependency Injection constructor.
*
* #param RegistryInterface $doctrine
*/
public function __construct(RegistryInterface $doctrine)
{
$this->doctrine = $doctrine;
}
/**
* {#inheritdoc}
*/
public function handle(DataTableQuery $request): DataTableResults
{
/** #var \Doctrine\ORM\EntityRepository $repository */
$repository = $this->doctrine->getRepository('AppBundle:User');
$results = new DataTableResults();
// Total number of users.
$query = $repository->createQueryBuilder('u')->select('COUNT(u.id)');
$results->recordsTotal = $query->getQuery()->getSingleScalarResult();
// Query to get requested entities.
$query = $repository->createQueryBuilder('u');
// Search.
if ($request->search->value) {
$query->where('(LOWER(u.username) LIKE :search OR' .
' LOWER(u.email) LIKE :search)');
$query->setParameter('search', strtolower("%{$request->search->value}%"));
}
// Filter by columns.
foreach ($request->columns as $column) {
if ($column->search->value) {
$value = strtolower($column->search->value);
// "ID" column
if ($column->data == 0) {
$query->andWhere('u.id = :id');
$query->setParameter('id', intval($value));
}
// "Username" column
elseif ($column->data == 1) {
$query->andWhere('LOWER(u.username) LIKE :username');
$query->setParameter('username', "%{$value}%");
}
// "Email" column
elseif ($column->data == 2) {
$query->andWhere('LOWER(u.email) LIKE :email');
$query->setParameter('email', "%{$value}%");
}
}
}
// Order.
foreach ($request->order as $order) {
// "ID" column
if ($order->column == 0) {
$query->addOrderBy('u.id', $order->dir);
}
// "Username" column
elseif ($order->column == 1) {
$query->addOrderBy('u.username', $order->dir);
}
// "Email" column
elseif ($order->column == 2) {
$query->addOrderBy('u.email', $order->dir);
}
}
// Get filtered count.
$queryCount = clone $query;
$queryCount->select('COUNT(u.id)');
$results->recordsFiltered = $queryCount->getQuery()->getSingleScalarResult();
// Restrict results.
$query->setMaxResults($request->length);
$query->setFirstResult($request->start);
/** #var \AppBundle\Entity\User[] $users */
$users = $query->getQuery()->getResult();
foreach ($users as $user) {
$results->data[] = [
$user->getId(),
$user->getUsername(),
$user->getEmail(),
];
}
return $results;
}
}
src/Controller/DataTableController.php
<?php
namespace App\Controller;
use DataTables\DataTablesInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
/**
*
* #Route("/users", name="users")
*
* #param Request $request
* #param DataTablesInterface $datatables
* #return JsonResponse
*/
class DataTableController extends Controller
{
const ID = 'users';
public function usersAction(Request $request, DataTablesInterface $datatables): JsonResponse
{
try {
// Tell the DataTables service to process the request,
// specifying ID of the required handler.
$results = $datatables->handle($request, 'users');
return $this->json($results);
}
catch (HttpException $e) {
// In fact the line below returns 400 HTTP status code.
// The message contains the error description.
return $this->json($e->getMessage(), $e->getStatusCode());
}
}
}
But I get the error message:
DataTables warning: table id=users - Invalid JSON response.
I did a network analyse, but there everything seems to work fine:
Your request type is GET so you can paste you request url directly to browser and you will see if response is valid JSON. Invalid JSON response means that result of your request is not JSON, maybe you have false or error.