Can't bind form data to doctrine find function. "ORMInvalidArgumentException" - php

I'm trying to make a simple guestbook in Symfony 4, my goal is that you can add a message to the guestbook but before that, it needs to be internally activated.
I'm now working on the activating site, but there is a problem I can't really fix. The error message is as follows:
Binding entities to query parameters only allowed for entities that
have an identifier.
What i'm trying to do is, after you've typed in the ID of the non-activated message in the Activator Form, it takes the data (ID) from the form that was given and Doctrine finds the specific row of that ID. Then, it changes the rows smallint (not boolean) "isActive" to 1, so the Guestbook know which messages to show.
At " $message = $repository->find($id); " symfony couldn't bind my form data with the query parameters. I've already tried this answer but it didn't work either.
GBActivatorController.php:
use App\Entity\Guestbook;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
class GBActivatorController extends AbstractController
{
public function index(Request $request)
{
//sets up the entity guestbook and the entitymanager from doctrine
$guestbook = new Guestbook();
$entityManager = $this->getDoctrine()->getManager();
//get all messages
$list = $this->getDoctrine()
->getRepository(Guestbook::class)
->findAll();
//create form for activating a non-activated message
$activator = $this->createFormBuilder($guestbook)
->add('id', NumberType::class,
array(
'mapped' => false,
))
->add('activate', SubmitType::class, ['label' => 'Activate'])
->getForm();
$activator->handleRequest($request);
/*when the submit button is clicked, get the data and
find the row with the specific id that was given.*/
if($activator->isSubmitted())
{
//gets the data from the form
$id = $activator->getData();
//sets up repository, so i dont need to type it again
$repository = $this->getDoctrine()->getRepository(Guestbook::class);
//(should) find the row
$message = $repository->find($id);
//activates row. (isActive = 1 instead of isActive = 0)
$message->setIsActive(1);
//persist and then execute to table
$entityManager->persist($message);
$entityManager->flush();
}
//renders the template
return $this->render('GBActivator.html.twig', array(
'list' => $list,
'activator' => $activator->createView()
));
}
}
Entity/Guestbook.php:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\GuestbookRepository")
*/
class Guestbook
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
public $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $email;
/**
* #ORM\Column(type="string", length=255)
*/
private $text;
/**
* #ORM\Column(type="smallint")
*/
private $isActive;
public function setId(Array $id)
{
$this->id = $id;
}
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;
}
public function getText(): ?string
{
return $this->text;
}
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
public function getIsActive(): ?int
{
return $this->isActive;
}
public function setIsActive(int $isActive): self
{
$this->isActive = $isActive;
return $this;
}
}

Thank you Eakethet.
My problem was that i demanded an array instead of a string. Stupid me. I changed
public function setId(Array $id)
{
$this->id = $id;
}
to
public function setId(string $id)
{
$this->id = $id;
}

Related

Could not determine access type for property "image" in class "App\Entity\XXXX". Symfony 4 - EasyAdmin 3.2 - VichUploader

