I'm trying to setup a many to many between fos Userbundle and my own group bundle so that I can group users. this is working fine. I can set a new group and can add as many users to this group as I like to. But when I want to check if a user is in a group, I get a Index join Column error. I think I didn't understand the usage of manytomany the correct way so it would be nice if you can help me getting the point.
My entities look like:
User:
class User extends BaseUser
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
protected $usergroups;
//....//
And my Group Entity looks like:
/**
* #ORM\ManyToMany(targetEntity="PrUserBundle\Entity\User", inversedBy="id")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
* #var user
*/
private $user;
//....
/**
* Add user
*
* #param \PrUserBundle\Entity\User $user
* #return Lieferanten
*/
public function addUser(\PrUserBundle\Entity\User $user)
{
$this->user[] = $user;
return $this;
}
/**
* Remove user
*
* #param \PrUserBundle\Entity\User $user
*/
public function removeUser(\PrUserBundle\Entity\User $user)
{
$this->user->removeElement($user);
}
/**
* Get user
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getUser()
{
return $this->user;
}
When I try to catch all users in a group, I get an error:
$group=$em->getRepository('PrGroupBundle:Group')->findAll();
var_dump($lfr[0]->getUser()->getId());
I guess I missunderstood how to handle the bidirectional manytomany. Or can I use a manytoone also?
Related
I have to internationalize an app and particularly an entity called Program. To do so, I created an other entity ProgramIntl which contains a "locale" attribute (en_GB, fr_FR, etc) and strings which must be internationalized. I want the programIntl attribute in Program to be an associative array (with locale as key).
We have an API to read/write programs. GET and POST works fine but when I want to update data (PUT), the programIntl is not updated: an insert query is launched (and fails because of the unique constraint, but that's not the question).
Here is the code:
In Program.php:
/**
* #var
*
* #ORM\OneToMany(targetEntity="ProgramIntl", mappedBy="program", cascade={"persist", "remove", "merge"}, indexBy="locale", fetch="EAGER")
* #ORM\JoinColumn(nullable=false, onDelete="cascade")
* #Groups({"program_read", "program_write"})
*/
private $programIntl;
public function addProgramIntl($programIntl)
{
$this->programIntl[$programIntl->getLocale()] = $programIntl;
$programIntl->setProgram($this);
return $this;
}
public function setProgramIntl($programIntls)
{
$this->programIntl->clear();
foreach ($programIntls as $locale => $programIntl) {
$programIntl->setLocale($locale);
$this->addProgramIntl($programIntl);
}
}
public function getProgramIntl()
{
return $this->programIntl;
}
In ProgramIntl.php:
/**
* #ORM\Entity(repositoryClass="App\Repository\ProgramIntlRepository")
* #ORM\Table(name="program_intl",uniqueConstraints={#ORM\UniqueConstraint(name="program_intl_unique", columns={"program_id", "locale"})})
*/
class ProgramIntl
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"program_read", "program_write"})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Program", inversedBy="programIntl")
* #ORM\JoinColumn(nullable=false)
*/
private $program;
/**
* #ORM\Column(type="string", length=5, options={"fixed" = true})
*/
private $locale;
/**
* #ORM\Column(type="string", length=64)
* #Assert\NotBlank()
* #Groups({"program_read", "program_write"})
*/
private $some_attr;
/* ... */
}
Any idea of what could be the reason of the "insert" instead of "update" ?
Thanks
I forgot to mention that we use api-platform.
But I found the solution myself. In case anyone is interested, adding the following annotation to classes Program and ProgramIntl solved the problem:
/* #ApiResource(attributes={
* "normalization_context"={"groups"={"program_read", "program_write"}},
* "denormalization_context"={"groups"={"program_read", "program_write"}}
* }) */
I'm trying to use the JMSSerializer with Symfony to build a simple json api.
So i have 2 simple Entities (1 User can have many Cars, each Car belongs to one User):
class Car
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="cars")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
}
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Car", mappedBy="user", orphanRemoval=true)
*/
private $cars;
}
Now i want to get all Cars with their User.
My Controller:
class CarController extends AbstractController
{
/**
* #param CarRepository $carRepository
*
* #Route("/", name="car_index", methods="GET")
*
* #return Response
*/
public function index(CarRepository $carRepository)
{
$cars = $carRepository->findAll();
$serializedEntity = $this->container->get('serializer')->serialize($cars, 'json');
return new Response($serializedEntity);
}
}
This will throw a 500 error:
A circular reference has been detected when serializing the object of
class \"App\Entity\Car\" (configured limit: 1)
Ok, sounds clear. JMS is trying to get each car with the user, and go to the cars and user ....
So my question is: How to prevent this behaviour? I just want all cars with their user, and after this, the iteration should be stopped.
You need to add max depth checks to prevent circular references.
This can be found in the documentation here
Basically you add the #MaxDepth(1) annotation or configure max_depth if you're using XML/YML configuration. Then serialize like this:
use JMS\Serializer\SerializationContext;
$serializer->serialize(
$data,
'json',
SerializationContext::create()->enableMaxDepthChecks()
);
Example Car class with MaxDepth annotation:
class Car
{
/**
* #\JMS\Serializer\Annotation\MaxDepth(1)
*
* [..]
*/
private $user;
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;
In my web application, I want my user's to be able to create roles and add users to them dynamically. The only thing I imagine, is to edit the security.yml every time, but this can't be the best solution, can it? It would be very nice, if there is something like a User Provider for roles, so I can define one which loads the roles from a database (doctrine).
Thanks for your help, hice3000.
Then, you should want to add a role Entity to your model Hice.
You have to know that Symfony2 provides support for dynamic roles too. You have a getRoles() method in the Symfony2 User spec in the API Doc, that your User entity should implement, that forces him to return Roles. These roles must either implement the role interface that specifies a getRole() method that returns, most usually, the role name itself.
You can then add the newly created role directly to your user role list that the getRoles() user method will then return.
Here is an example using annotations :
First role class
/**
* Role class
*
* #ORM\Entity()
*/
class Role implements RoleInterface, \Serializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="userRoles")
*/
private $users;
public function __construct()
{
$this->users = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getRole()
{
return $this->name;
}
}
And the User class
/**
* User
*
* #ORM\Entity()
*/
class User implements UserInterface, \Serializable
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255)
*/
private $username;
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
* #ORM\JoinTable(name="user_roles")
*/
private $userRoles;
public function __construct()
{
$this->userRoles = new \Doctrine\Common\Collections\ArrayCollection();
}
public function getRoles()
{
return $this->userRoles->toArray();
}
I've skipped imports and methods to simplify the approach.
EDIT : There is something to know about serialization too. As Sharom commented on github, you musn't serialize users in roles neither roles in users. Just read his post and I think you'll understand :)
I want to remove the need for a username in the FOSUserBundle. My users will login using an email address only and I've added real name fields as part of the user entity.
I realised that I needed to redo the entire mapping as described here.
I think I've done it correctly but when I try to submit the registration form I get the error:
"Only field names mapped by Doctrine can be validated for uniqueness."
The strange thing is that I haven't tried to assert a unique constraint to anything in the user entity.
Here is my full user entity file:
<?php
// src/MyApp/UserBundle/Entity/User.php
namespace MyApp\UserBundle\Entity;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
* #ORM\Table(name="depbook_user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your first name.", groups={"Registration", "Profile"})
* #Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
*/
protected $firstName;
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your last name.", groups={"Registration", "Profile"})
* #Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
*/
protected $lastName;
/**
* #ORM\Column(type="string", length=255)
*
* #Assert\NotBlank(message="Please enter your email address.", groups={"Registration", "Profile"})
* #Assert\MaxLength(limit="255", message="The name is too long.", groups={"Registration", "Profile"})
* #Assert\Email(groups={"Registration"})
*/
protected $email;
/**
* #ORM\Column(type="string", length=255, name="email_canonical", unique=true)
*/
protected $emailCanonical;
/**
* #ORM\Column(type="boolean")
*/
protected $enabled;
/**
* #ORM\Column(type="string")
*/
protected $salt;
/**
* #ORM\Column(type="string")
*/
protected $password;
/**
* #ORM\Column(type="datetime", nullable=true, name="last_login")
*/
protected $lastLogin;
/**
* #ORM\Column(type="boolean")
*/
protected $locked;
/**
* #ORM\Column(type="boolean")
*/
protected $expired;
/**
* #ORM\Column(type="datetime", nullable=true, name="expires_at")
*/
protected $expiresAt;
/**
* #ORM\Column(type="string", nullable=true, name="confirmation_token")
*/
protected $confirmationToken;
/**
* #ORM\Column(type="datetime", nullable=true, name="password_requested_at")
*/
protected $passwordRequestedAt;
/**
* #ORM\Column(type="array")
*/
protected $roles;
/**
* #ORM\Column(type="boolean", name="credentials_expired")
*/
protected $credentialsExpired;
/**
* #ORM\Column(type="datetime", nullable=true, name="credentials_expired_at")
*/
protected $credentialsExpiredAt;
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* #return string
*/
public function getLastName()
{
return $this->lastName;
}
/**
* Sets the first name.
*
* #param string $firstname
*
* #return User
*/
public function setFirstName($firstname)
{
$this->firstName = $firstname;
return $this;
}
/**
* Sets the last name.
*
* #param string $lastname
*
* #return User
*/
public function setLastName($lastname)
{
$this->lastName = $lastname;
return $this;
}
}
I've seen various suggestions about this but none of the suggestions seem to work for me. The FOSUserBundle docs are very sparse about what must be a very common request.
I think the easiest way to go about this is to leave the bundle as is and rather setup your user class to have a username equal to the email address.
Do this by overriding the setEmail() method to also set the $username property to the $email parameter and the setEmailCanonical() to also set the $usernameCanonical to the $emailCanonical.
public function setEmail($email){
$this->email = $email;
$this->username = $email;
}
public function setEmailCanonical($emailCanonical){
$this->emailCanonical = $emailCanonical;
$this->usernameCanonical = $emailCanonical;
}
All you will have to do other than this is semantics related. Like having your form label read E-mail instead of the default Username label. You can do this by overriding the translations files. I'll leave this up to you (or someone else) since it might not even be necessary for you.
With this strategy you will have redundant data in your database but it will save you a lot of remapping headache.
If you are using doctrine 2, you can use Life Cycle Events to put your logic inside a callback.
http://docs.doctrine-project.org/en/2.0.x/reference/events.html
/**
* #ORM\PreUpdate()
* #ORM\PrePersist()
*/
public function setUsernameToEmail()
{
$this->username = $this->email;
$this->usernameCanonical = $this->emailCanonical;
}
When I didn't want to require users to enter emails (thus making emails optional in FOSUserBundle), I use Symfony 2.7 + FOSUser+SonataUser+SonataAdmin.
At the same time I needed entered emails to be unique in the system. So Users have 2 options when registering:
Leave email empty
Provide a unique email, that is not yet in the system
Below is my solution that works as expected (I don't claim it to be the cleanest one, but hopefully it will show you a way how to accomplish a similar task)
1) Changes to Entity/User.php
namespace AppBundle\Entity;
use Sonata\UserBundle\Entity\BaseUser as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="fos_user")
*
*
* #ORM\AttributeOverrides({
* #ORM\AttributeOverride(name="email",
* column=#ORM\Column(
* type = "string",
* name = "email",
* nullable = true,
* unique = true
* )
* ),
* #ORM\AttributeOverride(name="emailCanonical",
* column=#ORM\Column(
* type = "string",
* name = "email_canonical",
* nullable = true,
* unique = true
* )
* )
* })
*
\*/
class User extends BaseUser
{
2) Executed app/console doctrine:migrations:diff & migrate, database tables were changed as expected adding "DEFAULT NULL" to email and email_canonical fields
3) No matter what I tried, email was being set to NULL, but email_canonical wasn't, it was returning "". I tried manually setting it to NULL in my RegistrationFormHandler, var_dump there confirmed that it was indeed set to NULL when email wasn't entered. But to the database FOSUser would submit "" empty string, which violated UNIQUE constraint I had set for emails, so the solution was to override method in Entity/User.php (as is discussed in previous answers to this question)
// src/AppBundle/Entity/User.php
// ...
public function setEmailCanonical($emailCanonical)
{
// when email is empty, force canonical to NULL
// for some reason by default "" empty string is inserted
$this->emailCanonical = $this->getEmail();
}
4) Change Validation for FOSUserBundle (or SonataUserBundle) in my case , so that it doesn't require email to be set. (I simply removed .. from validation.xml as non of those applied to email anymore)
Copy these 2 files into your config/validation/ directory (for SonataUser+FOSUser it is: Application/Sonata/UserBundle/Resources)
vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Resources/config/storage-validation/orm.xml
above path, config/validation/orm.xml
Rename "Registration" group in those files to your own name, like "myRegistration".
Bind your new validation_group to fos_user in config.yml. If using Sonata User, it is:
sonata_user:
profile:
register:
form:
...
validation_groups:
- myRegistration
- Default
Have fun.