Symfony ONE-TO-ONE relation - symfony

I am new to symfony2 and have trouble to do a left join between 2 entities. I'm getting following error message, and don't know how to solve this problem:
[Semantical Error] line 0, col 69 near 'i': Error: Class
Bundle\Entity\Users has no association named user_id
Entity Users:
class Users
{
/**
* #ORM\Id
* #ORM\Column(name="user_id", type="string", length="16")
* #ORM\OneToOne(targetEntity="UsersInformation", mappedBy="Users")
*/
protected $user_id;
/**
* #ORM\Column(type="string", length="255")
*/
protected $username;
/**
* #ORM\Column(type="string", length="32")
*/
protected $password;
...
/**
* Set user_id
*
* #param string $userId
*/
public function setUserId($userId)
{
$this->user_id = $userId;
}
/**
* Get user_id
*
* #return string
*/
public function getUserId()
{
return $this->user_id;
}
...
}
Entity UsersInformation:
class UsersInformation
{
/**
* #ORM\Id
* #ORM\Column(type="string", length="16")
* #ORM\OneToOne(targetEntity="Users", inversedBy="UsersInformation")
* #ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
*/
protected $user_id;
/**
* #ORM\Column(type="string", length="255")
*/
public $email;
/**
* #ORM\Column(type="string", length="1")
*/
public $gender;
/**
* #ORM\Column(type="string", length="255")
*/
public $company;
....
}
The UsersRepository looks like this:
public function getAllUsers($limit = null)
{
$qb = $this->createQueryBuilder('u')
->select('u, i')
->leftJoin('u.user_id', 'i');
if (false === is_null($limit))
$qb->setMaxResults($limit);
return $qb->getQuery()
->getResult();
}
What I'm doing wrong?