Strugling here trygin to integrate VichImageUploader into my EasyAdmin 3.2.
This version of EasyAdmin is letting us create custom Fields which works just fine.
In my case I am only trying to upload 1 image and push it into my DB. I set up my Easy Admin dashboard and just followed:
https://symfony.com/doc/2.x/bundles/EasyAdminBundle/integration/vichuploaderbundle.html
to hydrate my configureFields function inside my CrudController.
As in the docs, I made a imageFile field joint to a image field althogeter with seters and geters.
Inside my CrudController I use my custom field because it seems its the only way to do image uploads in this version of easyadmin.
My CrudController
namespace App\Controller\Admin;
use App\Entity\ButtonPlant;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use Vich\UploaderBundle\Form\Type\VichImageType;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Field\VichImageField;
class ButtonPlantCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return ButtonPlant::class;
}
public function configureFields(string $pageName): iterable
{
$imageFile = VichImageField::new('imageFile')->setFormType(VichImageType::class);
$image = ImageField::new('image')->setBasePath('/uploads/images');
$fields = [
TextField::new('content', 'Contenu'),
/* CollectionField::new('image')
->setEntryType(ImageType::class)
->setUploadDir('public\uploads\images\buttonplants'),
ImageField::new('imageFile')->setFormType(VichImageType::class), */
AssociationField::new('stepId', 'Etape'),
AssociationField::new('nextStepId', 'Prochaine Etape' ),
AssociationField::new('finalSheetId', 'Fiche Final'),
];
if ($pageName == Crud::PAGE_INDEX || $pageName == Crud::PAGE_DETAIL) {
$fields[] = $image;
} else {
$fields[] = $imageFile;
}
return $fields;
}
My Entity Controller
namespace App\Entity;
use App\Repository\ButtonPlantRepository;
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;
use DateTime;
/**
* #ORM\Entity(repositoryClass=ButtonPlantRepository::class)
* #Vich\Uploadable
*/
class ButtonPlant
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $content;
/**
* #ORM\Column(type="string", length=255)
* #var string
*/
private $image;
/**
* #Vich\UploadableField(mapping="buttonplant_images", fileNameProperty="image")
* #var File
*/
private $imageFile;
/**
* #ORM\OneToOne(targetEntity=FinalSheet::class, cascade={"persist", "remove"})
*/
private $finalSheetId;
/**
* #ORM\ManyToOne(targetEntity=CoursePlant::class, inversedBy="buttonPlants")
* #ORM\JoinColumn(nullable=false)
*/
private $stepId;
/**
* #ORM\OneToOne(targetEntity=CoursePlant::class, cascade={"persist", "remove"})
*/
private $nextStepId;
/**
* #ORM\Column(type="datetime", nullable=true)
* #var \DateTime
*/
private $updatedAt;
public function getId(): ?int
{
return $this->id;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getImage(): ?string
{
return $this->image;
}
public function setIamge(string $image): self
{
$this->image = $image;
return $this;
}
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) {
// if 'updatedAt' is not defined in your entity, use another property
$this->updatedAt = new \DateTime('now');
}
}
public function getImageFile()
{
return $this->imageFile;
}
public function getFinalSheetId(): ?FinalSheet
{
return $this->finalSheetId;
}
public function setFinalSheetId(?FinalSheet $finalSheetId): self
{
$this->finalSheetId = $finalSheetId;
return $this;
}
public function getStepId(): ?CoursePlant
{
return $this->stepId;
}
public function setStepId(?CoursePlant $stepId): self
{
$this->stepId = $stepId;
return $this;
}
public function getNextStepId(): ?CoursePlant
{
return $this->nextStepId;
}
public function setNextStepId(?CoursePlant $nextStepId): self
{
$this->nextStepId = $nextStepId;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
}
My custom Field
namespace EasyCorp\Bundle\EasyAdminBundle\Field;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Vich\UploaderBundle\Form\Type\VichImageType;
class VichImageField implements FieldInterface
{
use FieldTrait;
public static function new(string $propertyName, ?string $label = null)
{
return (new self())
->setProperty($propertyName)
->setTemplatePath('')
->setLabel($label)
->setFormType(VichImageType::class);
}
}
And my error is
Could not determine access type for property "image" in class "App\Entity\ButtonPlant".
Thanks in advance for any help
I solved my problem deleting the field "image" and creating it back but this time is allowed to be null.
Hopefully it can be useful for anyone

Request a linked field in symfony

