OneToOne in Symfony 2 - symfony

I try to save related entities User and Profile. I use the cascade={"persist"} option. Data saves properly in Database except user_id field in Profile table, its equal to null. When i turn relations profile_id field in User table saves properly. Where is my mistake? Here is my relations:
User entity:
/**
* #ORM\OneToOne(targetEntity="Profile", mappedBy="user", ,cascade={"persist"})
*/
Profile entity:
/**
* #ORM\OneToOne(targetEntity="User", inversedBy="profile", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
Getters & setters:
User:
` /**
* #param Profile $profile
*/
public function setProfile(Profile $profile)
{
$this->profile = $profile;
}
/**
* #return Profile
*/
public function getProfile()
{
return $this->profile;
}`
Profile:
`/**
* #param User $user
*/
public function setUser(User $user)
{
$this->user = $user;
}
/**
* #return User
*/
public function getUser()
{
return $this->user;
}`

This is essentially #skler answer but I couldn't explain myself in comments.
Try doing this in your User.php's setProfile():
public function setProfile(Profile $profile)
{
$this->profile = $profile;
$this->profile->setUser($this);
}

Maybe you add only profile in user: $user->addProfile($profile); and you didn't the viceversa: $profile->addUser($user);

Related

Selecting If One Row From Entity Exists in Symfony / Twig

I am making a web app using Symfony 4.
The app has (among others) a User entity, Post entity, and a PostLike entity. A user can create many posts, and a post can have many likes. So PostLike references User and Post. Below is my PostLike entity:
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Gedmo\Mapping\Annotation as Gedmo;
/**
* #ORM\Entity(repositoryClass="App\Repository\PostLikeRepository")
*/
class PostLike
{
/**
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="postLikes")
* #ORM\JoinColumn(nullable=true)
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Post", inversedBy="postLikes")
* #ORM\JoinColumn(nullable=true)
*/
private $post;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(type="datetime")
*/
private $createdAt;
/**
* #return mixed
*/
public function getId()
{
return $this->id;
}
/**
* #param mixed $id
*/
public function setId($id): void
{
$this->id = $id;
}
/**
* #return mixed
*/
public function getUser()
{
return $this->user;
}
/**
* #param mixed $user
*/
public function setUser($user): void
{
$this->user = $user;
}
/**
* #return mixed
*/
public function getPost()
{
return $this->post;
}
/**
* #param mixed $post
*/
public function setPost($post): void
{
$this->post = $post;
}
public function getCreatedAt()
{
return $this->createdAt;
}
}
When I am on the view page for an individual post, how would I reference whether a user has liked this post in TWIG? This will be the ‘many’ side of the relationship, but I just need one row (if it exists), and I’m not sure how to do this...
TIA.
In the controller you can check whether such PostLike with such user and post exist or not and pass it to the view:
$liked = false;
$postLike = $this->getDoctrine()->getManager()->getRepository('AppBundle:PostLike')->findOneBy(['user'=>$user->getId(),'post'=>$post->getId()]);
if($postLike !== null){
$liked = true;
}
If you want to simply show whether Likes exist you can add a field to the Post entity:
public function hasLikes()
{
return (0 === count($this->likes)) ? false : true;
}
and include in twig something like {% if post.hasLikes %}Liked{% endif %}.
You could do something similar with a count and a badge to show the number of likes.

Delete an entity inside another entity Symfony

I have 2 entities : User And Love. This is a "One-To-Many, Bidirectional".
Here are my entities:
User:
namespace SE\UserBundle\Entity;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="SE\UserBundle\Repository\UserRepository")
*/
class User extends BaseUser
{
//...
/**
* One User has Many Loves.
* #ORM\OneToMany(targetEntity="SE\CoreBundle\Entity\Love", mappedBy="user", cascade={"persist"})
*/
private $loves;
//...
public function __construct()
{
//...
$this->loves = new ArrayCollection();
//...
}
//...
/**
* Remove love
*
* #param \SE\CoreBundle\Entity\Love $love
*/
public function removeLove(\SE\CoreBundle\Entity\Love $love)
{
$this->loves->removeElement($love);
//delete love
}
//...
}
Love :
namespace SE\CoreBundle\Entity;
/**
* Love
*
* #ORM\Table(name="love")
* #ORM\Entity(repositoryClass="SE\CoreBundle\Repository\LoveRepository")
*/
class Love
{
//...
/**
* Many Loves have One User.
* #ORM\ManyToOne(targetEntity="SE\UserBundle\Entity\User", inversedBy="loves", cascade={"persist"})
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
//...
/**
* Set user
*
* #param \SE\UserBundle\Entity\User $user
*
* #return Love
*/
public function setUser(\SE\UserBundle\Entity\User $user = null)
{
$this->user = $user;
return $this;
}
//...
}
My question is:
Is there a way, when i execute removeLove() on my User, the corresponding Love entity autodelete in my database without using an entity manager in my controller. I would like my controller to look like that:
class DefaultController extends Controller
{
public function indexAction()
{
//...
$em = $this->getDoctrine()->getManager();
$user->removeLove($love);
$em->persist($user);
$em->flush();
//...
}
}
And the result would be that, $love would not be in getLoves() of the $user and $love would be delete of the database.
You could try: Orphan Removal option
Make sure Entity Love is not associated with another User. So one Entity Love is always associated with only one User

Doctrine: Get auto increment ID before persist/flush

I'm having trouble in Doctrine-Fixtures. I'd like to add a user and a email in another entity, but in relation to the user. So here is my process:
// Create user
$user1 = new User();
// Create user email and add the foreign key to the user
$user1Mail = new UserEmail();
$user1Mail->setEmail('test#example.com');
$user1Mail->setUser($user1);
// Add attributes
$user1->setEmail($user1Mail);
// ...
$manager->persist($user1Mail);
$manager->persist($user1);
$manager->flush();
I add the user of the email in $user1Mail->setUser($user1); before the persist, but the problem is, the user has no primary key --> the ID (auto increment). So to create the relation between the email and the user, the user needs to have a primary key to refer to.
I know the solution to create a unique token before and set this to the ID of the user, but I think this is a uncomfortable way because I need to check if the user ID is already in use.
Is there a good way to handle this?
// EDIT:
Here is the necessary entity relation:
User:
class User implements UserInterface, \Serializable
{
// ...
/**
* #var Application\CoreBundle\Entity\UserEmail
*
* #ORM\OneToOne(
* targetEntity="UserEmail",
* cascade={"persist"}
* )
* #ORM\JoinColumn(
* name="primaryEmail",
* referencedColumnName="id",
* nullable=false,
* onDelete="restrict"
* )
*/
private $email;
// ...
}
UserEmail:
class UserEmail
{
// ...
/**
* #var Application\CoreBundle\Entity\User
* #ORM\ManyToOne(
* targetEntity="User",
* cascade={"persist", "remove"}
* )
* #ORM\JoinColumn(
* name="userID",
* referencedColumnName="id",
* nullable=false
* )
*/
private $user;
// ...
}
As you can see, if you add a user you have to add a UserEmail also. But the UserEmail requires that the userID is already set, but it is only set if you persist the user into the db. How can I realize a fix for it?
I find it strange to see that your User has a OneToOne association towards UserEmail, and UserEmail has a ManyToOne association towards User, and those are 2 separate associations.
I think you'd rather have a single bidirectional OneToMany association:
class User implements UserInterface, \Serializable
{
// ...
/**
* #var ArrayCollection
*
* #ORM\OneToMany(targetEntity="UserEmail", mappedBy="user", cascade={"persist", "remove"})
*/
private $emails;
public function __construct()
{
$this->emails = new ArrayCollection();
}
/**
* #param UserEmail $email
*/
public function addEmail(UserEmail $email)
{
$this->emails->add($email);
$email->setUser($this);
}
/**
* #param UserEmail $email
*/
public function removeEmail(UserEmail $email)
{
$this->emails->removeElement($email);
$email->setUser(null);
}
/**
* #return UserEmail[]
*/
public function getEmails()
{
return $this->emails->toArray();
}
// ...
}
class UserEmail
{
// ...
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="emails")
* #ORM\JoinColumn(name="userID", referencedColumnName="id", nullable=FALSE)
*/
private $user;
/**
* #param User $user
*/
public setUser(User $user = null)
{
$this->user = $user;
}
/**
* #return User[]
*/
public function getUser()
{
return $this->user;
}
// ...
}
I've put a cascade on User::$emails, so any changes to User get cascaded towards UserEmail. This will make managing them easier.
Using this would look something like this:
$email = new UserEmail();
$user = new User();
$user->addEmail($email);
$em->persist($user);
$em->flush();
About foreign keys
Doctrine will manage the foreign keys of your entities for you. You don't need to manually set them on your entities when using associations.
Primary email
Personally I would add a property to UserEmail to mark it as primary. You'll need a bit more logic in the entities, but managing them will become effortless.
Here's the additional code you need:
class User
{
// ...
/**
* #param UserEmail $email
*/
public function addEmail(UserEmail $email)
{
$this->emails->add($email);
$email->setUser($this);
$this->safeguardPrimaryEmail();
}
/**
* #param UserEmail $email
*/
public function removeEmail(UserEmail $email)
{
$this->emails->removeElement($email);
$email->setUser(null);
$this->safeguardPrimaryEmail();
}
/**
* #param UserEmail $email
*/
public function setPrimaryEmail(UserEmail $newPrimaryEmail)
{
if (!$this->emails->contains($newPrimaryEmail)) {
throw new \InvalidArgumentException('Unknown email given');
}
foreach ($this->emails as $email) {
if ($email === $newPrimaryEmail) {
$email->setPrimary(true);
} else {
$email->setPrimary(false);
}
}
}
/**
* #return UserEmail|null
*/
public function getPrimaryEmail()
{
foreach ($this->emails as $email) {
if ($email->isPrimary()) {
return $email;
}
}
return null;
}
/**
* Make sure there's 1 and only 1 primary email (if there are any emails)
*/
private function safeguardPrimaryEmail()
{
$primaryFound = false;
foreach ($this->emails as $email) {
if ($email->isPrimary()) {
if ($primaryFound) {
// make sure there's no more than 1 primary email
$email->setPrimary(false);
} else {
$primaryFound = true;
}
}
}
if (!$primaryFound and !$this->emails->empty()) {
// make sure there's at least 1 primary email
$this->emails->first()->setPrimary(true);
}
}
// ...
}
class UserEmail
{
// ...
/**
* #var boolean
*
* #ORM\Column(type="boolean")
*/
private $isPrimary = false;
/**
* #internal Use
* #param bool $isPrimary
*/
public function setPrimary($isPrimary)
{
$this->isPrimary = (bool)$isPrimary;
}
/**
* #return bool
*/
public function isPrimary()
{
return $this->isPrimary;
}
// ...
}
You'll probably notice safeguardPrimaryEmail(). This will make sure the primary-mark will remain consistent when adding/removing emails.
Using this is very simple:
An email that's created is not primary by default.
If it's the first email added to a user, it will automatically become primary.
Additionally added emails will remain not primary.
When the primary email is removed, the first remaining email will become primary.
You can manually set another primary email by calling User::setPrimaryEmail().
There are many variations to this concept possible, so just view this as an example and refine it to your needs.
It's because Doctrine will generate the entity id when it's inserted into the database.
You can do it with an extra flush():
$user1 = new User();
$manager->persist($user1);
$manager->flush();
// Create user email and add the foreign key to the user
$user1Mail = new UserEmail();
$user1Mail->setEmail('test#example.com');
$user1Mail->setUser($user1);
// Add attributes
$user1->setEmail($user1Mail);
// ...
$manager->persist($user1Mail);
$manager->persist($user1);
$manager->flush();
Or you can set the email mapping in your User class to cascade persist.
It means that if a new not persisted object is added to that object, then the new object will be saved as well.
I don't know the exact structure of the entites, but it would look like
/**
* #ORM\OneToOne(targetEntity="user", cascade={"persist"})
* #ORM\JoinColumn(name="user_email_id", referencedColumnName="id")
*/
private $userEmail
So if you set a new e-mail to the user it will be auto-persisted if you persist the User entity.
I would prefer the second method if it works. I hope it will help.
Doctrine reference: Transitive persistence / Cascade Operations

how to update automatically doctrine entity from database

I create a database and I had the entity file, I added a new column to my table, now how can I update the Entity class and add getter ad setter to this new element ?
the table contains ; userid, username, firstname, password
I added a column "admin" (boolean)
here is my class : Users:
<?php
namespace Login\LoginBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Redirect
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Login\LoginBundle\Entity\Users")
*/
class Users
{
/**
* #var string
*/
private $userName;
/**
* #var string
*/
private $firstName;
/**
* #var string
*/
private $password;
/**
* #var integer
*/
private $userid;
/**
* Set userName
*
* #param string $userName
* #return Users
*/
public function setUserName($userName)
{
$this->userName = $userName;
return $this;
}
/**
* Get userName
*
* #return string
*/
public function getUserName()
{
return $this->userName;
}
/**
* Set firstName
*
* #param string $firstName
* #return Users
*/
public function setFirstName($firstName)
{
$this->firstName = $firstName;
return $this;
}
/**
* Get firstName
*
* #return string
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Set password
*
* #param string $password
* #return Users
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Get userid
*
* #return integer
*/
public function getUserid()
{
return $this->userid;
}
}
The generator bundle gives you a command to generate an entity from a set of fields via the app/console doctrine:generate:entity command.
Internally, this works by using template files to create your new entities, based on your inputs. Unfortunately, the tool does not yet have the ability to modify existing classes.
If you'd like, you can request that feature here: https://github.com/sensiolabs/SensioGeneratorBundle
But in the meantime, your best bet is one of the following:
If you haven't modified anything, you could erase the file and re-create it using app/console doctrine:generate:entity.
You can just add the field yourself, along with the getters, setters, and Doctrine configuration, using the formats you see used for the other fields in that class (this is probably the easier way, to be honest).

Symfony 2 security: account activation

I'm using Symfony 2 security system.
When some user trying to login, I want to additionally check whether user's field "activated" is true. If not, error message appears: "You have to activate your account first".
How can i implement this feature?
If you're using Doctrine as user provider you can implement the AdvancedUserInterface. This interface (definition visible below) provide the isEnabled() method which is equal to account activation status method. If this method return false, the user will have an error message like Your account is not enabled when he attempt to login.
I use it to provide e-mail validation on subscription.
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien#symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Security\Core\User;
/**
* AdvancedUserInterface adds status flags to a regular account.
*
* #author Fabien Potencier <fabien#symfony.com>
*/
interface AdvancedUserInterface extends UserInterface
{
/**
* Checks whether the user's account has expired.
*
* #return Boolean true if the user's account is non expired, false otherwise
*/
function isAccountNonExpired();
/**
* Checks whether the user is locked.
*
* #return Boolean true if the user is not locked, false otherwise
*/
function isAccountNonLocked();
/**
* Checks whether the user's credentials (password) has expired.
*
* #return Boolean true if the user's credentials are non expired, false otherwise
*/
function isCredentialsNonExpired();
/**
* Checks whether the user is enabled.
*
* #return Boolean true if the user is enabled, false otherwise
*/
function isEnabled();
}
i have tried for the first time. but got an error message
"User is locked"
it try to figure it out how to implement form AdvanceUserInterface.
I want to share code how to implement it, here you go :
class User implements AdvancedUserInterface, \Serializable
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=25, unique=true)
*/
private $username;
/**
* #ORM\Column(type="string", length=255)
* #Assert\Length(min=8, max=255)
*/
private $password;
/**
* #ORM\Column(type="string", length=60, unique=true)
* #Assert\Email(message = "The email '{{ value }}' is not a valid email.", checkMX = true, checkHost = true)
*/
private $email;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
/**
* #ORM\ManyToMany(targetEntity="Role", inversedBy="users")
*
*/
private $roles;
private $salt;
public function __construct()
{
$this->isActive = true;
$this->roles = new ArrayCollection();
// may not be needed, see section on salt below
// $this->salt = md5(uniqid(null, true));
}
/**
* #inheritDoc
*/
public function getUsername()
{
return $this->username;
}
/**
* #inheritDoc
*/
public function getSalt()
{
// you *may* need a real salt depending on your encoder
// see section on salt below
return null;
}
/**
* #inheritDoc
*/
public function getPassword()
{
return $this->password;
}
/**
* #inheritDoc
*/
public function getRoles()
{
//return array('ROLE_USER');
return $this->roles->toArray();
}
/**
* #inheritDoc
*/
public function eraseCredentials()
{
}
/**
* #see \Serializable::serialize()
*/
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt,
));
}
/**
* #see \Serializable::unserialize()
*/
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
// see section on salt below
// $this->salt
) = unserialize($serialized);
}
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set username
*
* #param string $username
* #return User
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* Set password
*
* #param string $password
* #return User
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Set salt
*
* #param string $salt
* #return User
*/
public function setSalt($salt)
{
$this->salt = $salt;
return $this;
}
/**
* Set email
*
* #param string $email
* #return User
*/
public function setEmail($email)
{
$this->email = $email;
return $this;
}
/**
* Get email
*
* #return string
*/
public function getEmail()
{
return $this->email;
}
/**
* Set isActive
*
* #param boolean $isActive
* #return User
*/
public function setIsActive($isActive)
{
$this->isActive = $isActive;
return $this;
}
/**
* Get isActive
*
* #return boolean
*/
public function getIsActive()
{
return $this->isActive;
}
/**
* Add roles
*
* #param \Bsi\BkpmBundle\Entity\Role $roles
* #return User
*/
public function addRole(\Bsi\BkpmBundle\Entity\Role $roles)
{
$this->roles[] = $roles;
return $this;
}
/**
* Remove roles
*
* #param \Bsi\BkpmBundle\Entity\Role $roles
*/
public function removeRole(\Bsi\BkpmBundle\Entity\Role $roles)
{
$this->roles->removeElement($roles);
}
public function isEnabled()
{
return $this->getIsActive();
}
public function isAccountNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return true;
}
public function isCredentialsNonExpired()
{
return true;
}
}
You can use the same procedure outlined here for handling expired passwords:
How to use the credentials expired property in Symfony AdvancedUserInterface?
Except you will be handling the Locked exception:
http://api.symfony.com/2.5/Symfony/Component/Security/Core/Exception/LockedException.html
Ignore this section. It doesn't apply when using the symfony security code
Just follow http://symfony.com/doc/current/cookbook/service_container/event_listener.html and handle the various security exceptions there:
LockedException
AccountExpiredException
CredentialsExpiredException
DisabledException
When using symfony's security code
You can't listen to kernel.exception events when dealing with form login. The security code handle all of the exceptions internally.
In order to handle security exceptions during login, you need to implement:
AuthenticationFailureHandlerInterface
AuthenticationSuccessHandlerInterface
I am implementing in my code now. There are several articles on the subject, but were difficult to find. I'll post them when I validate them.
The authentication success/failure handlers can't be used to redirect to a change password form since the user is all ready denied when they execute.
Although they turned out to be great places to dispatch events, like 'account_disabled', 'account_locked', 'account_expired', or 'credentials_expired'.
As long as the destination page allows anonymous users, you can redirect a user where ever you want. But if the destination page requires login, you will have a redirect loop.
A custom authentication listener will propably be needed for this. Another possibility would be to implement a doctrine onload listener and auth voter that sets a 'provisional user' flag when the account/credentials are expired and allows access to change user attributes.
I will eventually need to implement some similar solution, but I don't have time currently.
There are probably other ways.

Resources