Symfony 2 security: account activation - symfony

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.

Related

Symfony authenticator: In controllers, Doctrine returns user item with empty string field, although a value is set

My database schema consists mainly of the following entities: user and auth_token. Each user can have multiple auth_token's.
The problem: when selecting the currently authenticated user in a controller, the string field saltedPasswordHash is empty (""), although a value is set in the database. Getting saltedPasswordHash in the ApiKeyAuthenticator.php works (please have a look at the two TODO comments).
For whatever reason, selecting the email (string) or created (datetime) field works. Persisting new user entities with a saltedPasswordHash or selecting any other user item works fine.
An APIKeyAuthenticator is handling the authorization. When disabling firewall and authentication, everything works as expected. I included the source files below.
I'm using PHP 7.2.15-1 with mysql Ver 15.1 Distrib 10.3.13-MariaDB.
Security/ApiKeyAuthenticator.php
namespace App\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface
{
public function createToken(Request $request, $providerKey)
{
$apiKey = $request->headers->get('authToken');
if (!$apiKey) {
throw new BadCredentialsException();
// or to just skip api key authentication
// return null;
}
return new PreAuthenticatedToken(
'anon.',
$apiKey,
$providerKey
);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
if (!$userProvider instanceof ApiKeyUserProvider) {
throw new \InvalidArgumentException(
sprintf(
'The user provider must be an instance of ApiKeyUserProvider (%s was given).',
get_class($userProvider)
)
);
}
$apiKey = $token->getCredentials();
$username = $userProvider->getUsernameForApiKey($apiKey);
if (!$username) {
// CAUTION: this message will be returned to the client
// (so don't put any un-trusted messages / error strings here)
throw new BadCredentialsException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
$user = $userProvider->loadUserByAuthToken($apiKey);
if (!isset($user)) {
throw new BadCredentialsException(
sprintf('API Key "%s" does not exist.', $apiKey)
);
}
// TODO: HERE, THE $user->getSaltedPasswordHash() RETURNS THE CORRECT VALUE!
return new PreAuthenticatedToken(
$user, // TODO: with "new User()" instead, it works!
$apiKey,
$providerKey,
$user->getRoles()
);
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new Response(
// this contains information about *why* authentication failed
// use it, or return your own message
strtr($exception->getMessageKey(), $exception->getMessageData()),
401
);
}
}
Security/ApiKeyUserProvider.php
namespace App\Security;
use App\Entity\AuthToken;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class ApiKeyUserProvider implements UserProviderInterface
{
/**
* #var EntityManagerInterface
*/
private $em;
/**
* ApiKeyUserProvider constructor.
* #param EntityManagerInterface $em
*/
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
public function getUsernameForApiKey($apiKey)
{
return $apiKey;
}
public function loadUserByUsername($username)
{
// TODO: Implement loadUserByUsername() method.
}
/**
* Auth token is used as username
*
* #param string $authToken
* #return null|UserInterface
*/
public function loadUserByAuthToken($authToken): ?UserInterface
{
if (!isset($authToken)) {
return null;
}
$token = $this->em
->getRepository(AuthToken::class)
->findOneBy(['id' => AuthToken::hex2dec($authToken)]);
if (!isset($token)) {
return null;
}
return $token->getUser();
}
public
function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this example, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
public
function supportsClass($class)
{
return User::class === $class;
}
}
Entity/User.php
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
*/
class User implements UserInterface, EquatableInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* This needs to be nullable because the email includes the id of newly created users, which can only be obtained after inserting the new record.
* #ORM\Column(type="string", length=255, nullable=true, unique=true)
* #Assert\Length(max=255)
* #Assert\NotBlank()
*/
private $email;
/**
* Set null to disable login
* #ORM\Column(type="string", length=255, nullable=true)
* #Assert\Length(max=255)
* #Assert\NotBlank()
*/
private $saltedPasswordHash;
/**
* #ORM\Column(type="datetime")
*/
private $created;
// ...
/**
* #ORM\OneToMany(targetEntity="App\Entity\AuthToken", mappedBy="user", fetch="LAZY")
*/
private $authTokens;
/**
* #ORM\Column(type="string", length=5)
*/
private $role;
// ...
public function __construct()
{
$this->role = 'user';
$this->saltedPasswordHash = null;
}
public function getId()
{
return $this->id;
}
public function getSaltedPasswordHash(): ?string
{
return $this->saltedPasswordHash;
}
public function setSaltedPasswordHash(?string $saltedPasswordHash): self
{
$this->saltedPasswordHash = $saltedPasswordHash;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(?string $email): self
{
$this->email = $email;
return $this;
}
public function getCreated(): ?\DateTimeInterface
{
return $this->created;
}
public function setCreated(\DateTimeInterface $created): self
{
$this->created = $created;
return $this;
}
/**
* #return ArrayCollection
*/
public function getAuthTokens()
{
return $this->authTokens;
}
/**
* #param ArrayCollection $authTokens
* #return User
*/
public function setAuthTokens(ArrayCollection $authTokens): User
{
$this->authTokens = $authTokens;
return $this;
}
/**
* #param AuthToken $authToken
* #return User
*/
public function addAuthToken(AuthToken $authToken): User
{
$this->authTokens->add($authToken);
return $this;
}
/**
* #param AuthToken $authToken
* #return User
*/
public function removeAuthToken(AuthToken $authToken): User
{
$this->authTokens->removeElement($authToken);
return $this;
}
// ...
/**
* Returns the password used to authenticate the user.
*
* This should be the encoded password. On authentication, a plain-text
* password will be salted, encoded, and then compared to this value.
*
* #return string The password
*/
public function getPassword()
{
return $this->getSaltedPasswordHash();
}
/**
* Returns the salt that was originally used to encode the password.
*
* This can return null if the password was not encoded using a salt.
*
* #return string|null The salt
*/
public function getSalt()
{
// TODO: Implement getSalt() method.
}
/**
* Returns the username used to authenticate the user.
*
* #return string The username
*/
public function getUsername()
{
return $this->getEmail();
}
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
$this->setSaltedPasswordHash('');
}
/**
* #return mixed
*/
public function getRole()
{
return $this->role;
}
/**
* #param mixed $role
* #return User
*/
public function setRole($role): User
{
$this->role = $role;
return $this;
}
/**
* #return string
*/
public function __toString()
{
return "User " . $this->email;
}
/**
* The equality comparison should neither be done by referential equality
* nor by comparing identities (i.e. getId() === getId()).
*
* However, you do not need to compare every attribute, but only those that
* are relevant for assessing whether re-authentication is required.
*
* Also implementation should consider that $user instance may implement
* the extended user interface `AdvancedUserInterface`.
*
* https://stackoverflow.com/a/39884792/6144818
*
* #param UserInterface $user
* #return bool
*/
public function isEqualTo(UserInterface $user)
{
return (
$this->getUsername() == $user->getUsername()
) && (
$this->getRoles() == $user->getRoles()
);
}
// ...
}
Entitiy/AuthToken.php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\AuthTokenRepository")
*/
class AuthToken
{
/**
* #ORM\Id()
* #ORM\Column(type="decimal", precision=32, scale=0, options={"unsigned": true})
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="authTokens")
*/
private $user;
/**
* #ORM\Column(type="datetime")
*/
private $added;
/**
* #ORM\Column(type="datetime", nullable=true)
*/
private $lastSeen;
/**
* #ORM\Column(type="string", length=12, nullable=true)
*/
private $apiVersion;
/**
* #return string
*/
public function getId(): string
{
return $this->id;
}
/**
* #return string
*/
public function getHexId(): string
{
return $this->dec2hex($this->id);
}
/**
* #param mixed $id
*/
public function setId($id): void
{
$this->id = $id;
}
/**
* #param mixed $id
* #throws \Exception
*/
public function generateId(): void
{
$length = 32;
$str = "";
$characters = range('0', '9');
$max = count($characters) - 1;
for ($i = 0; $i < $length; $i++) {
$rand = random_int(0, $max);
$str .= $characters[$rand];
}
$this->id = $str;
}
/**
* #return mixed
*/
public function getUser()
{
return $this->user;
}
/**
* #param mixed $user
*/
public function setUser($user): void
{
$this->user = $user;
}
/**
* #return mixed
*/
public function getAdded()
{
return $this->added;
}
/**
* #param mixed $added
*/
public function setAdded($added): void
{
$this->added = $added;
}
/**
* #return mixed
*/
public function getLastSeen()
{
return $this->lastSeen;
}
/**
* #param mixed $lastSeen
*/
public function setLastSeen($lastSeen): void
{
$this->lastSeen = $lastSeen;
}
public function getApiVersion(): ?string
{
return $this->apiVersion;
}
public function setApiVersion(string $apiVersion): self
{
$this->apiVersion = $apiVersion;
return $this;
}
public static function dec2hex(string $dec): string
{
$hex = '';
do {
$last = bcmod($dec, 16);
$hex = dechex($last) . $hex;
$dec = bcdiv(bcsub($dec, $last), 16);
} while ($dec > 0);
return $hex;
}
public static function hex2dec($hex)
{
$dec = '0';
$len = strlen($hex);
for ($i = 1; $i <= $len; $i++)
$dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
return $dec;
}
/**
* #return string
*/
public function __toString()
{
return "AuthToken " . $this->id . " (" . $this->user . ")";
}
}
This function of your Entity/User.php is the reason of your behavior:
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
$this->setSaltedPasswordHash('');
}
When authentication comes in play on Symfony, after the authentication, the AuthenticationProviderManager would call that eraseCredentials function, so it doesn't leak sensible information, or even worse, the sensible information does not end up in your session.
Just try to comment the setter in that function and you should have what you expect.