I have a page that displays information about a movie. I recover in GET the id of the film. What I would like to do is retrieve the comments for each film (there is a filmId column in my table linked to the primary id of the film table)
/**
* #Route("/user/film/{id}", name="film")
*/
public function film(FilmRepository $repo, CommentRepository $comRepo, EntityManagerInterface $em, Request $req, $id)
{
$film = $repo->find($id);
$comments = $comRepo->findBy(array('id' => $id));
return $this->render('film/film.html.twig', [
'controller_name' => 'FilmController',
'film' => $film,
'comments' => $comments
]);
}
when I make a $comments = $comRepo->findBy(array('id' => $id)); I get some comments, but based on their id and NOT the film id (the comment with id 1 will be displayed on the film with id 1, but for example a comment with id 4 and the filmId a 1 will not appear on film 1, but on the film with id 4)
I tried to access the filmId field by simply making a $comments = $comRepo->findBy(array ('filmId' => $ id)); but i get the error :
An exception occurred while executing 'SELECT t0.id AS id_1, t0.content AS content_2, t0.created_at AS created_at_3, t0.author_id AS author_id_4 FROM comment t0 WHERE comment_film.film_id = ?' with params ["1"]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'comment_film.film_id' in 'where clause'
I tried a personalized request with, in my Comment repository:
public function findAllWithFilmId($filmId)
{
$em = $this->getEntityManager();
$query = $em->createQuery(
'SELECT c
FROM App\Entity\Comment c
WHERE c.filmId = :filmId'
)->setParameter('filmId', $filmId);
return $query->getResult();
}
But it doesn't seem to work..
Where do I go to make a request like this ?
How to modify the request, which seems erroneous, from symfony without disorganizing everything? or is there a better method to correct the problem?
This is my Comment Entity
<?php
namespace App\Entity;
use App\Entity\Film;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CommentRepository")
*/
class Comment
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="comments")
* #ORM\JoinColumn(nullable=false)
*/
private $author;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Film", inversedBy="comments")
* #ORM\JoinColumn(nullable=false)
*/
private $filmId;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
public function getId(): ?int
{
return $this->id;
}
public function getAuthor(): ?User
{
return $this->author;
}
public function setAuthor(?User $author): self
{
$this->author = $author;
return $this;
}
public function getFilmId(): ?Film
{
return $this->filmId;
}
public function setFilmId(?Film $filmId): self
{
$this->filmId = $filmId;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}
I think it is possible that the error comes from annotations, because starting on symfony during the make: entity, I defined types relations which I corrected later in phpmyadmin, but not the code. For example we can see that filmId is in ManyToMany, but I think it should be in OneToOne (FilmId can only have one id and an id can only correspond to one filmId), but I'm afraid that if I change certain things it breaks everything.
If you have set up your ORM relations correctly, it should be as simple as:
$film = $repo->find($id);
$comments = $film->getComments();
You might be missing a mapping in Film.php.
Here's an XML example, should be easy enough to convert to annotations:
In film:
<one-to-many field="comments" target-entity="App\...\Comments" mapped-by="film"/>
In comments:
<many-to-one field="film" target-entity="App\...\Film" inversed-by="comments"/>
First of all, I advise you to read more about the relations between entities.
Because, the current annotations says that you can have a lot of comments on many films. It's not right. One comment may belong to one film. One movie can have many comments.
Also, I want to note that, as far as I know, #JoinColumn should be in a child entity, that is, where the link to FK is contained.
Therefore, your entities should look like this:
Comment:
<?php
namespace App\Entity;
use App\Entity\Film;
use DateTimeInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\CommentRepository")
*/
class Comment
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="comments")
*/
private $author;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Film", inversedBy="comments")
* Here we set property for our table and property of foreign table to map our comment to the right film
* nullable, because comment couldn't be without film
* #ORM\JoinColumn(name="film_id", referencedColumnName="id", nullable=false)
*/
private $film;
/**
* #ORM\Column(type="text")
*/
private $content;
/**
* #ORM\Column(type="datetime")
*/
private $createdAt;
public function getId(): ?int
{
return $this->id;
}
public function getAuthor(): ?User
{
return $this->author;
}
public function setAuthor(?User $author): self
{
$this->author = $author;
return $this;
}
public function getFilmId(): ?Film
{
return $this->filmId;
}
public function setFilmId(?Film $filmId): self
{
$this->filmId = $filmId;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function getCreatedAt(): ?DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
}
Film:
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\FilmRepository")
*/
class Film
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Comment", mappedBy="film")
*/
private $comments;
public function __construct()
{
$this->comments = new ArrayCollection();
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
return $this;
}
public function getComments(): Collection
{
return $this->comments;
}
public function setComments(Collection $comments): Film
{
$this->comments = $comments;
return $this;
}
}
So, now, you can retrieve your comments via:
/**
* #Route("/user/film/{id}", name="film")
*/
public function film($id)
{
/** #var null|EntityManager $entityManager */
$entityManager = $this->get('doctrine.orm.entity_manager');
if (null == ($film = $entityManager->getRepository(Film::class)->find($id))){
throw new NotFoundHttpException('Film not found');
}
$comments = $film->getComments();
return $this->render('film/film.html.twig', [
'film' => $film,
'comments' => $comments
]);
}