I think the querybuilder looks alright. Your problem is likely in the data structure.
The way you normally approach this is by setting up a property per relation. For your Users class it'd make sense to have an information or userInformation one for example.
Perhaps something like this helps?
class User
{
/**
* #ORM\Id
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="UserInformation")
*/
protected $information;
}
class UserInformation
{
/**
* #ORM\Id
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="information")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}

Related

Multiple joins in a ManyToMany relation

I have 3 entities, User, Parking and Voiture.
User had a ManyToMany relation with parking while Parking has a OneToMany reamtion with voiture.
what I'm trying to do:
Get the cars (voitures) that belong to all parkings the currect user is related to
how I'm trying to do it:
Using querybuilder, but I still don't know how to make it work
here are my entities
Entity User:
<?php
/**
* #ORM\Entity
* #UniqueEntity(fields="username", message="Username already taken")
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
public function getId()
{
return $this->id;
}
/**
* #ORM\Column(type="string", length=191, unique=true)
* #Assert\NotBlank()
*/
private $username;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Parking", mappedBy="agents")
*/
private $parkings;
public function __construct()
{
$this->parkings = new ArrayCollection();
}
/**
* #return Collection|Parking[]
*/
public function getParkings(): Collection
{
return $this->parkings;
}
public function addParking(Parking $parking): self
{
if (!$this->parkings->contains($parking)) {
$this->parkings[] = $parking;
$parking->addAgent($this);
return $this;
}
return $this;
}
public function removeParking(Parking $parking): self
{
if ($this->parkings->contains($parking)) {
$this->parkings->removeElement($parking);
$parking->removeAgent($this);
}
return $this;
}
}
Entity Parking:
<?php
/**
* #ORM\Entity(repositoryClass="App\Repository\ParkingRepository")
*/
class Parking
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=55)
*/
private $libelle;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\user", inversedBy="parkings")
*/
private $agents;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Voiture", mappedBy="parking", orphanRemoval=true)
*/
private $voitures;
public function __construct()
{
$this->agents = new ArrayCollection();
$this->voitures = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
/**
* #return Collection|user[]
*/
public function getAgents(): Collection
{
return $this->agents;
}
public function addAgent(user $agent): self
{
if (!$this->agents->contains($agent)) {
$this->agents[] = $agent;
}
return $this;
}
public function removeAgent(user $agent): self
{
if ($this->agents->contains($agent)) {
$this->agents->removeElement($agent);
}
return $this;
}
/**
* #return Collection|Voiture[]
*/
public function getVoitures(): Collection
{
return $this->voitures;
}
public function addVoiture(Voiture $voiture): self
{
if (!$this->voitures->contains($voiture)) {
$this->voitures[] = $voiture;
$voiture->setParking($this);
}
return $this;
}
public function removeVoiture(Voiture $voiture): self
{
if ($this->voitures->contains($voiture)) {
$this->voitures->removeElement($voiture);
// set the owning side to null (unless already changed)
if ($voiture->getParking() === $this) {
$voiture->setParking(null);
}
}
return $this;
}
}
And Entity Voiture
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\VoitureRepository")
*/
class Voiture
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
*/
private $matricule;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Parking", inversedBy="voitures")
* #ORM\JoinColumn(nullable=false)
*/
private $parking;
/**
* #ORM\Column(type="boolean")
*/
private $parked;
public function getId(): ?int
{
return $this->id;
}
public function getMatricule(): ?string
{
return $this->matricule;
}
public function setMatricule(string $matricule): self
{
$this->matricule = $matricule;
return $this;
}
public function getParking(): ?Parking
{
return $this->parking;
}
public function setParking(?Parking $parking): self
{
$this->parking = $parking;
return $this;
}
}
I propose to add an intermediate entity UserParking between User and Parking entities.
So we have a OneToMany relationship between User and UserParking and a OneToMany relationship between Parking and UserParking instead of ManyToMany relationship between User and Parking.
The entities will be similar to code below:
Entity User:
<?php
/**
* #ORM\Entity
* #UniqueEntity(fields="username", message="Username already taken")
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
public function getId()
{
return $this->id;
}
/**
* #ORM\Column(type="string", length=191, unique=true)
* #Assert\NotBlank()
*/
private $username;
/**
* #ORM\OneToMany(targetEntity="App\Entity\UserParking", mappedBy="agent")
* #ORM\JoinColumn(nullable=true)
*/
private $user_parking;
// getter and setter
}
Entity Parking:
<?php
/**
* #ORM\Entity(repositoryClass="App\Repository\ParkingRepository")
*/
class Parking
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=55)
*/
private $libelle;
/**
* #ORM\OneToMany(targetEntity="App\Entity\UserParking", mappedBy="parking")
* #ORM\JoinColumn(nullable=true)
*/
private $user_parking;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Voiture", mappedBy="parking", orphanRemoval=true)
*/
private $voitures;
// getters and setters
}
Entity UserParking
/**
* UserParking
*
* #ORM\Table(name="user_parking")
* #ORM\Entity(repositoryClass="App\Repository\UserParkingRepository")
*/
class UserParking
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="user_parking")
* #ORM\JoinColumn(nullable=false)
*/
private $agent;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Parking", inversedBy="user_parking")
* #ORM\JoinColumn(nullable=false)
*/
private $parking;
// getter and setter
}
And Entity Voiture
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\VoitureRepository")
*/
class Voiture
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=200)
*/
private $matricule;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Parking", inversedBy="voitures")
* #ORM\JoinColumn(nullable=false)
*/
private $parking;
/**
* #ORM\Column(type="boolean")
*/
private $parked;
}
So to get the cars (voitures) that belong to all parkings the current user is related to you need to:
1- Get the current user object.
2- Get the UserParking array from the user object .
3- Get the Parking objects from the UserParking array.
4- Get the cars from the Parking objects.
The code will be similar to this:
$em = $this->getDoctrine()->getManager();
/* Get user from the session */
$user = $this->getUser();
$userParkings = $user->getUserParking();
$parkings = [];
foreach ($userParkings as $item) {
$parking = $item->getParking();
$parkings[count($parkings)] = $parking;
}
// you can get voitures from parkings
Start from Voiture entity and inner join the Parking and User entities using their associations:
$queryBuilder = $this->getDoctrine()->getRepository('App:Voiture')->createQueryBuilder('v');
$queryBuilder->innerJoin('v.parking', 'p');
$queryBuilder->innerJoin('v.agents', 'a');
Finally, you can filter on the relation either through a condition:
$queryBuilder->where('a.id = :userId');
$queryBuilder->setParameter("userId", 1);
$cars = $queryBuilder->getQuery()->getResult();
or place the condition on the $queryBuilder->innerJoin('v.agents', 'a', 'WITH', 'a.id = :userId'); see doctrine inner join with condition
References
SQL join: where clause vs. on clause