Symfony 2 - Entity is not updated

I am working with form aimed at uploading the file and updating the database in Symfony2. I want to manually set value of book_id field and not to allow user to change it in the form. Thus in my controller before using doctrine to persist document I am calling:
$documents->setBookId('1');
Unluckilly I get error which indicates that the doctrine does not recognise the above hard coded value input.
An exception occurred while executing 'INSERT INTO Documents (book_id, marker, document_date, link, notes) VALUES (?, ?, ?, ?, ?)' with params [null, "fdd", "2015-04-04", null, "test"]:
To my mind this may be connected with the fact that book_id field is related to Books. Therefore probably I should use setBook function instead. Could you please advice how to do this properly?
My controler file looks like this:
/**
* This code is aimed at checking if the book is chosen and therefore whether any further works may be carried out
*/
$session = new Session();
if(!$session->get("App_Books_Chosen_Lp")) return new RedirectResponse($this->generateUrl('app_listbooks'));
// Authorization goes here
$documents = new Documents();
$form = $this->createForm(new DocumentsType(), $documents);
$form->add('save', 'submit', array('label' => 'Dodaj dokument'));
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$documents->upload();
$documents->setBookId('1');
$em->persist($documents);
$em->flush();
}
return $this->render('AppBundle:Documents:adddocuments.html.twig', array('form' => $form->createView()));
Document class:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
* #ORM\Table(name="Documents")
* #ORM\HasLifecycleCallbacks
*/
class Documents
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="Books", inversedBy="documents")
* #ORM\JoinColumn(name="book_id", referencedColumnName="id")
*/
protected $book;
/**
* #ORM\Column(type="integer")
*/
protected $book_id;
/**
* #ORM\Column(type="string", length=220)
*/
protected $marker;
/**
* #ORM\Column(type="date", length=220)
*/
protected $document_date;
/**
* #ORM\Column(type="string", length=220)
* #Assert\File(maxSize="6000000")
*/
protected $link;
/**
* #ORM\Column(type="text")
*/
protected $notes;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set book_id
*
* #param integer $bookId
* #return Documents
*/
public function setBookId($bookId)
{
$this->book_id = $bookId;
return $this;
}
/**
* Get book_id
*
* #return integer
*/
public function getBookId()
{
return $this->book_id;
}
/**
* Set marker
*
* #param string $marker
* #return Documents
*/
public function setMarker($marker)
{
$this->marker = $marker;
return $this;
}
/**
* Get marker
*
* #return string
*/
public function getMarker()
{
return $this->marker;
}
/**
* Set document_date
*
* #param \DateTime $documentDate
* #return Documents
*/
public function setDocumentDate($documentDate)
{
$this->document_date = $documentDate;
return $this;
}
/**
* Get document_date
*
* #return \DateTime
*/
public function getDocumentDate()
{
return $this->document_date;
}
/**
* Set link
*
* #param string $link
* #return Documents
*/
public function setLink($link)
{
$this->link = $link;
return $this;
}
/**
* Get link
*
* #return string
*/
public function getLink()
{
return $this->link;
}
/**
* Set notes
*
* #param string $notes
* #return Documents
*/
public function setNotes($notes)
{
$this->notes = $notes;
return $this;
}
/**
* Get notes
*
* #return string
*/
public function getNotes()
{
return $this->notes;
}
/**
* Set book
*
* #param \AppBundle\Entity\Books $book
* #return Documents
*/
public function setBook(\AppBundle\Entity\Books $book = null)
{
$this->book = $book;
return $this;
}
/**
* Get book
*
* #return \AppBundle\Entity\Books
*/
public function getBook()
{
return $this->book;
}
/*
* ### FILE UPLOAD PROCESS ###
*/
/**
* #Assert\File(maxSize="6000000")
*/
private $file;
/**
* Sets file.
*
* #param UploadedFile $file
*/
public function setFile(UploadedFile $file = null)
{
$this->file = $file;
}
/**
* Get file.
*
* #return UploadedFile
*/
public function getFile()
{
return $this->file;
}
public function getAbsolutePath()
{
return null === $this->path
? null
: $this->getUploadRootDir().'/'.$this->path;
}
public function getWebPath()
{
return null === $this->path
? null
: $this->getUploadDir().'/'.$this->path;
}
protected function getUploadRootDir()
{
// the absolute directory path where uploaded
// documents should be saved
return __DIR__.'/../../../../web/'.$this->getUploadDir();
}
protected function getUploadDir()
{
// get rid of the __DIR__ so it doesn't screw up
// when displaying uploaded doc/image in the view.
return 'uploads/documents';
}
public function upload()
{
// the file property can be empty if the field is not required
if (null === $this->getFile()) {
return;
}
// use the original file name here but you should
// sanitize it at least to avoid any security issues
// move takes the target directory and then the
// target filename to move to
$this->getFile()->move(
$this->getUploadRootDir(),
$this->getFile()->getClientOriginalName()
);
// set the path property to the filename where you've saved the file
$this->path = $this->getFile()->getClientOriginalName();
// clean up the file property as you won't need it anymore
$this->file = null;
}
}
Okay, first since you're using ManyToOne relation, you don't actually need another property refering to the book - book_id. You can remove that and leave book only.
Then in your controller you have to query the database for that Book and set the that object your Document.
You can do it like this:
$bookId = 1; // Following your example, let's say tou already know the book ID.
$book = $em->getReference('AppBundle:Books', $bookId);
// Check if we actually found a record and then set it to Documents
// Looking at your entity mapping, your reference to Book can not be null,
// but doing an extra check never hurts, since this is just an example.
if( $book ) {
$documents->setBook($book);
}
-Update-
If you want to directly insert the bookID, then what is the purpose of having ManyToOne reference in your entity? Eventually you're going to have to start using doctrine's relations and objects properly. Also, the cool thing about getReference method is that you are getting a reference to an entity, without having to load the entity from the database - you get the so called Proxy objects.
The method EntityManager#getReference($entityName, $identifier) lets you obtain a reference to an entity for which the identifier is known, without loading that entity from the database. This is useful, for example, as a performance enhancement, when you want to establish an association to an entity for which you have the identifier
You can read further about this here.

