Symfony 4 - FOSUserBundle - Render user information on custom route - php

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,
));
}
}

Related

PHPStan and Doctrine: $id is never written, only read

I am using PHP8, symfony5 and doctrine2 with phpstan and getting these errors:
Property App\Entity\User::$id is never written, only read.
The code:
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Table(name="`user`")
* #ORM\Entity(repositoryClass=UserRepository::class)
* #UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
public const ROLE_USER = 'ROLE_USER';
public const ROLE_ADMIN = 'ROLE_ADMIN';
/**
* #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\Column(type="boolean")
*/
private $isVerified = false;
public function __construct()
{
$this->audioSessions = 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 getUserIdentifier(): 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 PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function isVerified(): bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): self
{
$this->isVerified = $isVerified;
return $this;
}
}
The code is correct which is also acknowledged by PHPStan as described here: https://phpstan.org/blog/detecting-unused-private-properties-methods-constants#what-if-my-code-is-%E2%80%9Cspecial%E2%80%9D%3F
So how can i solve these error message in PHPStan?
I tried installing https://github.com/phpstan/phpstan-doctrine and enabled this in my phpstan.neon file:
includes:
- vendor/phpstan/phpstan-doctrine/extension.neon
- vendor/phpstan/phpstan-doctrine/rules.neon
But the error still persists.
I am using phpstan ignore errors (https://phpstan.org/user-guide/ignoring-errors). To narrow the scope, my path is set to entity directory only.
# ./phpstan.neon
parameters:
ignoreErrors:
-
message: '#Property [a-zA-Z0-9\\_]+::\$id is never written, only read.#'
path: src/Entity/*
You need to configure objectManagerLoader so that the extension can see the entity metadata.
This will allow DQL validation, and the correct entity repositoryClass to be inferred when accessing $entityManager->getRepository(). Add to your phpstan.neon:
parameters:
doctrine:
objectManagerLoader: tests/object-manager.php
Example for Symfony 5:
// tests/object-manager.php
use App\Kernel;
use Symfony\Component\Dotenv\Dotenv;
require __DIR__ . '/../vendor/autoload.php';
(new Dotenv())->bootEnv(__DIR__ . '/../.env');
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();
From the documentation:
https://github.com/phpstan/phpstan-doctrine#configuration
Since this RFC is implemented, PHPStan implemented this feature in the 1.4 version. Unforthunully, there is no #readonly attribute (at least for the moment) that can be used with PHP < 8. For more detail, feel free to see this issue.

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

VichUploaderBundle, Form\Type\VichImageType Error Produccion

First of all, I apologize for my basic use of english, I hope you can understand me.
I'm doing a deployment of a project, the development was on Symfony 5.1, using easyadmin-bundle 3.1, and vich/uploader-bundle 1.15. On my localhost that works great, but when I move to production, on my dashboard, can't 'Create New' or 'Edit' in any entity who has images inside, it throw me this error.
An error has occurred resolving the options of the form "Vich\UploaderBundle\Form\Type\VichImageType": The options "upload_dir", "upload_filename" do not exist.
https://i.ibb.co/gWRjPLm/Screenshot-2020-11-20-An-error-has-occurred-resolving-the-options-of-the-form-Vich-Uploader-Bundle-F.png
The only place I find upload_dir, is inside vendor folder.
https://i.ibb.co/VHw39z5/Screenshot-2020-11-20-Symfony-Profiler.png
My entity
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass=ColoresRepository::class)
* #Vich\Uploadable()
*/
class Colores
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=30)
*/
private $nombre;
/**
* #ORM\Column(type="string", length=100)
*/
private $thumbnail;
/**
* #Vich\UploadableField(mapping="colores", fileNameProperty="thumbnail")
*/
private $thumbnailFile;
/**
* #ORM\Column(type="datetime")
*/
private $updatedAt;
public function __construct()
{
$this->updatedAt = new \DateTime();
}
/**
* #return mixed
*/
public function getThumbnailFile()
{
return $this->thumbnailFile;
}
/**
* #param mixed $thumbnailFile
*/
public function setThumbnailFile($thumbnailFile): void
{
$this->thumbnailFile = $thumbnailFile;
if($thumbnailFile) {
$this->updatedAt = new \DateTime();
}
}
/**
* #return mixed
*/
public function getThumbnail()
{
return $this->thumbnail;
}
/**
* #param mixed $thumbnail
*/
public function setThumbnail($thumbnail): void
{
$this->thumbnail = $thumbnail;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
public function getId(): ?int
{
return $this->id;
}
public function getNombre(): ?string
{
return $this->nombre;
}
public function setNombre(string $nombre): self
{
$this->nombre = $nombre;
return $this;
}
public function __toString()
{
return $this->nombre;
}
}
My Dashboard
<?php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Router\CrudUrlGenerator;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use App\Entity\Colores;
class DashboardController extends AbstractDashboardController
{
/**
* #Route("admin", name="admin")
*/
public function index(): Response
{
$routeBuilder = $this->get(CrudUrlGenerator::class)->build();
return $this->redirect($routeBuilder->setController(ColoresCrudController::class)->generateUrl());
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('Test Site');
}
public function configureMenuItems(): iterable
{
yield MenuItem::section('DESTACADOS');
yield MenuItem::linkToCrud('Colores', 'fa fa-paint-brush', Colores::class);
}
My Crud controller
<?php
namespace App\Controller\Admin;
use App\Entity\Colores;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use Vich\UploaderBundle\Form\Type\VichImageType;
class ColoresCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Colores::class;
}
public function configureFields(string $pageName): iterable
{
return [
TextField::new('nombre'),
ImageField::new('thumbnailFile')
->setFormType(VichImageType::class)->onlyOnForms(),
ImageField::new('thumbnail')
->setBasePath('/images/colores')->hideOnForm()
];
}
}
vich_uploader.yaml
vich_uploader:
db_driver: orm
mappings:
colores:
uri_prefix: /images/colores
upload_destination: '%kernel.project_dir%/public/images/colores'
namer: Vich\UploaderBundle\Naming\UniqidNamer
Using setFormType is an "undocumented hack"
There is an issue with the v3.1.8 and the ImageField.
You can try this syntax :
$filename = ImageField::new('filename', 'File')
->setBasePath('uploads/contact_message')
->setUploadDir('public/uploads/contact_message/');
If it doesn't work you can roll back to v.3.1.7 (force the version in your composer.json) and using the old syntax with 2 field ( the file and the filename)
$avatar = ImageField::new('avatar')->setBasePath('uploads/images/users')->setLabel('Photo');
$avatarFile = ImageField::new('avatarFile')->setFormType(VichImageType::class);
if (Crud::PAGE_INDEX === $pageName) {
return [ $avatar];
} elseif (Crud::PAGE_EDIT=== $pageName) {
return [$avatarFile];
You can create your own Custom Field (see https://symfony.com/doc/current/bundles/EasyAdminBundle/fields.html#creating-custom-fields) like this :
namespace App\Admin\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): self
{
return (new self())
->setProperty($propertyName)
->setLabel($label)
->setFormType(VichImageType::class)
->setCustomOption('image_uri', null)
->setCustomOption('download_uri', null)
;
}
public function setImageUri($imageUri): self
{
$this->setCustomOption('image_uri', $imageUri);
return $this;
}
public function setDownloadUri($downloadUri): self
{
$this->setCustomOption('download_uri', $downloadUri);
return $this;
}
}
And the use it in your crud controller configureFileds method :
VichImageField::new('avatarFile', 'Avatar')
->setDownloadUri('public/' . $this->getParameter('app.path.user_avatars'))
->setImageUri($this->getParameter('app.path.user_avatars'))
,
It works with the last EasyAdminBundle > v3.1.7

