Entity not found with Symfony 5.1 - php

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.

Related

Symfony 5 - Using DataTransformer on Entity with another column than primary key

Following this doc https://symfony.com/doc/current/form/data_transformers.html#harder-example-transforming-an-issue-number-into-an-issue-entity, we learn how to fill a field (which is an Entity) without using a choice field type or similar.
It seems to work, using the primary key of the Entity concerned (el famoso "id").
However, i want to use another field and store it in a table, but i have an error (see screeshot at bottom)
Entities :
<?php
namespace App\Entity;
use App\Repository\LandRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=LandRepository::class)
* #ORM\Table(name="land",indexes={#ORM\Index(columns={"uid"})})
* #ORM\HasLifecycleCallbacks()
*/
class Land
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $libelle;
/**
* #ORM\Column(type="integer")
*/
private $uid;
public function getId(): ?int
{
return $this->id;
}
public function getLibelle(): ?string
{
return $this->libelle;
}
public function setLibelle(string $libelle): self
{
$this->libelle = $libelle;
return $this;
}
public function getUid(): ?int
{
return $this->uid;
}
public function setUid(int $uid): self
{
$this->uid = $uid;
return $this;
}
}
namespace App\Entity;
use App\Repository\RideRepository;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass=RideRepository::class)
* #ORM\HasLifecycleCallbacks()
*/
class Ride
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $libelle;
/**
* #ORM\ManyToOne(targetEntity=Land::class)
* #ORM\JoinColumn(name="areaparent", referencedColumnName="uid")
*/
private $areaparent;
public function getId(): ?int
{
return $this->id;
}
public function getLibelle(): ?string
{
return $this->libelle;
}
public function setLibelle(string $libelle): self
{
$this->libelle = $libelle;
return $this;
}
public function getAreaparent(): ?Land
{
return $this->areaparent;
}
public function setAreaparent(?Land $areaparent): self
{
$this->areaparent = $areaparent;
return $this;
}
}
Expected final result :
"Land" table
"Ride" table
So you can see that in the "ride" table that the "areaparent" column is relative to the "land.uid" column.
"Land" form is classic
"Ride" form have a minor change : The "areaparent" is not a choice field, it's a text type which will call my DataTransformer
Extract of my form "RideType"
<?php
namespace App\Form;
use App\Entity\Ride;
use App\Entity\Land;
use Symfony\Component\Form\AbstractType;
use Symfony\Bridge\Doctrine\Form\Type\EntityType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use App\Repository\LandRepository;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use App\Form\DataTransformer\UIDTransformer;
class RideType extends AbstractType
{
private $transformer;
public function __construct(UIDTransformer $transformer)
{
$this->transformer = $transformer;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('libelle')
->add('areaparent', TextType::class, [
'invalid_message' => 'That is not a valid areaparent number',
])
;
$builder->get('areaparent')->addModelTransformer($this->transformer);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Ride::class,
]);
}
}
Extract of my DataTransformer :
<?php
namespace App\Form\DataTransformer;
use App\Entity\Land;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
class UIDTransformer implements DataTransformerInterface
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* Transforms an object (Land) to a string (areaparent).
*
* #param Land|null $Land
* #return string
*/
public function transform($land)
{
if (null === $land) {
return '';
}
return $land->getUid();
}
/**
* Transforms a string (police) to an object (Land).
*
* #param string $areaparent
* #return Land|null
* #throws TransformationFailedException if object (Land) is not found.
*/
public function reverseTransform($areaparent)
{
// no areaparent? It's optional, so that's ok
if (!$areaparent) {
return;
}
$land = $this->entityManager->getRepository(Land::class)->findByUid($areaparent);
if (count($land) == 0) {
// causes a validation error
// this message is not shown to the user
// see the invalid_message option
throw new TransformationFailedException(sprintf('Land with areaparent "%s" does not exist!',$areaparent));
}
return $land[0];
}
}
Error
I try to debug the symfony/doctrine code, and if i correctly understand, the error appears because the field "uid" is not considered as an "identifier".
See some dump.
Have you got some ideas ?
Thanks !

symfony CRUD Insert json, some doesn't get inserted