Doctrine2 after entity update still generate old database table

Im learning doctrine2. Problem is: I have just updated my entity class. Old version of entity consisted of $id, $name and $username fields. After this update below, I run command doctrine:generate:entities Acme, doctrine:update:schema and etc., but result is still old table with only 3 fields. It looks like old meta-data is saved somewhere. Can someone provide me with information what Im doing wrong ? And why I get old database table instead of new one ? And even how to solve my problem ?
namespace Acme\DemoBundle\Entity;
use Symfony\Component\Security\Core\Role\Role;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table(name="user")
* #ORM\Entity
*/
class User implements UserInterface, EquatableInterface
{
/**
* #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=50)
*/
private $username;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=50)
*/
private $password;
/**
* #var string
*
* #ORM\Column(name="roles", type="string", length=50)
*/
private $roles;
/**
* #var array
*
* #ORM\Column(name="apikey", type="array")
*/
private $apiKey;
/**
* #var string
*
* #ORM\Column(name="salt", type="string", length=10)
*/
private $salt;
function __construct($apiKey, $id, $password ,$roles , $salt, $username)
{
$this->apiKey = $apiKey;
$this->id = $id;
$this->password = $password;
$this->roles = $roles;
$this->salt = $salt;
$this->username = $username;
}
/**
* The equality comparison should neither be done by referential equality
* nor by comparing identities (i.e. getId() === getId()).
*
* However, you do not need to compare every attribute, but only those that
* are relevant for assessing whether re-authentication is required.
*
* Also implementation should consider that $user instance may implement
* the extended user interface `AdvancedUserInterface`.
*
* #param UserInterface $user
*
* #return bool
*/
public function isEqualTo(UserInterface $user)
{
if (!$user instanceof User) {
return false;
}
if ($this->password !== $user->getPassword()) {
return false;
}
if ($this->salt !== $user->getSalt()) {
return false;
}
if ($this->username !== $user->getUsername()) {
return false;
}
return true;
}
/**
* Returns the roles granted to the user.
*
* <code>
* public function getRoles()
* {
* return array('ROLE_USER');
* }
* </code>
*
* Alternatively, the roles might be stored on a ``roles`` property,
* and populated in any number of different ways when the user object
* is created.
*
* #return Role[] The user roles
*/
public function getRoles()
{
return $this->roles;
}
/**
* Returns the password used to authenticate the user.
*
* This should be the encoded password. On authentication, a plain-text
* password will be salted, encoded, and then compared to this value.
*
* #return string The password
*/
public function getPassword()
{
return $this->password;
}
/**
* Returns the salt that was originally used to encode the password.
*
* This can return null if the password was not encoded using a salt.
*
* #return string|null The salt
*/
public function getSalt()
{
return $this->salt;
}
/**
* Returns the username used to authenticate the user.
*
* #return string The username
*/
public function getUsername()
{
return $this->username;
}
/**
* #return string
*/
public function getApiKey()
{
return $this->apiKey;
}
/**
* #param string $apiKey
*/
public function setApiKey($apiKey)
{
$this->apiKey = $apiKey;
}
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* Removes sensitive data from the user.
*
* This is important if, at any given point, sensitive information like
* the plain-text password is stored on this object.
*/
public function eraseCredentials()
{
// TODO: Implement eraseCredentials() method.
}
}
If the old metadata is saved You have to clear cache
doctrine:cache:clear-metadata Clears all metadata cache for an entity manager
doctrine:cache:clear-query Clears all query cache for an entity manager
doctrine:cache:clear-result Clears result cache for an entity manager
I think you did not executed the update command correctly.
You'll need to force the changes:
php app/console doctrine:schema:update --force
Or dump the SQL and execute it manually:
php app/console doctrine:schema:update --dump-sql

OneToOne in Symfony 2

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

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).

Resources