Symfony Iterating over ArrayCollection

In my app I have 2 entities; User & Booking.
Booking entity:
namespace App\Entity;
/**
* #ORM\Table(name="booking")
* #ORM\Entity(repositoryClass="App\Repository\BookingRepository")
*/
class Booking
{
/**
* #ORM\Column(type="boolean")
* #Assert\NotBlank()
*/
private $isActive;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="bookings")
*/
private $user;
User entity:
/**
* #ORM\Table(name="app_user")
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields="email", message="This email address is already in use")
*/
class User implements AdvancedUserInterface
{
/**
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank()
* #Assert\Email()
*/
private $email;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="user")
* #Expose
*/
private $bookings;
/**
* User constructor.
*/
public function __construct()
{
$this->bookings = new ArrayCollection();
}
I tried to add a function to my user entity that returns the active booking, I tried this:
/**
* #return mixed
*/
public function getActiveBooking()
{
foreach( $this->bookings as $booking ) {
if( $booking->getIsActive() ) {
return $booking;
}
}
}
But I get the following error: Error: Call to a member function getRoom() on null
When I call it using $user->getActiveBooking()->getRoom()->getId()
Make sure that the user you are working with has an active booking.
getActiveBooking() is returning null because it seems user does not have an active booking.
That's why you are getting an error that you cannot call getRoom() on null because the previous function has returned null.
Have you tried to add a joinColumn like this:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="bookings")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
private $user;

Serialization of UploadedFile is not allowed