Doctrine ORM: composite and foreign keys as primary key

I am trying to get composite and foreign keys as primary keys working in Doctrine ORM. I know what I'm trying to do is possible because it is described here: https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/tutorials/composite-primary-keys.html#composite-and-foreign-keys-as-primary-key. This is exactly my use-case: I have some products, an order and an order item.
However, doctrine orm is unable to map this relation unto the database. The current problem is that only one of the annotated \Id primary keys is reflected on the mysql database. So $producto is translated unto the database correctly as producto_id and is both a primary key and a foreign key. However, the $orden property which is annotated in the same way doesn't appear whatsoever on my database.
This seems odd because when I was first testing this feature I tried only with one of the two properties and it worked fine, however, when both properties are annotated only one seems to be parsed by the metadata parser. Furthermore, I tried to revert my project to a usable state by forgetting about the foreign keys and just have a composite primary key (like I had it before), but now the parser doesn't seem to even recognize the primary key. For example, for:
class ProductoOrden
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $idOrden;
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $idProducto;
I get:
bash-3.2$ php bin/console make:migration
In MappingException.php line 52:
No identifier/primary key specified for Entity "App\Entity\ProductoOrden". Every Entity must
have an identifier/primary key.
So I'm unable to set it up properly or to revert it to the previous state (which is the strangest of all).
I'm about to restart my whole project from scratch, because I cannot make sense of how the metadata parsing works. I am worried I have screwed up the process because I have manually erased the files at 'src\Migrations' because of similar issues before and php bin/console doctrine:migrations:version --delete --all didn't seem to work or I haven't understood the proper use of it.
In conclusion: ¿Could anyone assert if what I am trying to do with ProducoOrden is possible (maybe I'm not understanding the documentation example)? Is there any way to completely wipe out previous cache about the annotations/ schema metadata?
I've looked unto the orm:schema-tool but I don't really get how to configure it properly or why I have to configure it at all of I already have the bin/console tool on my project.
I will show all three involved classes for completeness sake, but the main problem is within ProductoOrden (Order-items).
<?php
//Products
namespace App\Entity;
use App\Repository\ProductosRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=ProductosRepository::class)
* #UniqueEntity("idProducto", message=" {producto {{ value }}}: llave primaria violada ")
*/
class Productos
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $nombreProducto;
/**
* #ORM\Column(type="string", length=255)
*/
private $descripcionProducto;
/**
* #ORM\Column(type="string", length=255)
*/
private $urlImagen;
/**
* #ORM\Column(type="integer")
*/
private $puntosProducto;
public function getIdProducto(): ?int
{
return $this->idProducto;
}
public function getCodProducto(): ?int
{
return $this->idProducto;
}
public function setIdProducto(int $codProducto): self
{
$this->idProducto = $codProducto;
return $this;
}
public function getNombreProducto(): ?string
{
return $this->nombreProducto;
}
public function setNombreProducto(string $nombreProducto): self
{
$this->nombreProducto = $nombreProducto;
return $this;
}
public function getDescripcionProducto(): ?string
{
return $this->descripcionProducto;
}
public function setDescripcionProducto(string $descripcionProducto): self
{
$this->descripcionProducto = $descripcionProducto;
return $this;
}
public function getUrlImagen(): ?string
{
return $this->urlImagen;
}
public function setUrlImagen(string $urlImagen): self
{
$this->urlImagen = $urlImagen;
return $this;
}
public function getPuntosProducto(): ?int
{
return $this->puntosProducto;
}
public function setPuntosProducto(int $puntosProducto): self
{
$this->puntosProducto = $puntosProducto;
return $this;
}
public function __toString(){
$str = '{producto:'.$this->getIdProducto().', nombre: '.$this->getNombreProducto().'}';
return $str;
}
}
<?php
\\Orders
namespace App\Entity;
use App\Repository\OrdenesRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=OrdenesRepository::class)
* #UniqueEntity("idOrden", message="{orden {{ value }}}: llave primaria violada")
*/
class Ordenes
{
/**
* #ORM\Id()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="integer")
*/
private $totalOrden;
/**
* #ORM\Column(type="string", length=255)
*/
private $estado;
/**
* #ORM\OneToMany(targetEntity=ProductoOrden::class, mappedBy="orden", orphanRemoval=true)
*/
private $productosOrden;
public function __construct()
{
$this->productosOrden = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getIdOrden(): ?int
{
return $this->idOrden;
}
public function setIdOrden(int $idOrden): self
{
$this->idOrden = $idOrden;
return $this;
}
public function getTotalOrden(): ?int
{
return $this->totalOrden;
}
public function setTotalOrden(int $totalOrden): self
{
$this->totalOrden = $totalOrden;
return $this;
}
public function getEstado(): ?string
{
return $this->estado;
}
public function setEstado(string $estado): self
{
$this->estado = $estado;
return $this;
}
public function __toString(){
$str = '{orden:'.$this->getIdOrden().'}';
return $str;
}
/**
* #return Collection|ProductoOrden[]
*/
public function getProductosOrden(): Collection
{
return $this->productosOrden;
}
public function addProductosOrden(ProductoOrden $productosOrden): self
{
if (!$this->productosOrden->contains($productosOrden)) {
$this->productosOrden[] = $productosOrden;
$productosOrden->setOrden($this);
}
return $this;
}
public function removeProductosOrden(ProductoOrden $productosOrden): self
{
if ($this->productosOrden->contains($productosOrden)) {
$this->productosOrden->removeElement($productosOrden);
// set the owning side to null (unless already changed)
if ($productosOrden->getOrden() === $this) {
$productosOrden->setOrden(null);
}
}
return $this;
}
}
<?php
\\Order-items
namespace App\Entity;
use App\Repository\ProductoOrdenRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=ProductoOrdenRepository::class)
* #UniqueEntity(fields={"idOrden","idProducto"}, message="{prod. orden {{ value }}}: llave primaria violada")
*/
class ProductoOrden
{
/*
* #ORM\Id
* #ORM\ManyToOne(targetEntity=Ordenes::class, inversedBy="productosOrden")
* #ORM\JoinColumn(nullable=false)
*/
private $orden;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity=Productos::class)
* #ORM\JoinColumn(nullable=false)
*/
private $producto;
/**
* #ORM\Column(type="integer")
*/
private $puntos;
/**
* #ORM\Column(type="integer")
*/
private $cantidad;
public function getId(): ?int
{
return $this->idOrden;
}
public function setIdOrden(int $idOrden): self
{
$this ->idOrden = $idOrden;
return $this;
}
public function getIdProducto(): ?int
{
return $this->idProducto;
}
public function setIdProducto(int $idProducto): self
{
$this->idProducto = $idProducto;
return $this;
}
public function getPuntos(): ?int
{
return $this->puntos;
}
public function setPuntos(int $puntos): self
{
$this->puntos = $puntos;
return $this;
}
public function getCantidad(): ?int
{
return $this->cantidad;
}
public function setCantidad(int $cantidad): self
{
$this->cantidad = $cantidad;
return $this;
}
public function __toString(){
$str = '{productoOrden:'.$this->getId().', '.$this->getIdProducto().'}';
return $str;
}
public function getOrden(): ?Ordenes
{
return $this->orden;
}
public function setOrden(?Ordenes $orden): self
{
$this->orden = $orden;
return $this;
}
}
For the shown classes, the migration it generates is
final class Version20200814210929 extends AbstractMigration
{
public function getDescription() : string
{
return '';
}
public function up(Schema $schema) : void
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE producto_orden ADD puntos INT NOT NULL');
}
public function down(Schema $schema) : void
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE producto_orden DROP puntos');
}
}
As you can see, small changes like changing the type of a property work; but it doesn't seem to take on the id() and the association annotations.
Many thanks

Entity not found with Symfony 5.1

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.

Categories