Form issue when trying to create a checkbox with CollectionType calling to another FromType (cross entities situation)

This case is a case study, I'm trying to resolve this issue in order to explain how to organize entities and create forms to my students.
I have this singular relation between 3 of my entities :
Protagonist <--(OneToMany)--> EventRegistration <--(ManyToOne)--> Event
Which could not be transformed as a many to many relation because there are some columns inside the EventRegistration table :
Protagonist :
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProtagonistRepository")
*/
class Protagonist
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="string", length=100, nullable=true)
*/
private $japaneseName;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\Column(type="string", length=80, nullable=true)
*/
private $picture;
/**
* #ORM\Column(type="string", length=80, nullable=true)
*/
private $background;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $updated_at;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Category", inversedBy="protagonists")
* #ORM\JoinColumn(nullable=false)
*/
private $category;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Tag", mappedBy="protagonists")
*/
private $tags;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Registration", mappedBy="protagonist")
*/
private $registrations;
/**
* #ORM\Column(type="boolean", nullable=true)
*/
private $isAlive;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Event", mappedBy="protagonists")
*/
private $events;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EventRegistration", mappedBy="protagonist")
*/
private $eventRegistrations;
public function __construct()
{
$this->tags = new ArrayCollection();
$this->registrations = new ArrayCollection();
$this->events = new ArrayCollection();
$this->eventRegistrations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getJapaneseName(): ?string
{
return $this->japaneseName;
}
public function setJapaneseName(?string $japaneseName): self
{
$this->japaneseName = $japaneseName;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(string $description): self
{
$this->description = $description;
return $this;
}
public function getPicture(): ?string
{
return $this->picture;
}
public function setPicture(?string $picture): self
{
$this->picture = $picture;
return $this;
}
public function getBackground(): ?string
{
return $this->background;
}
public function setBackground(?string $background): self
{
$this->background = $background;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updated_at;
}
public function setUpdatedAt(?\DateTimeInterface $updated_at): self
{
$this->updated_at = $updated_at;
return $this;
}
public function getCategory(): ?Category
{
return $this->category;
}
public function setCategory(?Category $category): self
{
$this->category = $category;
return $this;
}
/**
* #return Collection|Tag[]
*/
public function getTags(): Collection
{
return $this->tags;
}
public function addTag(Tag $tag): self
{
if (!$this->tags->contains($tag)) {
$this->tags[] = $tag;
$tag->addProtagonist($this);
}
return $this;
}
public function removeTag(Tag $tag): self
{
if ($this->tags->contains($tag)) {
$this->tags->removeElement($tag);
$tag->removeProtagonist($this);
}
return $this;
}
/**
* #return Collection|Registration[]
*/
public function getRegistrations(): Collection
{
return $this->registrations;
}
public function addRegistration(Registration $registration): self
{
if (!$this->registrations->contains($registration)) {
$this->registrations[] = $registration;
$registration->setProtagonist($this);
}
return $this;
}
public function removeRegistration(Registration $registration): self
{
if ($this->registrations->contains($registration)) {
$this->registrations->removeElement($registration);
// set the owning side to null (unless already changed)
if ($registration->getProtagonist() === $this) {
$registration->setProtagonist(null);
}
}
return $this;
}
public function getIsAlive(): ?bool
{
return $this->isAlive;
}
public function setIsAlive(?bool $isAlive): self
{
$this->isAlive = $isAlive;
return $this;
}
/**
* #return Collection|Event[]
*/
public function getEvents(): Collection
{
return $this->events;
}
public function addEvent(Event $event): self
{
if (!$this->events->contains($event)) {
$this->events[] = $event;
$event->addProtagonist($this);
}
return $this;
}
public function removeEvent(Event $event): self
{
if ($this->events->contains($event)) {
$this->events->removeElement($event);
$event->removeProtagonist($this);
}
return $this;
}
/**
* #return Collection|EventRegistration[]
*/
public function getEventRegistrations(): Collection
{
return $this->eventRegistrations;
}
public function addEventRegistration(EventRegistration $eventRegistration): self
{
if (!$this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations[] = $eventRegistration;
$eventRegistration->setProtagonist($this);
}
return $this;
}
public function removeEventRegistration(EventRegistration $eventRegistration): self
{
if ($this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations->removeElement($eventRegistration);
// set the owning side to null (unless already changed)
if ($eventRegistration->getProtagonist() === $this) {
$eventRegistration->setProtagonist(null);
}
}
return $this;
}
}
EventRegistration :
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EventRegistrationRepository")
*/
class EventRegistration
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="datetimetz")
*/
private $registrationDate;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Protagonist", inversedBy="eventRegistrations")
*/
private $protagonist;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Event", inversedBy="eventRegistrations")
*/
private $event;
public function getId(): ?int
{
return $this->id;
}
public function getRegistrationDate(): ?\DateTimeInterface
{
return $this->registrationDate;
}
public function setRegistrationDate(\DateTimeInterface $registrationDate): self
{
$this->registrationDate = $registrationDate;
return $this;
}
public function getProtagonist(): ?Protagonist
{
return $this->protagonist;
}
public function setProtagonist(?Protagonist $protagonist): self
{
$this->protagonist = $protagonist;
return $this;
}
public function getEvent(): ?Event
{
return $this->event;
}
public function setEvent(?Event $event): self
{
$this->event = $event;
return $this;
}
}
Event :
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\EventRepository")
*/
class Event
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\Column(type="datetimetz", nullable=true)
*/
private $start_date;
/**
* #ORM\Column(type="datetimetz", nullable=true)
*/
private $end_date;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Protagonist", inversedBy="events")
*/
private $protagonists;
/**
* #ORM\OneToMany(targetEntity="App\Entity\EventRegistration", mappedBy="event")
*/
private $eventRegistrations;
public function __construct()
{
$this->protagonists = new ArrayCollection();
$this->eventRegistrations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getStartDate(): ?\DateTimeInterface
{
return $this->start_date;
}
public function setStartDate(?\DateTimeInterface $start_date): self
{
$this->start_date = $start_date;
return $this;
}
public function getEndDate(): ?\DateTimeInterface
{
return $this->end_date;
}
public function setEndDate(?\DateTimeInterface $end_date): self
{
$this->end_date = $end_date;
return $this;
}
/**
* #return Collection|Protagonist[]
*/
public function getProtagonists(): Collection
{
return $this->protagonists;
}
public function addProtagonist(Protagonist $protagonist): self
{
if (!$this->protagonists->contains($protagonist)) {
$this->protagonists[] = $protagonist;
}
return $this;
}
public function removeProtagonist(Protagonist $protagonist): self
{
if ($this->protagonists->contains($protagonist)) {
$this->protagonists->removeElement($protagonist);
}
return $this;
}
/**
* #return Collection|EventRegistration[]
*/
public function getEventRegistrations(): Collection
{
return $this->eventRegistrations;
}
public function addEventRegistration(EventRegistration $eventRegistration): self
{
if (!$this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations[] = $eventRegistration;
$eventRegistration->setEvent($this);
}
return $this;
}
public function removeEventRegistration(EventRegistration $eventRegistration): self
{
if ($this->eventRegistrations->contains($eventRegistration)) {
$this->eventRegistrations->removeElement($eventRegistration);
// set the owning side to null (unless already changed)
if ($eventRegistration->getEvent() === $this) {
$eventRegistration->setEvent(null);
}
}
return $this;
}
}
I can access a collection of eventRegistrations with my Protagonist and Event entity, and I can access the protagonist and event with my EventRegistration entity.
The issue crops up when I try to create a checkbox with all the events available for the protagonist : I don't have any attribute that allows me to make a collection of those events :
ProtagonistType
<?php
namespace App\Form;
use App\Entity\Category;
use App\Entity\Event;
use App\Entity\EventRegistration;
use App\Entity\Protagonist;
use App\Entity\Tag;
use App\Repository\EventRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
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;
/**
* Class ProtagonistType
* #package App\Form
*/
class ProtagonistType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
->add('japaneseName', TextType::class, [
'required' => false
])
->add('description', TextareaType::class)
->add('picture', TextType::class, [
'required' => false
])
->add('background', TextType::class, [
'required' => false
])
->add('isAlive', CheckboxType::class)
->add('category', EntityType::class, [
'class' => Category::class,
'choice_label' => 'title',
'expanded' => true,
'multiple' => false
])
->add('tags', EntityType::class, [
'class' => Tag::class,
'choice_label' => 'name',
'expanded' => true,
'multiple' => true,
'by_reference' => false,
])
**->add('eventRegistrations', CollectionType::class, [
'entry_type' => EventRegistrationType::class
])**
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Protagonist::class,
]);
}
}
EventRegistrationType :
<?php
namespace App\Form;
use App\Entity\Event;
use App\Entity\EventRegistration;
use App\Repository\EventRepository;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
/**
* Class EventRegistrationType
* #package App\Form
*/
class EventRegistrationType extends AbstractType
{
/**
* #param FormBuilderInterface $builder
* #param array $options
*/
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('event', EntityType::class, [
'class' => Event::class,
'choice_label' => 'name',
'multiple' => true,
'expanded' => true,
'by_reference' => false,
])
;
}
/**
* #param OptionsResolver $resolver
*/
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => EventRegistration::class,
]);
}
}
The only effective solution I found is to create a ManyToMany relation between Protagonist and Event, then set another Registration table which is on a ManyToOne relation with Protagonist in order to get the protagonists registrations.
Still I'd like to make this many to many relation with extra fields works, I'm all ears for any solution you'd get to solve this issue.
Thank you!
Some theory
Entities are your models, business objects that have identities, data, behavior.
They are the heart, building blocks of your business model.
When we design entities - in the first place we should consider them as objects, that have their own shape and responsibilities instead of them being just containers for data stored in a database. Also, we should care about proper relations between entities.
Ideally, entities should always be valid. If so - they can be persisted at any time.
Persistence is a separate concern.
In general case, it's even not necessary for entities to be persisted into a database. They could just be persisted in memory, file system, key-value storage etc.
Forms is also a separate concern that is closer to working with user interfaces.
Forms help us to render user interface, translate requests from users to some structures of known shape that is easier to work with, than with raw data from requests, and validate this submitted data.
These structures are just containers for data retrieved from requests, they should not have any behavior.
These structures can be invalid at certain times.
What about the issue described?
So making entities playing roles of these forms underlying data structures might be not the best idea ever.
It's just clearly mixing of concerns and rigid coupling between different layers.
That is why you're having these issues.
So instead of using EventRegistration class as a data_class for EventRegistrationType and Protagonist - for ProtagonistType - consider creating separate data structures. Propagate submitted data to entities only when it's successfully validated.
Some useful links to read more on the topic (though the author calls underlying structures - commands somewhy):
Decoupling (Symfony2) Forms from Entities
Form, Command, and Model Validation
Other useful topics:
The Clean Architecture
Domain-Driven Design in PHP

