I faced a problem when trying to implement the Custom Entity Provider for user authentication. I got the Mapping Exception that my entity is not a valid or mapped super class. Here is my Actual Error:
------------- Exception/Error Start --------------
1/1 MappingException: Class MunichInnovationGroup\PatentBundle\Entity\UmUsers is not a valid entity or mapped super class.
in C:\wamp\www\idp\vendor\doctrine\lib\Doctrine\ORM\Mapping\MappingException.php line 142
at MappingException::classIsNotAValidEntityOrMappedSuperClass() in C:\wamp\www\idp\vendor\doctrine\lib\Doctrine\ORM\Mapping\Driver\DriverChain.php line 80
at DriverChain->loadMetadataForClass() in C:\wamp\www\idp\vendor\doctrine\lib\Doctrine\ORM\Mapping\ClassMetadataFactory.php line 281
at ClassMetadataFactory->loadMetadata() in C:\wamp\www\idp\vendor\doctrine\lib\Doctrine\ORM\Mapping\ClassMetadataFactory.php line 170
at ClassMetadataFactory->getMetadataFor() in C:\wamp\www\idp\vendor\doctrine\lib\Doctrine\ORM\EntityManager.php line 257
at EntityManager->getClassMetadata() in C:\wamp\www\idp\vendor\doctrine\lib\Doctrine\ORM\EntityManager.php line 573
at EntityManager->getRepository() in C:\wamp\www\idp\vendor\symfony\src\Symfony\Bridge\Doctrine\Security\User\EntityUserProvider.php line 42
at EntityUserProvider->__construct() in C:\wamp\www\idp\app\cache\dev\appDevDebugProjectContainer.php line 2191
at appDevDebugProjectContainer->getSecurity_User_Provider_Concrete_UserDbService() in C:\wamp\www\idp\app\bootstrap.php.cache line 190
at Container->get() in C:\wamp\www\idp\app\cache\dev\appDevDebugProjectContainer.php line 2174
at appDevDebugProjectContainer->getSecurity_User_Provider_Concrete_ChainProviderService() in C:\wamp\www\idp\app\bootstrap.php.cache line 190
at Container->get() in C:\wamp\www\idp\app\cache\dev\appDevDebugProjectContainer.php line 2140
at appDevDebugProjectContainer->getSecurity_Authentication_ManagerService() in C:\wamp\www\idp\app\bootstrap.php.cache line 190
at Container->get() in C:\wamp\www\idp\app\cache\dev\appDevDebugProjectContainer.php line 1304
at appDevDebugProjectContainer->getSecurity_ContextService() in C:\wamp\www\idp\app\bootstrap.php.cache line 190
at Container->get() in C:\wamp\www\idp\app\cache\dev\appDevDebugProjectContainer.php line 1178
at appDevDebugProjectContainer->getProfilerService() in C:\wamp\www\idp\app\bootstrap.php.cache line 190
at Container->get() in C:\wamp\www\idp\app\cache\dev\appDevDebugProjectContainer.php line 1195
at appDevDebugProjectContainer->getProfilerListenerService() in C:\wamp\www\idp\app\bootstrap.php.cache line 190
at Container->get() in C:\wamp\www\idp\app\cache\dev\classes.php line 4779
at ContainerAwareEventDispatcher->lazyLoad() in C:\wamp\www\idp\app\cache\dev\classes.php line 4768
at ContainerAwareEventDispatcher->dispatch() in C:\wamp\www\idp\app\cache\dev\classes.php line 3916
at HttpKernel->handleException() in C:\wamp\www\idp\app\cache\dev\classes.php line 3855
at HttpKernel->handle() in C:\wamp\www\idp\app\cache\dev\classes.php line 4828
at HttpKernel->handle() in C:\wamp\www\idp\app\bootstrap.php.cache line 547
at Kernel->handle() in C:\wamp\www\idp\web\app_dev.php line 20
**------------- Exception/Error End --------------**
Lets describe my application
I have three databases DB-A, DB-B, DB-C and make three database connections in config.yml file which are default: DB-A, DB_B: DB-B, DB_C: DB-C
default connection is configured with default_entity_manager ORM, other entity managers DB_B uses connection DB_B and mapping with MIGPatBundle, DB_C uses connection DB_C and also mapping with MIGPatBundle.
My UmUsers entity resides in MIGPatBundle and we need to validate it in DB-B database.
I explain these elaborately because if i change the default_entity_manager ORM from default to DB_B then it works fine.
Here is my files
------------- config.yml Start --------------
**Doctrine Configuration**
doctrine:
dbal:
default_connection: default
connections:
default:
driver: "%db_driver_DB-A%"
host: "%db_host_DB-A%"
port: "%db_port_DB-A%"
dbname: "%db_name_DB-A%"
user: "%db_user_DB-A%"
password: "%db_password_DB-A%"
charset: UTF8
DB_B:
driver: "%db_driver_DB-B%"
host: "%db_host_DB-B%"
port: "%db_port_DB-B%"
dbname: "%db_name_DB-B%"
user: "%db_user_DB-B%"
password: "%db_password_DB-B%"
charset: UTF8
DB_C:
driver: "%db_driver_DB-C%"
host: "%db_host_DB-C%"
port: "%db_port_DB-C%"
dbname: "%db_name_DB-C%"
user: "%db_user_DB-C%"
password: "%db_password_DB-C%"
charset: UTF8
orm:
default_entity_manager: default
entity_managers:
default:
connection: default
mappings:
MIGBundle: ~
DB_B:
connection: DB_B
mappings:
MIGPatBundle: ~
DB_C:
connection: DB_C
mappings:
MIGPatBundle: ~
------------- config.yml End --------------
------------- security.yml Start ------------
security:
encoders:
MIG\PatBundle\Entity\UmUsers:
algorithm: sha512
encode-as-base64: true
iterations: 10
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
chain_provider:
providers: [user_db]
user_db:
entity: { class: MIG\PatBundle\Entity\UmUsers }
firewalls:
secured_area:
pattern: ^/
anonymous: ~
logout:
target: /
form_login:
provider: chain_provider
check_path: /security/login_check
default_target_path: /
login_path: /security/login
access_control:
- { path: ^/account, roles: ROLE_USER }
- { path: ^/management, roles: ROLE_ADMIN }
- { path: ^/portfolio, roles: ROLE_USER }
- { path: ^/portfoliogroup, roles: ROLE_USER }
- { path: ^/security, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/userpatent, roles: ROLE_USER }
- { path: ^/_internal, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
access_denied_url: /security/forbidden
------------- security.yml End --------------
------------- User Entity Start --------------
<?php
namespace MIG\PatBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Security\Core\User\UserInterface;
/**
*MIG\PatBundle\Entity\UmUsers
*
*#ORM\Entity(repositoryClass="MIG\PatBundle\Entity\UserRepository")
*#ORM\Table(name = "um_users")
*/
class UmUsers implements UserInterface
{
/**
#var string $id
#ORM\Column(name="id", type="string", length=36, nullable=false)
#ORM\Id
#ORM\GeneratedValue(strategy="UUID")
*/
private $id;
/**
* #ORM\Column(name="user_name", type="string", length=50, nullable=true)
*
* #var string $userName
*/
private $userName;
/**
* #var string $email
*
* #ORM\Column(name="email", type="string", length=100, nullable=false)
*/
private $email;
/**
* #ORM\Column(name="password", type="string", length=100, nullable=false)
*
* #Assert\MinLength(
* limit = 6,
* message = "The password must contain at least {{ limit }} characters."
* )
* #var string $password
*/
private $password;
/**
* #var string $salt
*
* #ORM\Column(name="salt", type="string", length=255, nullable=false)
*/
private $salt;
/**
* #var string $activationCode
*
* #ORM\Column(name="activation_code", type="string", length=255, nullable=false)
*/
private $activationCode;
/**
* #var boolean $activated
*
* #ORM\Column(name="activated", type="boolean", nullable=true)
*/
private $activated;
/**
* #var UmRoles
*
* #ORM\ManyToMany(targetEntity="UmRoles", inversedBy="user")
* #ORM\JoinTable(name="um_usersinroles",
* joinColumns={
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="role_id", referencedColumnName="id")
* }
* )
*/
private $role;
public function __construct()
{
$this->role = new \Doctrine\Common\Collections\ArrayCollection();
$this->createdAt = new \DateTime();
$this->lastLoginAt = new \DateTime();
$this->lastUpdatedDate = new \DateTime();
$this->lastLoginDate = new \DateTime();
$this->lastPasswordChangedDate = new \DateTime();
}
/**
* Get id
*
* #return string
*/
public function getId()
{
return $this->id;
}
/**
* Set userName
*
* #param string $userName
*/
public function setUserName($userName)
{
$this->userName = $userName;
}
/**
* Get userName
*
* #return string
*/
public function getUserName()
{
return $this->userName;
}
/**
* Set email
*
* #param string $email
*/
public function setEmail($email)
{
$this->email = $email;
}
/**
* Get email
*
* #return string
*/
public function getEmail()
{
return $this->email;
}
/**
* Set password
*
* #param string $password
*/
public function setPassword($password)
{
$this->password = $password;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set salt
*
* #param string $salt
*/
public function setSalt($salt)
{
$this->salt = $salt;
}
/**
* Get salt
*
* #return string
*/
public function getSalt()
{
return $this->salt;
}
/**
* Set activationCode
*
* #param string $activationCode
*/
public function setActivationCode($activationCode)
{
$this->activationCode = $activationCode;
}
/**
* Get activationCode
*
* #return string
*/
public function getActivationCode()
{
return $this->activationCode;
}
/**
* Set activated
*
* #param boolean $activated
*/
public function setActivated($activated)
{
$this->activated = $activated;
}
/**
* Get activated
*
* #return boolean
*/
public function getActivated()
{
return $this->activated;
}
/**
* Add role
*
* #param MIG\PatBundle\Entity\UmRoles $role
*/
public function addUmRoles(\MIG\PatBundle\Entity\UmRoles $role)
{
$this->role[] = $role;
}
/**
* Get role
*
* #return Doctrine\Common\Collections\Collection
*/
public function getRole()
{
return $this->role;
}
/**
* Gets an array of roles.
*
* #return array An array of Role objects
*/
public function getRoles()
{
return $this->getRole()->toArray();
}
/**
* Erases the user credentials.
*/
public function eraseCredentials()
{
}
/**
* Compares this user to another to determine if they are the same.
*
* #param UserInterface $user The user
* #return booleanean True if equal, false othwerwise.
*/
public function equals(UserInterface $user)
{
return md5($this->getUsername()) == md5($user->getUsername());
}
}
------------- User Entity End --------------
------------- UserRepository Entity Start--------------
namespace MIG\PatBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function loadUserByUsername($username)
{
$q = $this
->createQueryBuilder('u')
->where('u.userName = :username OR u.email = :email')
->setParameter('username', $username)
->setParameter('email', $username)
->getQuery()
;
try {
// The Query::getSingleResult() method throws an exception
// if there is no record matching the criteria.
$user = $q->getSingleResult();
} catch (NoResultException $e) {
throw new UsernameNotFoundException(sprintf('Unable to find an active admin MunichInnovationGroup\PatentBundle\Entity\UmUsers object identified by "%s".', $username), null, 0, $e);
}
return $user;
}
public function refreshUser(UserInterface $user)
{
$class = get_class($user);
if (!$this->supportsClass($class)) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $class));
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $this->getEntityName() === $class || is_subclass_of($class, $this->getEntityName());
}
}
------------- UserRepository Entity End--------------
Actually i have a deadline to complete this task. I will be very much glade to you if someone can give me the solution.
Regards,
Zaif
If you have a look at vendor/symfony/src/Symfony/Bundle/SecurityBundle/Resources/config/security.xml, you'll see this:
<service id="security.user.provider.entity" class="%security.user.provider.entity.class%" abstract="true" public="false" >
<argument type="service" id="security.user.entity_manager" />
</service>
<service id="security.user.entity_manager" alias="doctrine.orm.entity_manager" public="false" />
I think you need to override the security.user.entity_manager , but I am not sure how (maybe adding some lines in security.yml will do the trick), and since it is not public, I am not sure whether you can.
If this does not work, maybe you could report an issue about this.
Related
Versions I Used:
using (symfony 5.2.6) with (api-platform 2.7.0) alongside (php 8.0.3) and (postgres 13)
Description
used maker bundle to generate a User Entity and configured the security.yaml to encode passwords already tried auto and bcrypt or even argon2i non of them seems to work and hash the passwords
Possible Solution
as the symfony documentation describes it should automatically encode passwords if we are using security bundle and implementing UserInterface but this one seems like a bug cause i have tried many things nothing works . maybe using UserPasswordEncoderInterface but this one should not be used when User class is implementing UserInterface anyway i hope someone check this and tell me i'm wrong otherwise seems like a bug.
security.yaml:
security:
encoders:
App\Entity\User:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: app_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#firewalls-authentication
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
# or require ROLE_ADMIN or IS_AUTHENTICATED_FULLY for /admin*
# - { path: '^/admin', roles: [IS_AUTHENTICATED_FULLY, ROLE_ADMIN] }
# the 'path' value can be any valid regular expression
# (this one will match URLs like /api/post/7298 and /api/comment/528491)
# - { path: ^/api/(post|comment)/\d+$, roles: ROLE_USER }
User.php:
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use App\Entity\Trait\HasDateTime;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ApiResource
* #ORM\Entity(repositoryClass=UserRepository::class)
* #ORM\Table(name="`user`")
* #ORM\HasLifecycleCallbacks
*/
class User implements UserInterface
{
use HasDateTime;
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
#[Assert\NotBlank]
#[Assert\Email]
private $email;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
#[Assert\NotBlank]
private $username;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
#[Assert\NotBlank]
private $password;
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): string
{
return (string) $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
/**
* A visual identifier that represents this user.
*
* #see UserInterface
*/
public function getUsername(): string
{
return (string) $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
/**
* #see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* #see UserInterface
*/
public function getPassword(): string
{
return (string) $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* Returning a salt is only needed, if you are not using a modern
* hashing algorithm (e.g. bcrypt or sodium) in your security.yaml.
*
* #see UserInterface
*/
public function getSalt(): ?string
{
return null;
}
/**
* #see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
}
tried to save data in the database each time it saved passwords as plaintext.
also tried to remove the trait class thought it might conflict some how which seems strange(i know) (but i just gave it a try) it was not hashing passwords even after removing the trait. what am i doing wrong or missing here?
thanks in advance
here is the solution:
i had to create a class that implements DataPersister then in there we have to encode the password . here is the code:
<?php
namespace App\DataPersister;
use App\Entity\User;
use ApiPlatform\Core\DataPersister;
use Doctrine\ORM\EntityManagerInterface;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
class UserDataPersister implements DataPersisterInterface
{
public function __construct(private EntityManagerInterface $entityManager, private UserPasswordEncoderInterface $userPasswordEncoderInterface)
{
}
/**
* Is the data supported by the persister?
*/
public function supports($data): bool
{
return $data instanceof User;
}
/**
* #param User $data
* #return object|void Void will not be supported in API Platform 3, an object should always be returned
*/
public function persist($data)
{
if ($data->getPassword()) {
$data->setPassword(
$this->userPasswordEncoderInterface->encodePassword($data, $data->getPassword())
);
}
$this->entityManager->persist($data);
$this->entityManager->flush();
}
/**
* Removes the data.
*/
public function remove($data)
{
$this->entityManager->remove($data);
$this->entityManager->flush();
}
}
I have a User entity, with password encryption by bcrypt, in the database.
In fact, when a user is created, I generate a random password of 7 characters, and send him by mail. This password is encrypted using bcrypt and then in the database in the password attribute.
I would like that when my user connects from my login form, he can enter the password that I sent him by email to authenticate. But I do not understand how to do it. I can look at different sites, I do not see.
In addition, I think my code is a little mess ... My ultimate goal being to authenticate the user, so he has an open session, with a possibility to disconnect, and it is restricted to certain pages. But also to manage if the user is activated, and its expiration date.
I emphasize the fact that I am a beginner in symfony (I discovered it 2 weeks ago as part of my internship, and I knew very little PHP, so it's quite difficult for me)
I do not use the fosUserBundle bundle.
This is my code :
User.php :
<?php
namespace Site\PagesBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="Site\PagesBundle\Repository\UserRepository")
*/
class User implements UserInterface, \Serializable
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="username", type="string", length=255, nullable=true, unique=true)
*/
private $username;
/**
* #var string
*
* #ORM\Column(name="nom", type="string", length=255)
*/
private $nom;
/**
* #var string
*
* #ORM\Column(name="prenom", type="string", length=255)
*/
private $prenom;
/**
* #var string
*
* #ORM\Column(name="email", type="string", length=254, unique=true)
*/
private $email;
/**
* #Assert\Length(max=4096)
*/
private $plainPassword;
/**
* #var string
*
* #ORM\Column(name="password", type="string", length=64, nullable=true)
*/
private $password;
/**
* #var int
*
* #ORM\Column(name="nbTelechargementsAuto", type="integer", nullable=true)
*/
private $nbTelechargementsAuto;
/**
* #var bool
*
* #ORM\Column(name="isActif", type="boolean")
*/
private $isActif=0;
/**
* #ORM\Column(type="datetime", nullable=true)
*
* #var \DateTime
*/
private $createdAt;
/**
* #var bool
*
* #ORM\Column(name="isCreated", type="boolean")
*/
private $isCreated=0;
/**
* #ORM\Column(type="array")
*/
private $roles;
/**
* Set createdAt
*
* #param \DateTime $createdAt
*
* #return User
*/
public function setCreatedAt()
{
$this->createdAt = new \DateTimeImmutable();
return $this;
}
/**
* Get createdAt
*
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set nom
*
* #param string $nom
*
* #return User
*/
public function setNom($nom)
{
$this->nom = $nom;
return $this;
}
/**
* Get nom
*
* #return string
*/
public function getNom()
{
return $this->nom;
}
/**
* Set nom
*
* #param string $username
*
* #return User
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* Get nom
*
* #return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Set prenom
*
* #param string $prenom
*
* #return User
*/
public function setPrenom($prenom)
{
$this->prenom = $prenom;
return $this;
}
/**
* Get prenom
*
* #return string
*/
public function getPrenom()
{
return $this->prenom;
}
/**
* 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 password
*
* #param string $password
*
* #return User
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* Set nbTelechargementsAuto
*
* #param integer $nbTelechargementsAuto
*
* #return User
*/
public function setNbTelechargementsAuto($nbTelechargementsAuto)
{
$this->nbTelechargementsAuto = $nbTelechargementsAuto;
return $this;
}
/**
* Get nbTelechargementsAuto
*
* #return int
*/
public function getNbTelechargementsAuto()
{
return $this->nbTelechargementsAuto;
}
/**
* Set isActif
*
* #param boolean $isActif
*
* #return User
*/
public function setIsActif($isActif)
{
$this->isActif = $isActif;
return $this;
}
/**
* Get isActif
*
* #return bool
*/
public function getIsActif()
{
return $this->isActif;
}
/**
* Set isCreated
*
* #param boolean $isCreated
*
* #return User
*/
public function setIsCreated($isCreated)
{
$this->isCreated = $isCreated;
return $this;
}
/**
* Get isCreated
*
* #return bool
*/
public function getIsCreated()
{
return $this->isCreated;
}
public function __construct()
{
$this->roles = ['ROLE_USER'];
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($password)
{
$this->plainPassword = $password;
}
public function getSalt()
{
// you *may* need a real salt depending on your encoder
// see section on salt below
return null;
}
public function getRoles()
{
return $this->roles;
}
public function eraseCredentials()
{
}
/** #see \Serializable::serialize() */
public function serialize()
{
return serialize([
$this->id,
$this->username,
$this->password,
$this->email,
$this->nbTelechargementsAuto,
$this->nom,
$this->prenom,
// see section on salt below
// $this->salt,
]);
}
/** #see \Serializable::unserialize() */
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
$this->email,
$this->nbTelechargementsAuto,
$this->nom,
$this->prenom,
// see section on salt below
// $this->salt
) = unserialize($serialized, ['allowed_classes' => false]);
}
/**
* Activation du compte
*/
public function activerCompte($nbPackages, $nbHomonymes)
{
if($this->getIsCreated() == 0)
{
$unUsername = $this->getNom();
$unUsername .= ".";
$unUsername .= $this->getPrenom();
$unUsername = strtolower($unUsername);
if($nbHomonymes > 0)
{
$nbHomonymes++;
$unUsername .= $nbHomonymes;
}
$this->setUsername(strtolower($unUsername));
$this->setNbTelechargementsAuto($nbPackages);
$this->setCreatedAt();
$this->setIsCreated(true);
}
$password = $this->generatePassword();
$this->setPlainPassword($password);
$this->setIsActif(true);
return $this;
}
public function constructionUsername()
{
$unUsername = $this->getNom();
$unUsername .= ".";
$unUsername .=$this->getPrenom();
return $unUsername;
}
/**
* Désactivation du compte
*/
public function desactiverCompte()
{
$this->setIsActif(false);
$this->setCreatedAt();
return $this;
}
public function registration()
{
// Passage Nom 1ère lettre Majuscule le reste minuscule
$leNom = $this->getNom();
$leNom = strtolower($leNom);
$leNom = ucfirst($leNom);
// Passage Nom 1ère lettre Majuscule le reste minuscule
$lePrenom = $this->getPrenom();
$lePrenom = strtolower($lePrenom);
$lePrenom = ucfirst($lePrenom);
$this->setNom($leNom);
$this->setPrenom($lePrenom);
$this->setCreatedAt();
}
/**
* Génération d'un mot de passe
* $nb_caractere = nombre de caractère du mdp
* Pas de caractère qui se ressemble (L minuscule, 1 et i majuscule, Zéro et la lettre o.)
* Sécurité supplémentaire avec : caractères speciaux: - + = $ % ! # #
* Pas d'espace pour éviter les erreurs
*/
function generatePassword($nb_caractere = 7)
{
$mot_de_passe = "";
$chaine = "abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ023456789#!$?&";
$longeur_chaine = strlen($chaine);
for($i = 1; $i <= $nb_caractere; $i++)
{
$place_aleatoire = mt_rand(0,($longeur_chaine-1));
$mot_de_passe .= $chaine[$place_aleatoire];
}
return $mot_de_passe;
}
}
config.php:
imports:
- { resource: parameters.yml }
- { resource: security.yml }
- { resource: services.yml }
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
locale: en
framework:
# ...
templating:
engines: ['twig']
#esi: ~
#translator: { fallbacks: ['%locale%'] }
secret: '%secret%'
router:
resource: '%kernel.project_dir%/app/config/routing.yml'
strict_requirements: ~
form: ~
csrf_protection: ~
validation: { enable_annotations: true }
#serializer: { enable_annotations: true }
default_locale: '%locale%'
trusted_hosts: ~
session:
# https://symfony.com/doc/current/reference/configuration/framework.html#handler-id
handler_id: session.handler.native_file
save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
fragments: ~
http_method_override: true
assets: ~
php_errors:
log: true
# Twig Configuration
twig:
debug: '%kernel.debug%'
strict_variables: '%kernel.debug%'
form_themes: ['bootstrap_4_layout.html.twig']
# Doctrine Configuration
doctrine:
dbal:
driver: pdo_mysql
host: '%database_host%'
port: '%database_port%'
dbname: '%database_name%'
user: '%database_user%'
password: '%database_password%'
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: '%kernel.project_dir%/var/data/data.sqlite'
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
#path: '%database_path%'
orm:
auto_generate_proxy_classes: '%kernel.debug%'
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
# Swiftmailer Configuration
swiftmailer:
transport: '%mailer_transport%'
host: '%mailer_host%'
username: '%mailer_user%'
password: '%mailer_password%'
spool: { type: memory }
sensio_framework_extra:
router:
annotations: false
assetic:
debug: "%kernel.debug%"
use_controller: false
bundles: [ ]
#java: /usr/bin/java
java: C:\Program Files\Java\jdk1.8.0_65\bin\java.exe
filters:
cssrewrite: ~
cssembed:
jar: "%kernel.root_dir%/Resources/java/cssembed.jar"
yui_js:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
yui_css:
jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar"
lessphp:
file: "%kernel.root_dir%/../vendor/oyejorge/less.php/lessc.inc.php"
apply_to: ".less$"
assets:
jquery_js:
inputs:
- "%kernel.root_dir%/../vendor/components/jquery/jquery.min.js"
filters: [?yui_js]
output: js/jquery.min.js
bootstrap_css:
inputs:
- "%kernel.root_dir%/../vendor/twbs/bootstrap/less/bootstrap.less"
filters:
- lessphp
- cssrewrite
output: css/bootstrap.css
bootstrap_js:
inputs:
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/affix.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/alert.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/button.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/carousel.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/collapse.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/dropdown.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/modal.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/tooltip.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/popover.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/scrollspy.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/tab.js"
- "%kernel.root_dir%/../vendor/twbs/bootstrap/js/transition.js"
filters: [?yui_js]
output: js/bootstrap.js
fonts_glyphicons_eot:
inputs:
- "%kernel.root_dir%/../vendor/twbs/bootstrap/fonts/glyphicons-halflings-regular.eot"
output: "fonts/glyphicons-halflings-regular.eot"
fonts_glyphicons_svg:
inputs:
- "%kernel.root_dir%/../vendor/twbs/bootstrap/fonts/glyphicons-halflings-regular.svg"
output: "fonts/glyphicons-halflings-regular.svg"
fonts_glyphicons_ttf:
inputs:
- "%kernel.root_dir%/../vendor/twbs/bootstrap/fonts/glyphicons-halflings-regular.ttf"
output: "fonts/glyphicons-halflings-regular.ttf"
fonts_glyphicons_woff:
inputs:
- "%kernel.root_dir%/../vendor/twbs/bootstrap/fonts/glyphicons-halflings-regular.woff"
output: "fonts/glyphicons-halflings-regular.woff"
stof_doctrine_extensions:
orm:
default:
sluggable: true
vich_uploader:
db_driver: orm
twig: true
storage: file_system
mappings:
paquet:
uri_prefix: fichiers/packages
upload_destination: '%kernel.root_dir%/../web/fichiers/packages/'
inject_on_load: true
delete_on_update: true
delete_on_remove: true
notice:
uri_prefix: fichiers/notices
upload_destination: '%kernel.root_dir%/../web/fichiers/notices/'
inject_on_load: false
delete_on_update: true
delete_on_remove: true
fos_ck_editor:
configs:
my_config:
toolbar: [ ["Source", "-", "Save"], "/", ["Anchor"], "/", ["Maximize"] ]
uiColor: "#000000"
Security.yml :
# To get started with security, check out the documentation:
# https://symfony.com/doc/current/security.html
security:
encoders:
Site\PagesBundle\Entity\User: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: [ ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH ]
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
in_memory: {memory: ~}
in_database:
entity:
class: Site\PagesBundle\Entity\User
property: username
our_db_provider:
entity:
class: Site\PagesBundle\Entity\User
property: username
# if you're using multiple entity managers
# manager_name: customer
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
provider: in_database
form_login:
login_path: security_login
check_path: security_login
logout:
path: security_logout
target: informations
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
#http_basic: ~
# https://symfony.com/doc/current/security/form_login_setup.html
#form_login: ~
And a part fo the controller that I use to manage what is relative to the navigation of the user, including its authentication
DefaultController.php :
<?php
namespace Site\PagesBundle\Controller;
use Site\PagesBundle\Entity\User;
use Site\PagesBundle\Entity\Paquet;
use Site\PagesBundle\Entity\TypeUser;
use Site\PagesBundle\Entity\Information;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Vich\UploaderBundle\Handler\DownloadHandler;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
/**
* Default controller.
*
* #Route("accueil")
*/
class DefaultController extends Controller
{
/**
* Accueil
*
* #Route("/", name="connexion_index")
* #Method({"GET", "POST"})
*/
public function indexAction(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$em = $this->getDoctrine()->getManager(); //Récupération du manager
$listeInfos = $em->getRepository('PagesBundle:Information')->getInformationsZone("Zone 1"); //Récupération d'une liste d'informations
$user = new User(); //Initialisation de l'objet User
$form = $this->createForm('Site\PagesBundle\Form\ConnexionType', $user); //Formulaire de création
$form->handleRequest($request);
//Traitement si le formulaire est soumis ( Ajout du package dans la BDD )
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$valide = $this->getDoctrine()->getManager()->getRepository('PagesBundle:User')->authentifier($user->getUsername(),$password);
dump($valide);
dump($password);
if($valide == 1)
{
return $this->redirectToRoute('accueil');
}
else
{
return $this->render('#Pages/Default/connexion.html.twig',array(
'user' => $user,
'form' => $form->createView(),
'listeInfos' => $listeInfos,
));
}
// On ajoute un package, donc on offre un téléchargement supplémentaire aux utilisateurs concernés
$this->getDoctrine()->getManager()->getRepository('PagesBundle:User')->updateNbDDLAll("inc");
//return $this->redirectToRoute('paquets_index'); // Redirection page de gestion de packages
}
return $this->render('#Pages/Default/connexion.html.twig',array(
'user' => $user,
'form' => $form->createView(),
'listeInfos' => $listeInfos,
));
}
Thanks for your help !
EDIT:
My function :
/**
* Accueil
*
* #Route("/", name="connexion_index")
* #Method({"GET", "POST"})
*/
public function indexAction(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$em = $this->getDoctrine()->getManager(); //Récupération du manager
$listeInfos = $em->getRepository('PagesBundle:Information')->getInformationsZone("Zone 1"); //Récupération d'une liste d'informations
$user = new User(); //Initialisation de l'objet User
$form = $this->createForm('Site\PagesBundle\Form\ConnexionType', $user); //Formulaire de création
$form->handleRequest($request);
//Traitement si le formulaire est soumis ( Ajout du package dans la BDD )
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$valide = $this->getDoctrine()->getManager()->getRepository('PagesBundle:User')->authentifier($user->getUsername(),$password);
dump($valide);
dump($password);
if($valide == 1)
{
return $this->redirectToRoute('accueil');
}
else
{
return $this->render('#Pages/Default/connexion.html.twig',array(
'user' => $user,
'form' => $form->createView(),
'listeInfos' => $listeInfos,
));
}
// On ajoute un package, donc on offre un téléchargement supplémentaire aux utilisateurs concernés
$this->getDoctrine()->getManager()->getRepository('PagesBundle:User')->updateNbDDLAll("inc");
//return $this->redirectToRoute('paquets_index'); // Redirection page de gestion de packages
}
I retrieve the password entered by the user in the login form.
I encrypt it. Then I run a check in my UserRepository with the username and password encrypted.
If the function returns true, there is someone who has the username entered and the encrypted password entered.
My UserRepository function :
public function authentifier($username, $password)
{
$queryBuilder = $this->createQueryBuilder("u")
->select("count(u.id)")
->where("u.username = :username")
->andWhere("u.password = :password")
->setParameter("username",$username)
->setParameter("password",$password);
return $queryBuilder->getQuery()->getSingleScalarResult();
}
}
However, what is the cost in the security.yml?
To check if the posted password on your login form match with the one in database, you just need to bcrypt it and check if it's the same as the one in db.
You get your User from db
you encode the posted password
You check if the posted encoded password is the same as the one sotred in your User
But in your Controller code, it seems you create a new user (you create a new User instrad of getting one from db, and you persist it) ?
I'm new here and I'm from abroad, so sorry for my mistakes in English. Okey, so I have problem with login system in my first application in Symfony. Before I was writing some simple apps in Laravel. But here, I don't know what's wrong. I made new bundle and my login system stop working. I can put data in form, but when I pass it to authorization, website redirects me to login page and I'm not logged in. This is my code:
Controller:
<?php
// src/AppBundle/Controller/RegistrationController.php
namespace AppBundle\Controller;
use AppBundle\Form\UserType;
use AppBundle\Entity\User;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class RegistrationController extends Controller
{
/**
* #Route("/register", name="user_registration")
*/
public function registerAction(Request $request)
{
// 1) build the form
$user = new User();
$form = $this->createForm(UserType::class, $user);
// 2) handle the submit (will only happen on POST)
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// 3) Encode the password (you could also do this via Doctrine listener)
$password = $this->get('security.password_encoder')
->encodePassword($user, $user->getPlainPassword());
$user->setPassword($password);
// 4) save the User!
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
// ... do any other work - like sending them an email, etc
// maybe set a "flash" success message for the user
return $this->redirectToRoute('homepage');
}
return $this->render(
'registration/registration.html.twig',
array('form' => $form->createView())
);
}
/**
* #Route("/login", name="user_login")
*/
public function loginAction(Request $request) {
$authenticationUtils = $this->get('security.authentication_utils');
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('registration/login.html.twig', array(
'last_username' => $lastUsername,
'error' => $error,
));
}
/**
* #Route("/login_check", name="user_login_check")
*/
public function loginCheckAction() {
throw new \Exception('This should never be reached!');
}
/**
* #Route("/logout", name="user_logout")
*/
public function logoutAction()
{
throw new \Exception('This should never be reached!');
}
/**
* #Route("/login_failed", name="user_login_fail")
*/
public function loginError() {
return new Response('ERROR');
}
}
security.yml
# To get started with security, check out the documentation:
# http://symfony.com/doc/current/book/security.html
security:
encoders:
# Our user class and the algorithm we'll use to encode passwords
# http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
AppBundle\Entity\User: bcrypt
# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
our_db_provider:
entity:
class: AppBundle:User
property: username
firewalls:
default:
anonymous: ~
http_basic: ~
provider: our_db_provider
form_login:
login_path: user_login
check_path: user_login_check
failure_path: user_login_fail
logout:
path: user_logout
target: homepage
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: ~
access_control:
- { path: ^/admin, roles: ROLE_ADMIN}
# activate different ways to authenticate
# http_basic: ~
# http://symfony.com/doc/current/book/security.html#a-configuring-how-your-users-will-authenticate
# form_login: ~
# http://symfony.com/doc/current/cookbook/security/form_login_setup.html
User Entity
<?php
namespace AppBundle\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\AdvancedUserInterface;
/**
* User
*/
class User implements AdvancedUserInterface, \Serializable
{
/**
* #var int
*/
private $id;
/**
* #var string
*/
private $username;
/**
* #var string
*/
private $password;
/**
* #var string
*/
private $email;
/**
* #var bool
*/
private $isActive;
/**
* #var string
*/
private $plainPassword;
public function __construct() {
$this->setIsActive(TRUE);
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set username
*
* #param string $username
*
* #return User
*/
public function setUsername($username)
{
$this->username = $username;
return $this;
}
/**
* Get username
*
* #return string
*/
public function getUsername()
{
return $this->username;
}
/**
* Set password
*
* #param string $password
*
* #return User
*/
public function setPassword($password)
{
$this->password = $password;
return $this;
}
/**
* Get password
*
* #return string
*/
public function getPassword()
{
return $this->password;
}
/**
* 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 bool
*/
public function getIsActive()
{
return $this->isActive;
}
public function getRoles()
{
return array('ROLE_USER');
}
public function eraseCredentials()
{
}
public function serialize()
{
return serialize(array(
$this->id,
$this->username,
$this->password,
));
}
public function unserialize($serialized)
{
list (
$this->id,
$this->username,
$this->password,
) = unserialize($serialized);
}
public function getSalt()
{
// The bcrypt algorithm doesn't require a separate salt.
// You *may* need a real salt if you choose a different encoder.
return null;
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function setPlainPassword($password)
{
$this->plainPassword = $password;
}
public function isAccountNonExpired()
{
return true;
}
public function isAccountNonLocked()
{
return true;
}
public function isCredentialsNonExpired()
{
return true;
}
public function isEnabled()
{
return $this->isActive;
}
}
Thanks for your help.
I am trying to do my own authentication class.
Here is my User entity.
<?php
namespace AppBundle\Entity;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class User
{
/**
* #ORM\Column(type="int", length="11")
*/
protected $id;
/**
* #ORM\Column(type="string", length="25")
*/
protected $login;
/**
* #ORM\Column(type="string", length="25")
*/
protected $password;
/**
* #ORM\Column(type="string", length="25")
*/
protected $firstName;
/**
* #ORM\Column(type="string", length="25")
*/
protected $lastName;
/**
* #ORM\Column(type="string", length="25")
*/
protected $email;
public function getId()
{
return $this->id;
}
public function getLogin()
{
return $this->login;
}
public function getPassword()
{
return $this->password;
}
public function getFirstName()
{
return $this->firstName;
}
public function getLastName()
{
return $this->lastName;
}
public function getEmail()
{
return $this->email;
}
public function setLogin($login)
{
$this->login = $login;
}
public function setPassword($password)
{
$this->password = $password;
}
public function setFirstName($firstName)
{
$this->firstName = $firstName;
}
public function setLastName($lastName)
{
$this->lastName = $lastName;
}
public function setEmail($email)
{
$this->email = $email;
}
}
And the security settings (just like in docs)
security:
encoders:
AppBundle\Entity\User:
algorithm: sha512
encode-as-base64: true
iterations: 10
providers:
main:
entity: { class: AppBundle:User, property: login }
firewalls:
main:
pattern: /.*
form_login:
check_path: /account/check
login_path: /account/login
logout: true
security: true
anonymous: true
access_control:
- { path: /admin/.*, role: ROLE_ADMIN }
- { path: /.*, role: IS_AUTHENTICATED_ANONYMOUSLY }
I am getting next error - [Type Error] Attribute "length" of #ORM\Column declared on property AppBundle\Entity\User::$id expects a(n) integer, but got string.
Im not sure i can understand the error. From where it got string? I dont even have anything in users table.
I would like to ask you to help me to solve this.
Thanks
You're passing it a string by enclosing it in quotes. I'm suspect you think it's similar to HTML where you do need to enclose attributes in quotes - that is not the case here:
class User
{
/**
* #ORM\Column(type="int", length=11)
*/
protected $id;
//...
}
Apply this change everything you used length="11"
If I'm not wrong type should be integer and you don't need the length. So something like
/**
* #ORM\Column(type="integer")
*/
protected $id;
Did any one ever find an solution for this issue?
I'm having the same issue.
My config.yml:
# Doctrine Configuration
doctrine:
dbal:
driver: "%database_server%"
host: "%database_host%"
port: "%database_port%"
dbname: "%database_name%"
user: "%database_user%"
password: "%database_password%"
charset: UTF8
# if using pdo_sqlite as your database driver:
# 1. add the path in parameters.yml
# e.g. database_path: "%kernel.root_dir%/data/data.db3"
# 2. Uncomment database_path in parameters.yml.dist
# 3. Uncomment next line:
# path: "%database_path%"
orm:
auto_generate_proxy_classes: "%kernel.debug%"
naming_strategy: doctrine.orm.naming_strategy.underscore
auto_mapping: true
#Gedmo Package extension for Symfony and Doctrine
mappings:
gedmo_tree:
type: annotation
prefix: Gedmo\Tree\Entity
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Tree/Entity"
alias: GedmoTree
is_bundle: false
gedmo_sortable:
type: annotation
prefix: Gedmo\Sortable\Entity
dir: "%kernel.root_dir%/../vendor/gedmo/doctrine-extensions/lib/Gedmo/Sortable/Entity"
alias: GedmoTree
is_bundle: false
[...]
stof_doctrine_extensions:
default_locale: "%locale%"
translation_fallback: true
orm:
default:
timestampable: true
blameable: true
My doctrine_extension.yml is included in the config file:
services:
extension.listener:
class: Omega\HomeBundle\Library\Listener\DoctrineExtensionListener
calls:
- [ setContainer, [#service_container]]
tags:
# loggable hooks user username if one is in security context
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }
# Doctrine Extension listeners to handle behaviors
gedmo.listener.tree:
class: Gedmo\Tree\TreeListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
gedmo.listener.sortable:
class: Gedmo\Sortable\SortableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
gedmo.listener.timestampable:
class: Gedmo\Timestampable\TimestampableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
gedmo.listener.loggable:
class: Gedmo\Loggable\LoggableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
gedmo.listener.blameable:
class: Gedmo\Blameable\BlameableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
- [ setUserValue, [ #security.token_storage ] ]
I created myself an trait to handle the created, updated, updated_by and createdby fields:
namespace HomeBundle\Traits;
use Doctrine\ORM\Mapping as ORM;
use Omega\UserBundle\Entity\Users;
use Gedmo\Mapping\Annotation as Gedmo;
trait LogableTrait
{
/**
* #var Users
* #Gedmo\Blameable(on="create")
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\Users")
* #ORM\JoinColumn(name="log_created_by", referencedColumnName="id")
*/
protected $CreatedBy;
/**
* #var Users
* #Gedmo\Blameable(on="update")
* #ORM\ManyToOne(targetEntity="UserBundle\Entity\Users")
* #ORM\JoinColumn(name="log_updated_by", referencedColumnName="id")
*/
protected $UpdatedBy;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="created", type="datetime")
* #var \DateTime
*/
protected $created;
/**
* #Gedmo\Timestampable(on="create")
* #ORM\Column(name="updated", type="datetime")
* #var \DateTime
*/
protected $updated;
/**
* #return Users
*/
public function getCreatedBy ()
{
return $this->CreatedBy;
}
/**
* #param Users $CreatedBy
*
* #return $this
*/
public function setCreatedBy (Users $CreatedBy )
{
$this->CreatedBy = $CreatedBy;
return $this;
}
/**
* #return Users
*/
public function getUpdatedBy ()
{
return $this->UpdatedBy;
}
/**
* #param Users $UpdatedBy
*
* #return $this
*/
public function setUpdatedBy (Users $UpdatedBy )
{
$this->UpdatedBy = $UpdatedBy;
return $this;
}
}
But everytime that I use this Bundle I get:
The class 'Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage' was not found in the chain configured namespaces Gedmo\Tree\Entity, Gedmo\Sortable\Entity, JMS\JobQueueBundle\Entity, AccountingBundle\Entity, DocumentsBundle\Entity, EavBundle\Entity, HomeBundle\Entity, UserBundle\Entity, CustomerBundle\Entity, Jns\Bundle\XhprofBundle\Entity
I hope some body can help me.
for any one that Is having th same issue like me that the blamable feature is not working:
My solution was to implement the BlamableListener with an different approach:
namespace HomeBundle\Library;
use Doctrine\Common\NotifyPropertyChanged;
use Gedmo\Exception\InvalidArgumentException;
use Gedmo\Timestampable\TimestampableListener;
use Gedmo\Blameable\Mapping\Event\BlameableAdapter;
use Gedmo\Blameable\Mapping\Driver\Annotation;
/**
* The Blameable listener handles the update of
* dates on creation and update.
*
* #author Gediminas Morkevicius <gediminas.morkevicius#gmail.com>
* #license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
class BlameableListener extends TimestampableListener
{
protected $user;
/**
* Get the user value to set on a blameable field
*
* #param object $meta
* #param string $field
*
* #return mixed
*/
public function getUserValue($meta, $field)
{
if ($meta->hasAssociation($field)) {
if (null !== $this->user && ! is_object($this->user)) {
throw new InvalidArgumentException("Blame is reference, user must be an object");
}
$user = $this->user->getToken()->getUser();
if(!is_object($user))
{
return null;
}
return $user;
}
// ok so its not an association, then it is a string
if (is_object($this->user)) {
if (method_exists($this->user, 'getUsername')) {
return (string) $this->user->getUsername();
}
if (method_exists($this->user, '__toString')) {
return $this->user->__toString();
}
throw new InvalidArgumentException("Field expects string, user must be a string, or object should have method getUsername or __toString");
}
return $this->user;
}
/**
* Set a user value to return
*
* #param mixed $user
*/
public function setUserValue($user)
{
$this->user = $user;
}
/**
* {#inheritDoc}
*/
protected function getNamespace()
{
return __NAMESPACE__;
}
/**
* Updates a field
*
* #param object $object
* #param BlameableAdapter $ea
* #param $meta
* #param $field
*/
protected function updateField($object, $ea, $meta, $field)
{
$property = $meta->getReflectionProperty($field);
$oldValue = $property->getValue($object);
$newValue = $this->getUserValue($meta, $field);
//if blame is reference, persist object
if ($meta->hasAssociation($field) && $newValue) {
$ea->getObjectManager()->persist($newValue);
}
$property->setValue($object, $newValue);
if ($object instanceof NotifyPropertyChanged) {
$uow = $ea->getObjectManager()->getUnitOfWork();
$uow->propertyChanged($object, $field, $oldValue, $newValue);
}
}
}
adjust the service for the blamable:
gedmo.listener.blameable:
class: HomeBundle\Library\BlameableListener
tags:
- { name: doctrine.event_subscriber, connection: default }
calls:
- [ setAnnotationReader, [ #annotation_reader ] ]
- [ setUserValue, [ #security.token_storage ] ]
you need to copy the mapping library to the same location as the listener itself. Adjust the namespaces and it works. It seems that some structures changed in symfony 2.7 so the plugin no longer works out of the box.
If you want to update the updated_by field, you must specify the field so that when you update it, do so in updated_by. For example:
/**
* #var \DateTime $updated
*
* #Gedmo\Timestampable(on="update")
* #ORM\Column(type="datetime", nullable=true)
*/
protected $updated;
/**
* #var string $updatedBy
*
* #Gedmo\Blameable(on="update", field="updated")
* #ORM\Column(type="string", nullable=true)
*/
protected $updatedBy;
Pay attention to field="updated"
Just create DoctrineExtensionSubscriber
Manually tag the listeners
Gedmo\Loggable\LoggableListener:
tags:
- { name: doctrine_mongodb.odm.event_subscriber }
Create DoctrineExtensionSubscriber
<?php
namespace App\EventSubscriber;
use Gedmo\Blameable\BlameableListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class DoctrineExtensionSubscriber implements EventSubscriberInterface
{
/**
* #var BlameableListener
*/
private $blameableListener;
/**
* #var TokenStorageInterface
*/
private $tokenStorage;
/**
* #var TranslatableListener
*/
private $translatableListener;
/**
* #var LoggableListener
*/
private $loggableListener;
public function __construct(
BlameableListener $blameableListener,
TokenStorageInterface $tokenStorage,
TranslatableListener $translatableListener,
LoggableListener $loggableListener
) {
$this->blameableListener = $blameableListener;
$this->tokenStorage = $tokenStorage;
$this->translatableListener = $translatableListener;
$this->loggableListener = $loggableListener;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => 'onKernelRequest',
KernelEvents::FINISH_REQUEST => 'onLateKernelRequest'
];
}
public function onKernelRequest(): void
{
if ($this->tokenStorage !== null &&
$this->tokenStorage->getToken() !== null &&
$this->tokenStorage->getToken()->isAuthenticated() === true
) {
$this->blameableListener->setUserValue($this->tokenStorage->getToken()->getUser());
}
}
public function onLateKernelRequest(FinishRequestEvent $event): void
{
$this->translatableListener->setTranslatableLocale($event->getRequest()->getLocale());
}
}