I'm trying to upload a file via vichuploader bundle on my Users entity.
Using hwioauthbundle that implements UserInterface, and i think the errors comes from that bundle...
So every time i try to uplod a file i got this exception :
Serialization of 'Symfony\Component\HttpFoundation\File\UploadedFile'
is not allowed
I already tried this solution but also same exception.
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use Symfony\Component\Security\Core\User\UserInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\OAuthUser;
/**
* Users
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UsersRepository")
*
*/
class Users extends OAuthUser
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #var string
*
* #ORM\Column(name="first_name", type="string", length=255)
*/
private $firstName;
/**
* #var string
*
* #ORM\Column(name="last_name", type="string", length=255)
*/
private $lastName;
/**
* #var string
*
* #ORM\Column(name="civility", type="string", length=255, nullable=true)
*/
private $civility;
/**
* #var string
*
* #ORM\Column(name="avatar", type="string", length=255, nullable=true)
*/
private $avatar;
/**
* #var string
*
* #ORM\Column(name="qualification", type="string", length=255, nullable=true)
*/
private $qualification;
/**
* #var string
*
* #ORM\Column(name="level", type="string", length=255, nullable=true)
*/
private $level;
/**
* #var \DateTime
*
* #ORM\Column(name="birth_date", type="date", nullable=true)
*/
private $birthDate;
/**
* #var \DateTime
*
* #ORM\Column(name="hiring_date", type="date", nullable=true)
*/
private $hiringDate;
/**
* #var string
*
* #ORM\Column(name="country", type="string", length=255, nullable=true)
*/
private $country;
/**
* #var string
*
* #ORM\Column(name="city", type="string", length=255, nullable=true)
*/
private $city;
/**
* #var string
*
* #ORM\Column(name="linkedin_profil", type="string", length=255, nullable=true)
*/
private $linkedinProfil;
/**
* #var string
*
* #ORM\Column(name="web_site", type="string", length=255, nullable=true)
*/
private $webSite;
/**
* #var \DateTime
*
* #ORM\Column(name="date_last_cnx", type="datetimetz", nullable=true)
*/
private $dateLastCnx;
/**
*
* #ORM\OneToOne(targetEntity="AppBundle\Entity\Files",cascade={"persist"})
* #ORM\JoinColumn(name="file_id", referencedColumnName="id")
*/
public $cv;
/** #ORM\Column(name="google_id", type="string", length=255, nullable=true) */
private $google_id;
Files.php
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* Files
* #Vich\Uploadable
* #ORM\Table(name="files")
* #ORM\Entity(repositoryClass="AppBundle\Repository\FilesRepository")
*/
class Files
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName", size="imageSize")
*
* #var File
*/
protected $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
protected $imageName;
public function getId()
{
return $this->id;
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*
* #return Product
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
return $this;
}
/**
* #return File|null
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*
* #return Product
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* #return string|null
*/
public function getImageName()
{
return $this->imageName;
}
}
My form Userstype:
use AppBundle\Form\FilesType;
->add('cv',FilesType::class)
My form Filestype:
-> add('imageFile',
VichFileType::class, [
'required' => false,
'allow_delete' => true,
'download_link' => true,
'label' => false,
]);
You are serializing ImageFile entity within this code that's why you are getting the error , try removing that and add updatedAt field so that doctrine can track changes on the entity:
/**
* Files
* #ORM\Table(name="files")
* #ORM\Entity(repositoryClass="AppBundle\Repository\FilesRepository")
* #Vich\Uploadable
*/
class Files implements \Serializable
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName", size="imageSize")
*
* #var File
*/
protected $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
protected $imageName;
/**
* #ORM\Column(type="datetime")
*
* #var string
*/
protected $updatedAt;
public function getId()
{
return $this->id;
}
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*
* #return Product
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
return $this;
}
/**
* #return File|null
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*
* #return Product
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* #return string|null
*/
public function getImageName()
{
return $this->imageName;
}
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize(array(
$this->id,
$this->imageName,
));
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
) = unserialize($serialized);
}
/**
* #return string
*/
public function getUpdatedAt()
{
return $this->updatedAt;
}
/**
* #param string $updatedAt
*/
public function setUpdatedAt($updatedAt)
{
$this->updatedAt = $updatedAt;
}
}
for Symfony5
Class User implement UserInterface
public function __serialize(): array
{
return [
'id' => $this->id,
'email' => $this->email,
//......
];
}
public function __unserialize(array $serialized): User
{
$this->id = $serialized['id'];
$this->email = $serialized['email'];
// .....
return $this;
}
Check this out, one of your Users's relation has an UploadedFile property, which is essentially a File aka a filestream which aren't allowed to be serialized. Therefore you need to specify what you want to serialized:
https://symfony.com/doc/3.3/security/entity_provider.html
Basically you only neeed to serialized id, username and password (three is an example code on the above page)

How to use Sluggable with a custom mapping type?