So I'm trying to create a method that inserts Recipies to MySQL database.
The schema of my project that a recipe (Recette) has many Ingredients.
The Recette has a Title(titre), Subtitle(Soustitre) and Ingredients that must be inserted in "POST" Request I'll show you my code, my "POST" request in Postman and the Result I get.
Also, My same code has an update function that doesn't work as well and it's always the problem of the ingredients.
This is my Controller:
namespace App\Controller;
use App\Entity\Ingredients;
use App\Entity\Recettes;
use Doctrine\ORM\EntityManagerInterface;
use http\Env\Response;
use Psr\Container\ContainerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use App\Repository\RecettesRepository;
use Symfony\Component\Serializer\Exception\NotEncodableValueException;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class ApiRecetteController extends AbstractController
{
/**
* #Route("/api/recette", name="api_recette_index", methods={"GET"})
*/
public function index(RecettesRepository $recettesRepository)
{
return $this->json($recettesRepository->findAll(), 200, [], ['groups' => 'recette:read']);
}
/**
* #Route("/api/recette", name="api_recette_addRecettes", methods={"POST"})
*/
public function addRecettes(Request $request, SerializerInterface $serializer, EntityManagerInterface
$entityManager)
{
$jsonRecu = $request->getContent();
try {
$recette = $serializer->deserialize($jsonRecu, Recettes::class,"json");
$entityManager->persist($recette);
$entityManager->flush();
return $this->json($recette,201);
}
catch (NotEncodableValueException $e){
return $this->json([
'status'=>400,
'message' =>$e->getMessage()
],400);
}
}
/**
* #Route("/api/recette/Update/{id}", name="api_recette_updateRecettes", methods={"PUT"})
*/
public function UpdateRecettes($id, RecettesRepository $recettesRepository, Request $request,
EntityManagerInterface $entityManager)
{
$entityManger = $this->getDoctrine()->getManager();
$recettes = $entityManger->getRepository(Recettes::class)->find($id);
$data = json_decode($request->getContent(), true);
if (!$recettes) {
throw $this->createNotFoundException(
'Aucune recette trouvé pour id' . $id
);
}
empty($data['Titre']) ? true : $recettes->setTitre($data['Titre']);
empty($data['Soustitre']) ? true : $recettes->setSoustitre($data['Soustitre']);
$entityManager->persist($recettes);
$entityManger->flush();
return $this->json($recettes,204, [], ['groups' => 'recette:read']);
}
/**
* #Route("/api/recette/{id}", name="api_recette_DeleteRecettes", methods={"DELETE"})
*/
public function DeleteRecettes($id, EntityManagerInterface $entityManager, Request $request)
{
$recettes = $entityManager->getRepository(Recettes::class)->find($id);
$entityManager->remove($recettes);
$entityManager->flush();
return $this->json($recettes,202, [], ['groups' => 'recette:read']);
}
}
and My Two Entities
<?PHP
namespace App\Entity;
use App\Repository\IngredientsRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Entity(repositoryClass="App\Repository\IngredientsRepository",
repositoryClass=IngredientsRepository::class)
*/
class Ingredients
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
* #Groups("recette:read")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups("recette:read")
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity=Recettes::class, inversedBy="ingredients")
* #ORM\JoinColumn(nullable=false)
*/
private $recettes;
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 getRecettes(): ?Recettes
{
return $this->recettes;
}
public function setRecettes(?Recettes $recettes): self
{
$this->recettes = $recettes;
return $this;
}
public function __toString(): ?string
{
return $this->getName();
}
}
And Recettes
<?PHP
namespace App\Entity;
use App\Repository\RecettesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* #ORM\Entity(repositoryClass=RecettesRepository::class)
*/
class Recettes
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
* #Groups("recette:read")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
* #Groups("recette:read")
*/
private $Titre;
/**
* #ORM\Column(type="string", length=255, nullable=true)
* #Groups("recette:read")
*/
private $Soustitre;
/**
* #ORM\OneToMany(targetEntity=Ingredients::class, mappedBy="recettes")
*/
private $ingredients;
public function __construct()
{
$this->ingredients = 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 getSoustitre(): ?string
{
return $this->Soustitre;
}
public function setSoustitre(?string $Soustitre): self
{
$this->Soustitre = $Soustitre;
return $this;
}
/**
* #return Collection|Ingredients[]
*/
public function getingredients(): Collection
{
return $this->ingredients;
}
public function addingredients(Ingredients $ingredients): self
{
if (!$this->ingredients->contains($ingredients)) {
$this->ingredients[] = $ingredients;
$ingredients->setRecettes($this);
}
return $this;
}
public function removeingredients(Ingredients $ingredients): self
{
if ($this->ingredients->contains($ingredients)) {
$this->ingredients->removeElement($ingredients);
// set the owning side to null (unless already changed)
if ($ingredients->getRecettes() === $this) {
$ingredients->setRecettes(null);
}
}
return $this;
}
}
My Request json
JSON Row Request
My Response with 201 ok status but empty Ingredients inserted
Response From Server
To insert related entities you need to set cascade={"persist"} to your relation, ie:
/**
* #ORM\OneToMany(targetEntity=Ingredients::class, mappedBy="recettes", cascade={"persist"})
*/
private $ingredients;
Else you will need to persist the Recettes first, get the id and set it on your Ingredientss before persisting them.

Populate dependent select box using Symfony & Ajax

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.

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

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