FOSUserBundle override mapping to remove need for username - symfony

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.

Related

Symfony OneToMany with associative array : new row inserted instead of update

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"}}
* }) */

Symfony - Entity with a unique value in two fields

In my user entity, I have two fields (username and email)
and I want them to be mutually unique. I put the Annotation « UniqueEntity » on the top of my entity class and a unique property on each field like this :
/**
* Class User
* #package App\Entity
* #ORM\Table(name="user", uniqueConstraints={
* #ORM\UniqueConstraint(name="app_user_email", columns="email"),
* #ORM\UniqueConstraint(name="app_user_username", columns="username")
* })
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #UniqueEntity(fields={"email"})
* #UniqueEntity(fields={"username"})
*/
class User implements UserInterface, \Serializable
{
....
/**
* #var string
*
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\NotBlank
*/
private $username;
/**
* #var string
*
* #ORM\Column(type="string", length=255, unique=true)
* #Assert\Email
* #Assert\NotBlank
* #Assert\Length(max=255, maxMessage="...")
*/
private $email;
....
}
In my database I can't have 2 identical email or username. This is a valid point. I want moreover that a value in one of the two fields is not found in one of the other two fields.
For example, if an entry in the User table has a specific email value, I do not want to be able to enter the same value in the username field for an other user.
What is the best way to do this ?
The purpose of all this is for a user to be able to authenticate with his username or email and if a mail for a user is equal to a username of another user it will cause a problem.
Hy, in your entity you can add a callback constraint
First add
use Symfony\Component\Validator\Context\ExecutionContextInterface;
and in your class add
/**
* #Assert\Callback
*/
public function validate(ExecutionContextInterface $context, $payload)
{
if ($this->username == $this->email) {
$context->buildViolation('The username can\'t be the email')
->atPath('username')
->addViolation();
}
}
check https://symfony.com/doc/current/reference/constraints/Callback.html for your symfony version

symfony entity catching up inversed Values in many to many

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?

how to check whether foreign key is working or not in symfony2 with doctrine

I have followed One-to-Many relation not working and created a one to many relationship
i have a users table where i have following fields
- id(primary key)
- name
- pwd
i have attachments table where user can upload more than one file i.e one user_id contains multiple files
- id
- user_id(foreignkey)
- path
my user entity contains the following code
namespace Repair\StoreBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
/**
* users
* #ORM\Entity(repositoryClass="Repair\StoreBundle\Entity\usersRepository")
*/
class users
{
/**
* #var integer
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
--some code --
/**
* #ORM\OneToMany(targetEntity="attachments", mappedBy="user_id")
*/
private $attachments;
public function __construct()
{
$this->attachments= new ArrayCollection();
}
/**
* Add attachments
*
* #param \Repair\StoreBundle\Entity\attachments $attachments
* #return users
*/
public function addAttachment(\Repair\StoreBundle\Entity\attachments $attachments)
{
$this->attachments[] = $attachments;
return $this;
}
/**
* Remove attachments
*
* #param \Repair\StoreBundle\Entity\attachments $attachments
*/
public function removeAttachment(\Repair\StoreBundle\Entity\attachments $attachments)
{
$this->attachments->removeElement($attachments);
}
/**
* Get attachments
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getAttachments()
{
return $this->attachments;
}
this is my attachments entity
namespace Repair\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* attachments
*/
class attachments
{
-- some code for id--
private $id;
/**
* #var integer
* #ORM\Column(name="user_id", type="integer", nullable=false)
* #ORM\ManyToOne(targetEntity="users", inversedBy="users")
* #ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
*/
protected $userId;
public function getId()
{
return $this->id;
}
/**
* Set userId
* #param integer $userId
* #return attachments
*/
public function setUserId($userId)
{
$this->userId = $userId;
return $this;
}
/**
* Get userId
*
* #return integer
*/
public function getuserId()
{
return $this->userId;
}
--Some code for paths --
}
It is not displaying any errors
but how to know whether the foriegn key is set or not i went to phpmyadmin and checked the indexes it only shows the primary keys.please say whether i did correct or not and how to check whether foreign key is set or not
problem is in your annotations. In your attachment entity you should have annotation like this.
* #ORM\ManyToOne(targetEntity="users", inversedBy="annotations")
you don't have to have join column annotation. But if you want to have it there, it should look like this.
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
Also it shouldn't be called user_id but user, because doctrine takes it as whole entity.

Symfony2 forms - Setting several fields using one method

Let's say that I have an entity.
This entity has two field that should be changed only through one function, because of domain logic
How could I make the form framework to set the values using one method call.
What I've read about data transformers, lead me to the impression that it can not be used to this propose.
Next is form events, this are the available events
PRE_BIND
BIND
POST_BIND
PRE_SET_DATA
POST_SET_DATA
BIND_CLIENT_DATA
BIND_NORM_DATA
SET_DATA
but the documentation about this is very scarce.
This is a sample entity
<?php
namespace X3\TestBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Test
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="X3\TestBundle\Entity\TestRepository")
*/
class Test
{
/**
* #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;
/**
* #var string
*
* #ORM\Column(name="firstValue", type="string", length=255)
*/
private $firstValue;
/**
* #var string
*
* #ORM\Column(name="secondValue", type="string", length=255)
*/
private $secondValue;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
* #return Test
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* Set firstValue
*
* #param string $firstValue
* #return Test
*/
protected function setFirstValue($firstValue)
{
$this->firstValue = $firstValue;
return $this;
}
/**
* Get firstValue
*
* #return string
*/
public function getFirstValue()
{
return $this->firstValue;
}
/**
* Set secondValue
*
* #param string $secondValue
* #return Test
*/
protected function setSecondValue($secondValue)
{
$this->secondValue = $secondValue;
return $this;
}
/**
* Get secondValue
*
* #return string
*/
public function getSecondValue()
{
return $this->secondValue;
}
//
// The objective here is that the form use this function
// to set the two values in one call
//
protected function setValues($firstValue, $secondValue)
{
$this->firstValue = $firstValue;
$this->secondValue = $secondValue;
return $this;
}
}
Do note that setFirstValue($firstValue) and setSecondValue($secondValue) are protected, the values should be set using the method setValues($firstValue, $secondValue)
Is there an event I can use, to retrieve the firstValue and secondValue and set it using setValues($firstValue, $secondValue) and avoid the form component to complain about Method "setFirstValue()" is not public in class...?
Some code or link to it, would be a bonus.
Extending the form framework would not be easy, even if using some of form events.
One possible solution would be simply create values property in the entity which isn't mapped to any database field, and its setter and getter. Then the setValues function can do whatever needed to set firstValue and secondValue.
You may want to use DataTransformer. It allows you to control the transformation of data in both directions. Anyway you need to add values field to form, that will transformed to two fields and saved as one field.

Resources