how to use form builder to make form from entity relation of entity in symfony4

TL;TR:How to make form fields from formbuilder with relation column
Here is my Level Entity
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\LevelRepository")
*/
class Level
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=10)
*/
private $name;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $description;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Area", mappedBy="levelid", orphanRemoval=true)
*/
private $areas;
public function __construct()
{
$this->areas = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getDescription(): ?string
{
return $this->description;
}
public function setDescription(?string $description): self
{
$this->description = $description;
return $this;
}
/**
* #return Collection|Area[]
*/
public function getAreas(): Collection
{
return $this->areas;
}
public function addArea(Area $area): self
{
if (!$this->areas->contains($area)) {
$this->areas[] = $area;
$area->setLevelid($this);
}
return $this;
}
public function removeArea(Area $area): self
{
if ($this->areas->contains($area)) {
$this->areas->removeElement($area);
// set the owning side to null (unless already changed)
if ($area->getLevelid() === $this) {
$area->setLevelid(null);
}
}
return $this;
}
}
and this is my Area Entity
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\AreaRepository")
*/
class Area
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Property", mappedBy="area")
*/
private $property;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Level", inversedBy="areas")
* #ORM\JoinColumn(nullable=false)
*/
private $levelid;
public function __construct()
{
$this->property = new ArrayCollection();
$this->projects = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection|property[]
*/
public function getProperty(): Collection
{
return $this->property;
}
public function addProperty(property $property): self
{
if (!$this->property->contains($property)) {
$this->property[] = $property;
$property->setArea($this);
}
return $this;
}
public function removeProperty(property $property): self
{
if ($this->property->contains($property)) {
$this->property->removeElement($property);
// set the owning side to null (unless already changed)
if ($property->getArea() === $this) {
$property->setArea(null);
}
}
return $this;
}
public function getLevelid(): ?Level
{
return $this->levelid;
}
public function setLevelid(?Level $levelid): self
{
$this->levelid = $levelid;
return $this;
}
}
Area connected to Level
And this is Area Form builder
<?php
namespace App\Form;
use App\Entity\Area;
// use App\Entity\District;
use App\Entity\Level;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
class AreaType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name')
->add('Level', EntityType::class, array(
// looks for choices from this entity
'class' => Level::class,
'label' => 'Level',
// uses the User.username property as the visible option string
'choice_label' => 'name',
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Area::class,
]);
}
}
And the error is
Neither the property "Level" nor one of the methods "getLevel()", "level()", "isLevel()", "hasLevel()", "__get()" exist and have public access in class "App\Entity\Area".
Your problem is caused by form field and Entity field name mismatch. AreaType tries to refer to level property using Symfony's PropertyAccessor on Area while it does not have such field.
You should either rename your entity field from $levelid to $level (because it actually holds an Entity, not ID), changing setter and getter accordingly (recommended solution) or change form field name from Level to levelid.
You can find more information about Symfony Forms in official docummentation.