In a Symfony2 project, I created a custom mapping field in order to encrypt strings in the database as suggested in this StackOverflow question.
I want to slugify one of the database fields using the Gedmo\Sluggable Doctrine extension. But obviously I get the following error message because the "encrypted_string" is not an allowed type:
[Gedmo\Exception\InvalidMappingException]
Cannot use field - [username] for slug storage, type is not valid and must
be 'string' or 'text' in class - My\PrivateApplication\Bundle\UserBundle
\Entity\User
--EDIT--
This is my Entity:
<?php
namespace My\PrivateApplication\Bundle\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo,
Gedmo\Translatable\Translatable;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* My\PrivateApplication\Bundle\UserBundle\Entity\User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="UserRepository")
*/
class User implements AdvancedUserInterface, \Serializable {
/**
* #var int
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(type="encrypted_string", length=255)
*/
private $surname;
/**
* #var string
*
* #ORM\Column(type="encrypted_string", length=255)
*/
private $name;
/**
* #var string
*
* #ORM\Column(type="encrypted_string", length=255, unique=true)
*/
private $username;
/**
* #var string
*
* #ORM\Column(type="string", length=255)
*/
private $password;
/**
* #var string
*
* #ORM\Column(type="encrypted_string", length=255)
*/
private $email;
/**
* #var string
*
* #ORM\Column(type="string", length=255, unique=true)
* #Gedmo\Slug(fields={"username"})
*/
private $slug;
/**
* #var string
*
* #ORM\Column(name="salt", type="string", length=40)
*/
private $salt;
/**
* Whether the account is active or not
*
* #var boolean
*
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
/**
* Whether the account is locked or not
*
* #var boolean
*
* #ORM\Column(name="is_locked", type="boolean")
*/
private $isLocked;
/**
* #var \DateTime
*
* #ORM\Column(name="created_at", type="datetime", nullable=true)
* #Gedmo\Timestampable(on="create")
*
*/
private $createdAt;
/**
* #var \DateTime
*
* #ORM\Column(name="updated_at", type="datetime", nullable=true)
* #Gedmo\Timestampable(on="update")
*/
private $updatedAt;
/**
* #var string
*
* #Gedmo\Locale
*/
private $locale;
/**
* Set the locale for the translatable behavior
*
* #param string $locale
*/
public function setTranslatableLocale($locale) {
$this->locale = $locale;
}
public function eraseCredentials()
{
}
public function getPassword()
{
return $this->password;
}
public function getRoles()
{
return array((string)$this->getRole());
}
public function getSalt()
{
return $this->salt;
}
public function getUsername()
{
return $this->username;
}
public function isAccountNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return ($this->isLocked()==0)? false : true;
}
public function isCredentialsNonExpired()
{
return true;
}
public function isEnabled()
{
return ($this->isActive==0) ? false : true;
}
/**
* Serialize the User object
* #see Serializable::serialize()
*/
public function serialize()
{
return serialize(array($this->id, $this->username, $this->password, $this->salt));
}
/**
* Unserialize the User object
* #see Serializable::unserialize()
*/
public function unserialize($serialized)
{
list($this->id, $this->username, $this->password, $this->salt) = unserialize($serialized);
}
//other accessor methods
}
The property $validTypes of the class Gedmo\Sluggable\Mapping\Driver\Annotation seems to define the valid types for the sluggable. How I can modify the SluggableListener to use my new custom type?
Thank you very much.

RoleInterface throws "call on a non-object" error