Symfony 4 - FOSUserBundle - Render user information on custom route

I have a constructor and route in my custom ProfileController
private $userManager;
public function __construct(UserManagerInterface $userManager)
{
$this->userManager = $userManager;
}
/**
* #Route("/profile/bookings", name="profile_bookings")
*/
public function bookings()
{
$user = $this->getUser();
return $this->render('profile/bookings/bookings.html.twig', array('user'=>$user));
}
And in my template I reference
{{ user.first_name }}
But I get the error:
HTTP 500 Internal Server Error
Neither the property "first_name" nor one of the methods "first_name()", "getfirst_name()"/"isfirst_name()"/"hasfirst_name()" or "__call()" exist and have public access in class "App\Entity\User".
How do I get the user info from db and display in sub pages of profile?
Edit: User Entity ...
<?php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
/**
* #ORM\Entity
* #ORM\Table(name="`user`")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="string", length=190)
*/
private $first_name;
/**
* #ORM\Column(type="string", length=190)
*/
private $last_name;
/**
* #ORM\Column(type="string", length=190, nullable=true)
*/
private $phone_number;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $profile_height;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $profile_weight;
/**
* #ORM\Column(type="date", nullable=true)
*/
private $profile_dob;
/**
* #ORM\Column(type="string", length=190, nullable=true)
*/
private $profile_gender;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="user")
*/
private $bookings;
public function __construct()
{
parent::__construct();
$this->bookings = new ArrayCollection();
}
/**
* Overridde setEmail method so that username is now optional
*
* #param string $email
* #return User
*/
public function setEmail($email)
{
$this->setUsername($email);
return parent::setEmail($email);
}
public function getFirstName()
{
return $this->first_name;
}
public function setFirstName($first_name)
{
$this->first_name = $first_name;
}
public function getLastName()
{
return $this->last_name;
}
public function setLastName($last_name)
{
$this->last_name = $last_name;
}
public function getPhoneNumber(): ?string
{
return $this->phone_number;
}
public function setPhoneNumber(string $phone_number): self
{
$this->phone_number = $phone_number;
return $this;
}
public function getProfileHeight(): ?int
{
return $this->profile_height;
}
public function setProfileHeight(?int $profile_height): self
{
$this->profile_height = $profile_height;
return $this;
}
public function getProfileDob(): ?\DateTimeInterface
{
return $this->profile_dob;
}
public function setProfileDob(?\DateTimeInterface $profile_dob): self
{
$this->profile_dob = $profile_dob;
return $this;
}
public function getProfileWeight(): ?int
{
return $this->profile_weight;
}
public function setProfileWeight(?int $profile_weight): self
{
$this->profile_weight = $profile_weight;
return $this;
}
public function getProfileGender(): ?string
{
return $this->profile_gender;
}
public function setProfileGender(?string $profile_gender): self
{
$this->profile_gender = $profile_gender;
return $this;
}
/**
* #return Collection|Booking[]
*/
public function getBookings(): Collection
{
return $this->bookings;
}
public function addBooking(Booking $booking): self
{
if (!$this->bookings->contains($booking)) {
$this->bookings[] = $booking;
$booking->setUser($this);
}
return $this;
}
public function removeBooking(Booking $booking): self
{
if ($this->bookings->contains($booking)) {
$this->bookings->removeElement($booking);
// set the owning side to null (unless already changed)
if ($booking->getUser() === $this) {
$booking->setUser(null);
}
}
return $this;
}
}
Thanks.
#Franck Gamess is right but you can also get rid of the get.
If you write {{ user.firstName }}, twig will associate that to your method getFirstName() automatically.
I don't know why you write your properties with snake_case but you could change it to camelCase and access your properties via their "real" name.
Just use in your twig template:
{{ user.getFirstName }}
It works fine. Normally what Twig does is quite simple on the PHP Layer:
check if user is an array and first_name a valid element;
if not, and if user is an object, check that first_name is a valid property;
if not, and if user is an object, check that first_name is a valid method (even if first_name is the constructor - use __construct() instead);
if not, and if user is an object, check that getfirst_name is a valid method;
if not, and if user is an object, check that isfirst_name is a valid method;
if not, and if user is an object, check that hasfirst_name is a valid method;
if not, return a null value.
See Twig variables.
By the way you should follow the Symfony Coding Standard for your variable, because it can be difficult for twig to find value of properties written in snake_case.
I don't think you should construct the UserManagerInterface in your controller. Also, like Franck says, use the coding standard if you can, it will save a lot of time and frustration in the future!
Here is the controller I use in a Symfony 4 project:
namespace App\Controller;
use FOS\UserBundle\Model\UserInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* #Route("/profile/bookings", name="profile_bookings")
*/
public function bookings()
{
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface) {
throw new AccessDeniedException('This user does not have access to this section.');
}
return $this->render('profile/bookings/bookings.html.twig', array(
'user' => $user,
));
}
}

Categories