I'm working over Symfony 2.0.16
I have in my UserProvider the method getRoles
public function getRoles()
{
/**
* #var \Doctrine\Common\Collections\ArrayCollection $rol
*/
return $this->rol->toArray();
}
and my Rol entity has the role interface
class Rol implements \Symfony\Component\Security\Core\Role\RoleInterface
//...
public function getRole()
{
return $this->getName();
}
but when I try to login I get the following error
Fatal error: Call to a member function getRole() on a non-object in C:\Users\julian\Code\parqueadero\vendor\symfony\src\Symfony\Bundle\SecurityBundle\DataCollector\SecurityDataCollector.php on line 57
Reading the class SecurityDataCollector, the error is thrown by a Closure
array_map(function ($role){ return $role->getRole();}, $token->getRoles()
Now I change this to
array_map(function ($role){ var_dump($role); return $role->getRole();}, $token->getRoles()
To my surprise, $role is a object Rol but I can't understand why I get the error.
I found the solution the problem is a bug in PHP 5.4 (the php i'm using) serialize method the github user yoannch proposed this solution, is overwrite the serialize/unserialize methods using json_encode/json_decode methods
class User implements \Serializable
//...
/**
* Serializes the content of the current User object
* #return string
*/
public function serialize()
{
return \json_encode(
array($this->username, $this->password, $this->salt,
$this->rol, $this->id));
}
/**
* Unserializes the given string in the current User object
* #param serialized
*/
public function unserialize($serialized)
{
list($this->username, $this->password, $this->salt,
$this->rol, $this->id) = \json_decode(
$serialized);
}
only need change the correct name properties
I had the same problem (Windows, PHP 5.4.5), updated to 5.4.7 and it still did not work. Nevertheless, I came up with a workaround that needs less maintenance (when overwriting the serialization functions as described in the article you mentioned, you'll have to keep them up to date when adding/removing fields). So far it works for me, I hope there are no other problems resulting from the workaround I might have forgotten. Just alter the User's getRoles() function like that:
/**
* #inheritDoc
*/
public function getRoles()
{
$roles = array();
foreach ($this->userRoles as $role) {
$roles[] = $role->getRole();
}
return $roles;
}
Note that $role->getRole() returns the role name as a String (e.g. ROLE_ADMIN).
solution of this problem is very simple.
All problems associated with circular references at yours User and Role objects.
So you have not to serialize User::$roles and Role::$users fields.
Look at Symfony\Component\Security\Core\Authentication\Token\AbstractToken::__construct() and Symfony\Component\Security\Core\Authentication\Token\AbstractToken::serialize().
How you can see, Symfony take your user's roles by invoking UserInterface::getRoles() before serialization. And serialize User and Roles separately.
You have to implement \Serializable interface in User and Role entities.
Example:
/**
* Acme\Bundle\UserBundle\Entity\User
*
* #ORM\Table(name="`user`")
* #ORM\Entity(repositoryClass="Acme\Bundle\UserBundle\Entity\UserRepository")
*/
class User implements AdvancedUserInterface, EquatableInterface, \Serializable
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $username
*
* #ORM\Column(type="string", length=30, unique=true)
*/
private $username;
/**
* #var string $email
*
* #ORM\Column(type="string", length=100, unique=true)
*/
private $email;
/**
* #var string $salt
*
* #ORM\Column(type="string", length=40)
*/
private $salt;
/**
* #var string $password
*
* #ORM\Column(type="string", length=128)
*/
private $password;
/**
* #var boolean $isActive
*
* #ORM\Column(type="boolean")
*/
private $isActive;
/**
* User's roles. (Owning Side)
*
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
*/
private $roles;
// .....
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
/*
* ! Don't serialize $roles field !
*/
return \serialize(array(
$this->id,
$this->username,
$this->email,
$this->salt,
$this->password,
$this->isActive
));
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->email,
$this->salt,
$this->password,
$this->isActive
) = \unserialize($serialized);
}
}
/**
* Acme\Bundle\UserBundle\Entity\Role
*
* #ORM\Table(name="role")
* #ORM\Entity
*
*/
class Role implements RoleInterface, \Serializable
{
/**
* #var integer $id
*
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string $role
*
* #ORM\Column(name="role", type="string", length=20, unique=true)
*/
private $role;
/**
* Users in group (Inverse Side)
*
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="User", mappedBy="roles")
*/
private $users;
// .....
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
/*
* ! Don't serialize $users field !
*/
return \serialize(array(
$this->id,
$this->role
));
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list(
$this->id,
$this->role
) = \unserialize($serialized);
}
}
And all will be serialized/unserialized correctly.
See discus at https://github.com/symfony/symfony/issues/3691
See also:
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/entities-in-session.html#serializing-entity-into-the-session

Resources