Symfony 4 JWT : I can't get response from controller when accessing api - api

I'm working on this tutorial : Implementing JWT Authentication to your API Platform application
and I'm trying to get a protected access to the api action controller :
public function api()
{
return new Response(sprintf('Logged in as %s', $this->getUser()->getUsername()));
}
For reminder here is the security.yaml :
security:
encoders:
App\Entity\User:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
entity_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# main:
# anonymous: true
login:
pattern: ^/login
stateless: true
anonymous: true
json_login:
check_path: /login_check
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
register:
pattern: ^/register
stateless: true
anonymous: true
api:
pattern: ^/api
stateless: true
anonymous: false
provider: entity_provider
guard:
authenticators:
- lexik_jwt_authentication.jwt_token_authenticator
# activate different ways to authenticate
# http_basic: true
# https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
# form_login: true
# https://symfony.com/doc/current/security/form_login_setup.html
# 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: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api, roles: IS_AUTHENTICATED_FULLY }
And the routes.yaml :
register:
path: /register
controller: App\Controller\AuthController::register
methods: POST
api:
path: /api
controller: App\Controller\AuthController::api
login_check:
path: /login_check
methods: [POST]
I have the same code excepted the User entity :
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiSubresource;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
use ApiPlatform\Core\Bridge\Doctrine\Orm\Filter\SearchFilter;
/**
* #ORM\Entity(repositoryClass="App\Repository\UserRepository")
* #ApiResource(normalizationContext={"groups"={"user"}})
* #ApiFilter(SearchFilter::class, properties={"centres.id": "exact"})
*/
class User implements UserInterface
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $id;
/**
* #ORM\Column(type="string", length=50, unique=true)
* #Groups({"user"})
*/
private $username;
/**
* #ORM\Column(type="string", length=64)
* #Groups({"user"})
*/
private $password;
/**
* #ORM\Column(type="string", length=50, nullable=true)
* #Groups({"user"})
*/
private $prenom;
/**
* #ORM\Column(type="string", length=50, nullable=true)
* #Groups({"user"})
*/
private $nom;
/**
* #ORM\Column(type="string", length=80, unique=true)
* #Groups({"user"})
*/
private $email;
/**
* #ORM\Column(type="array")
* #Groups({"user"})
*/
private $roles = [];
/**
* #ORM\Column(type="datetime", nullable=true)
* #Groups({"user"})
*/
private $dateNaissance;
/**
* #ORM\Column(type="datetime")
* #Groups({"user"})
*/
private $dateEnregistrement;
/**
* #ORM\Column(type="datetime", nullable=true)
* #Groups({"user"})
*/
private $dateDernierePartie;
/**
* #ORM\Column(type="boolean")
* #Groups({"user"})
*/
private $actif;
/**
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $niveau;
/**
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $experience;
/**
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $nbVictimes;
/**
* #ORM\Column(type="integer")
* #Groups({"user"})
*/
private $nbMorts;
/**
* #ORM\Column(type="integer", nullable=true)
* #Groups({"user"})
*/
private $justesse;
/**
* #ORM\Column(type="integer", nullable=true)
* #Groups({"user"})
*/
private $nbParties;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Carte", mappedBy="client")
* #Groups({"user"})
* #var Collection
*/
private $cartes;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Equipe", inversedBy="joueurs")
* #ORM\JoinColumn(nullable=true)
* #Groups({"user"})
*/
private $equipe;
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Centre", inversedBy="clients")
* #ORM\JoinTable(name="users_centres")
* #var Collection
* #Groups({"user"})
*/
private $centres;
public function __construct()
{
$this->cartes = new ArrayCollection();
$this->centres = new ArrayCollection();
$this->actif = true;
$this->niveau = 1;
$this->experience = 0;
$this->nbVictimes = 0;
$this->nbMorts = 0;
$this->justesse = 0;
$this->nbParties = 0;
$this->dateEnregistrement = new \DateTime();
}
/**
* #param int|null $id
* #param string $username
* #param string $email
* #param string $password
* #param array $roles
* #param \DateTime|null $dateEnregistrement
* #return User
*/
static public function creer(
?int $id = null,
string $username,
string $email,
string $password,
array $roles,
?\DateTime $dateEnregistrement = null
)
{
$user = new self();
$user->id = $id;
$user->username = $username;
$user->email = $email;
$user->password = $password;
$user->roles = $roles;
$user->dateEnregistrement = $dateEnregistrement;
return $user;
}
public function addCarte(Carte $carte)
{
if ($this->cartes->contains($carte)) {
return;
}
$this->cartes->add($carte);
$carte->setClient($this);
}
public function addCentre(Centre $centre)
{
if ($this->centres->contains($centre)) {
return;
}
$this->centres->add($centre);
//$centre->inscrireJoueur($this);
}
public function ajouterNbVictimes(int $nbVictimes)
{
$this->nbVictimes += $nbVictimes;
}
public function ajouterJustesse(int $justesse)
{
$this->justesse += $justesse;
}
public function diminuerJustesse(int $justesse)
{
$this->justesse -= $justesse;
}
public function ajouterNbMorts(int $nbMorts)
{
$this->nbMorts += $nbMorts;
}
public function getId(): ?int
{
return $this->id;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function getRoles(): ?array
{
return $this->roles;
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
public function getDateNaissance(): ?\DateTimeInterface
{
return $this->dateNaissance;
}
public function setDateNaissance(\DateTimeInterface $dateNaissance): self
{
$this->dateNaissance = $dateNaissance;
return $this;
}
public function getDateEnregistrement(): ?\DateTimeInterface
{
return $this->dateEnregistrement;
}
public function setDateEnregistrement(\DateTimeInterface $dateEnregistrement): self
{
$this->dateEnregistrement = $dateEnregistrement;
return $this;
}
public function getDateDernierePartie(): ?\DateTimeInterface
{
return $this->dateDernierePartie;
}
public function setDateDernierePartie(?\DateTimeInterface $dateDernierePartie): self
{
$this->dateDernierePartie = $dateDernierePartie;
return $this;
}
public function getActif(): ?bool
{
return $this->actif;
}
public function setActif(bool $actif): self
{
$this->actif = $actif;
return $this;
}
public function getNiveau(): ?int
{
return $this->niveau;
}
public function setNiveau(int $niveau): self
{
$this->niveau = $niveau;
return $this;
}
public function getExperience(): ?int
{
return $this->experience;
}
public function setExperience(int $experience): self
{
$this->experience = $experience;
return $this;
}
public function getNbVictimes(): ?int
{
return $this->nbVictimes;
}
public function setNbVictimes(int $nbVictimes): self
{
$this->nbVictimes = $nbVictimes;
return $this;
}
public function getNbMorts(): ?int
{
return $this->nbMorts;
}
public function setNbMorts(int $nbMorts): self
{
$this->nbMorts = $nbMorts;
return $this;
}
public function getJustesse(): ?int
{
return $this->justesse;
}
public function setJustesse(int $justesse): self
{
$this->justesse = $justesse;
return $this;
}
/**
* #return mixed
*/
public function getNbParties()
{
return $this->nbParties;
}
/**
* #param mixed $nbParties
*/
public function setNbParties($nbParties): void
{
$this->nbParties = $nbParties;
}
/**
* #return mixed
*/
public function getCartes()
{
return $this->cartes;
}
/**
* #param mixed $cartes
*/
public function setCartes($cartes): void
{
$this->cartes = $cartes;
}
/**
* #return mixed
*/
public function getEquipe()
{
return $this->equipe;
}
/**
* #param mixed $equipe
*/
public function setEquipe($equipe): void
{
$this->equipe = $equipe;
}
/**
* #return mixed
*/
public function getCentres()
{
return $this->centres;
}
/**
* #param mixed $centre
*/
public function setCentres($centres): void
{
$this->centres = $centres;
}
/**
* 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 null;
}
/**
* Returns the username used to authenticate the user.
*
* #return string The username
*/
public function getUsername()
{
return $this->username;
}
/**
* 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()
{
}
}
I have also a Carte entity, a Centre entity, an Equipe entity and a Partie entity.
I'm using curl or Postamn to make requests :
I've made curl -H "Authorization: Bearer [TOKEN]" http://localhost:8000/api but the result is :
{"#context":"\/api\/contexts\/Entrypoint","#id":"\/api","#type":"Entrypoint","user":"\/api\/users","carte":"\/api\/cartes","equipe":"\/api\/equipes","centre":"\/api\/centres","partie":"\/api\/parties"}
Or with Postman :
{
"#context": "/api/contexts/Entrypoint",
"#id": "/api",
"#type": "Entrypoint",
"user": "/api/users",
"carte": "/api/cartes",
"equipe": "/api/equipes",
"centre": "/api/centres",
"partie": "/api/parties"
}
I don't get the message Logged in as [username] as expected.
How to get it ?
Thanks for help.

I had this same issue and to solve this I had to comment/remove this line from this file: config/routes/api_platform.yaml
api_platform:
resource: .
type: api_platform
# prefix: /api

Related

Symfony 4 roles management

I am currently working on a project in Symfony 4 a website for a library.
While configuring roles in the file security.yaml, I decommented this line :
access_control:
{ path: ^/admin, roles: ROLE_ADMIN }
{ path: ^/profil, roles: ROLE_USER }
And than I have this error :
image_error_roles
My User class :
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use App\Repository\UtilisateurRepository;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity(repositoryClass=UtilisateurRepository::class)
* #UniqueEntity(
* fields={"Email"},
* message="L'email que vous avez indiqué est déjà utilisé !"
* )
*/
class Utilisateur implements UserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $Nom;
/**
* #ORM\Column(type="string", length=255)
*/
private $Prenom;
/**
* #ORM\Column(type="string", length=255)
* #Assert\Email()
*/
private $Email;
/**
* #ORM\Column(type="string", length=255)
*/
private $Adresse;
/**
* #ORM\Column(type="string", length=255)
*/
private $Mdp;
/**
* #ORM\Column(type="string", length=255)
*/
private $Username;
/**
* #ORM\Column(type="string", length=255)
* #Assert\Length(min="8", minMessage="Votre mot de passe doit faire minimum 8 caractères")
* #Assert\EqualTo(propertyPath="Confirm_Password",message="Votre mot de passe doit être pareil")
*/
private $Password;
/*
* #Assert\EqualTo(propertyPath="Password",message="Votre mot de passe doit être pareil")
*/
public $Confirm_Password;
/**
* #ORM\ManyToOne(targetEntity=TypeUtilisateur::class, inversedBy="utilisateur")
* #ORM\JoinColumn(nullable=false)
*/
private $typeUtilisateur;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
public function getId(): ?int
{
return $this->id;
}
public function getNom(): ?string
{
return $this->Nom;
}
public function setNom(string $Nom): self
{
$this->Nom = $Nom;
return $this;
}
public function getPrenom(): ?string
{
return $this->Prenom;
}
public function setPrenom(string $Prenom): self
{
$this->Prenom = $Prenom;
return $this;
}
public function getEmail(): ?string
{
return $this->Email;
}
public function setEmail(string $Email): self
{
$this->Email = $Email;
return $this;
}
public function getAdresse(): ?string
{
return $this->Adresse;
}
public function setAdresse(string $Adresse): self
{
$this->Adresse = $Adresse;
return $this;
}
public function getMdp(): ?string
{
return $this->Mdp;
}
public function setMdp(string $Mdp): self
{
$this->Mdp = $Mdp;
return $this;
}
public function eraseCredentials(){}
public function getsalt(){}
public function getUsername(): ?string
{
return $this->Username;
}
public function setUsername(string $Username): self
{
$this->Username = $Username;
return $this;
}
public function getPassword(): ?string
{
return $this->Password;
}
public function setPassword(string $Password): self
{
$this->Password = $Password;
return $this;
}
public function getTypeUtilisateur(): ?TypeUtilisateur
{
return $this->typeUtilisateur;
}
public function setTypeUtilisateur(?TypeUtilisateur $typeUtilisateur): self
{
$this->typeUtilisateur = $typeUtilisateur;
return $this;
}
public function getRoles()
{
$roles = $this->roles;
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
}
File security.yaml :
security:
encoders:
App\Entity\Utilisateur:
algorithm: bcrypt
# https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
providers:
users_in_memory: { memory: null }
in_database:
entity:
class: App\Entity\Utilisateur
property: Email
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
anonymous: true
lazy: true
provider: in_database
form_login:
login_path: security_login
check_path: security_login
logout:
path: security_logout
target: home
# 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: ^/profil, roles: ROLE_USER }
# role_hierarchy:
ROLE_LIBRAIRE: ROLE_USER
ROLE_ADMIN: ROLE_LIBRAIRE
I don't know what is the problem, thanks for you help !
The error message says clearly there is a problem with your yaml.
access_control:
{ path: ^/admin, roles: ROLE_ADMIN }
{ path: ^/profil, roles: ROLE_USER }
should be:
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profil, roles: ROLE_USER }

Encode password easyadmin v3

I have my user that I can manage from my administration panel, I can change the password, but the problem is that in the database it is not encrypted. It is in clear in the database, Save you how I could do it so that it is not anymore? I give you my user entity as well as the crud user And I use the easyadmin v3 and symfony 5 bundle.
My entity User
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* #ORM\Entity(repositoryClass=UserRepository::class)
*/
class User implements UserInterface
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=180, unique=true)
*/
private $email;
/**
* #ORM\Column(type="json")
*/
private $roles = [];
/**
* #var string The hashed password
* #ORM\Column(type="string")
*/
private $password;
/**
* #ORM\Column(type="string", length=255)
*/
private $prenom;
/**
* #ORM\Column(type="string", length=255)
*/
private $nom;
/**
* #ORM\Column(type="string", length=255)
*/
private $telephone;
/**
* #ORM\Column(type="text", nullable=true)
*/
private $aPropos;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $facebook;
/**
* #ORM\OneToMany(targetEntity=Realisation::class, mappedBy="user", orphanRemoval=true)
*/
private $realisations;
public function __construct()
{
$this->realisations = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $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->email;
}
/**
* #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;
}
public function getPrenom(): ?string
{
return $this->prenom;
}
public function setPrenom(string $prenom): self
{
$this->prenom = $prenom;
return $this;
}
public function getNom(): ?string
{
return $this->nom;
}
public function setNom(string $nom): self
{
$this->nom = $nom;
return $this;
}
public function getTelephone(): ?string
{
return $this->telephone;
}
public function setTelephone(string $telephone): self
{
$this->telephone = $telephone;
return $this;
}
public function getAPropos(): ?string
{
return $this->aPropos;
}
public function setAPropos(?string $aPropos): self
{
$this->aPropos = $aPropos;
return $this;
}
public function getFacebook(): ?string
{
return $this->facebook;
}
public function setFacebook(?string $facebook): self
{
$this->facebook = $facebook;
return $this;
}
/**
* #return Collection|Realisation[]
*/
public function getRealisations(): Collection
{
return $this->realisations;
}
public function addRealisation(Realisation $realisation): self
{
if (!$this->realisations->contains($realisation)) {
$this->realisations[] = $realisation;
$realisation->setUser($this);
}
return $this;
}
public function removeRealisation(Realisation $realisation): self
{
if ($this->realisations->removeElement($realisation)) {
// set the owning side to null (unless already changed)
if ($realisation->getUser() === $this) {
$realisation->setUser(null);
}
}
return $this;
}
public function __toString()
{
return $this->nom;
}
/* public function __toString(){
return $this->nom;
}*/
}
<?php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureFields(string $pageName): iterable
{
return [
IntegerField::new('id','ID')->onlyOnIndex(),
TextField::new('email'),
TextField::new('password'),
TextField::new('nom'),
TextField::new('telephone'),
TextField::new('aPropos'),
TextField::new('facebook'),
];
}
}
This could be helpful ...
<?php
namespace App\Event\Subscriber;
use App\Entity\BackendUser;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent;
class EasyAdminHooksSubscriber implements EventSubscriberInterface {
/**
* #var UserPasswordEncoderInterface
*/
private $passwordEncoder;
/**
* #var ContainerInterface
*/
private $container;
/**
* EasyAdminSubscriber constructor.
*
* #param UserPasswordEncoderInterface $passwordEncoder
* #param ContainerInterface $container
*/
public function __construct(UserPasswordEncoderInterface $passwordEncoder, ContainerInterface $container) {
$this->passwordEncoder = $passwordEncoder;
$this->container = $container;
}
public static function getSubscribedEvents(): array {
return array(
BeforeEntityUpdatedEvent::class => array('preUpdateEntity')
);
}
/**
* #param BeforeEntityUpdatedEvent $event
*
* #noinspection PhpUnused
*/
public function preUpdateEntity(BeforeEntityUpdatedEvent $event) {
$entity = $event->getEntityInstance();
if($entity instanceof BackendUser) {
$this->preUpdateBackendUser($entity);
}
}
/**
* #param BackendUser $be_user
*/
private function preUpdateBackendUser(BackendUser &$be_user) {
$plain_password = $be_user->getPlainPassword();
if(!empty($plain_password)) {
$new_password = $this->passwordEncoder->encodePassword($be_user, $plain_password);
$be_user->setPassword($new_password);
$be_user->setPlainPassword();
}
}
}
Here's a solution I found while trying to create/edit users from my web app's admin dashboard (Symfony 5.3 and EasyAdmin v3). I found it in the EasyAdmin issue tracker over on Github.
You will need to add a plain password field to you User class and set the appropriate getter & setter methods.
/**
* #var string
*/
private $plainPassword;
/**
* #return string
*/
public function getPlainPassword(): string
{
return $this->plainPassword;
}
Add event listeners to listen to create/edit form submission events, extract the plain password from the submitted form data and then hash it.
/** #var UserPasswordHasherInterface */
private $hasher;
public function createEditFormBuilder(EntityDto $entityDto, KeyValueStore $keyValueStore, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createEditFormBuilder($entityDto, $keyValueStore, $context);
$this->addEncodePasswordEventListener($formBuilder);
return $formBuilder;
}
public function createNewFormBuilder(EntityDto $entityDto, KeyValueStore $formOptions, AdminContext $context): FormBuilderInterface
{
$formBuilder = parent::createNewFormBuilder($entityDto, $formOptions, $context);
$this->addEncodePasswordEventListener($formBuilder);
return $formBuilder;
}
/**
* #param FormBuilderInterface $formBuilder
*/
public function addEncodePasswordEventListener(FormBuilderInterface $formBuilder)
{
$formBuilder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event){
/** #var User $user */
$user = $event->getData();
if ($user->getPlainPassword()) {
$user->setPassword($this->hasher->hashPassword($user, $user->getPlainPassword()));
}
});
}
Then finally you need to render the appropriate form fields.
public function configureFields(string $pageName): iterable
{
return [
# other fields
Field::new('plainPassword', 'New Password')->onlyOnForms()
->setFormType(RepeatedType::class)
->setFormTypeOptions([
'type' => PasswordType::class,
'first_options' => ['label' => 'New password'],
'second_options' => ['label' => 'Repeat Password']
])->setRequired(true)
];
}
Hope someone finds this useful.
You can add plain password field in User entity:
private ?string $plainPassword= '';
public function getPlainPassword(): ?string
{
return $this->plainPassword;
}
public function setPlainPassword(?string $plainPassword): void
{
$this->plainPassword = $plainPassword;
}
Add password updater in User repo:
public function setNewPassword(PasswordAuthenticatedUserInterface $user, string $plainPassword): void
{
$hashedPassword = $this->hasher->hashPassword($user, $plainPassword);
$user->setPassword($hashedPassword);
$this->_em->persist($user);
$this->_em->flush();
}
And override update/persist methods In UserCrudController:
public function updateEntity(EntityManagerInterface $entityManager, $entityInstance): void
{
$this->updatePassword($entityInstance);
parent::updateEntity($entityManager, $entityInstance);
}
public function persistEntity(EntityManagerInterface $entityManager, $entityInstance): void
{
$this->updatePassword($entityInstance);
parent::persistEntity($entityManager, $entityInstance);
}
private function updatePassword(User $user): void
{
if ($user->getPlainPassword() == '') return;
$this->userRepository->setNewPassword($user, $user->getPlainPassword());
}
It's work in Symfony 5.4.2 / EasyAdmin 3.5.19

Symfony 3. Problems with join and the VichUploaderBundle

I load several pictures with the Vichuploaderbundle and the EntryImg Entity.
That is successful!
I have also an Entity named Entries.
Each Entry should be linked with one or more Images
I want to join both Entities in the Repository.
What is the error?
Here is my Entries Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
//use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\EntriesRepository")
* #ORM\Table(name="entries")
*/
class Entries
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
* #ORM\OneToMany(targetEntity="AppBundle\Entity\EntryImg", mappedBy="entries")
*/
private $id;
/**
* #ORM\Column(type="string")
*/
private $name;
/**
* #ORM\Column(type="text")
*/
private $description;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\User")
* #ORM\JoinColumn()
*/
private $user;
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}
public function getDescription()
{
return $this->description();
}
public function setDescription($description)
{
$this->description = $description;
return $this;
}
public function getUser()
{
return $this->user;
}
public function setUser(User $user)
{
$this->user = $user;
}
}
My Image Entity
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\HttpFoundation\File\File;
//use Symfony\Component\HttpFoundation\File\UploadedFile;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
* #ORM\Table(name="`entry_img`")
* #Vich\Uploadable
*/
class EntryImg
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #ORM\Column(type="string", length=255)
* #var string
*/
private $image;
/**
* #Assert\File(maxSize="2000k",mimeTypes={"image/png", "image/jpeg", "image/pjpeg"})
* #Vich\UploadableField(mapping="entry_images", fileNameProperty="image")
* #var File
*/
private $imageFile;
/**
* #ORM\Column(type="datetime")
* #var \DateTime
*/
private $updatedAt;
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Entries")
* #ORM\JoinColumn(name="entries_id",referencedColumnName="id",nullable=true)
*/
private $entry;
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
// VERY IMPORTANT:
// It is required that at least one field changes if you are using Doctrine,
// otherwise the event listeners won't be called and the file is lost
if ($image) {
// if 'updatedAt' is not defined in your entity, use another property
$this->updatedAt = new \DateTime('now');
}
}
public function getImageFile()
{
return $this->imageFile;
}
public function setImage($image)
{
$this->image = $image;
}
public function getImage()
{
return $this->image;
}
public function getId()
{
return $this->id;
}
public function getEntry(){
return $this->entry();
}
public function setEntry(Entries $entry){
$this->entry = $entry;
}
}
and my repository
<?php
namespace AppBundle\Repository;
use AppBundle\Entity\Entries;
use AppBundle\Entity\EntryImg;
use Doctrine\ORM\EntityRepository;
class EntriesRepository extends EntityRepository
{
public function findAllEntries($e_ids)
{
$query = $this->createQueryBuilder('e')
->leftJoin('e.id','entry_img');
$i = 0;
foreach($e_ids as $e_id){
if($i==0){
//var_dump($e_id);
$query->where('e.id = :entries_'.$i)
->setParameter('entries_'.$i,$e_id['entry']);
}else if($i>0){
$query->orWhere('e.id = :entries_'.$i)
->setParameter('entries_'.$i,$e_id['entry']);
}
$i++;
}
$result = $query->getQuery()->execute();
return $result;
}
In your class Entries, you have this line:
#ORM\OneToMany(targetEntity="AppBundle\Entity\EntryImg", mappedBy="entries")
But in your EntryImg class, there isn't any $entries property.
Also, if 1 Entries can have many EntryImg, you need to use arraycollection.
I think you should rewrite your Entries class like this:
use Doctrine\Common\Collections\ArrayCollection; //don't forget this line for the constructor
class Entries
{
/**
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\Column(type="integer")
//look here I remove the relation and put it on $entriesImg property
*/
private $id;
/**
* #ORM\Column(type="array")
* #ORM\OneToMany(targetEntity="AppBundle\Entity\EntryImg", mappedBy="entry") //look here I changed "entries" by "entry" which is the actual property in your EntryImg class. (you did not have any $entries property)
*/
private $entriesImg;
.. the rest of your properties
public function __construct()
{
$this->entriesImg= new ArrayCollection();
}
public function addEntryImg(\AppBundle\Entity\EntryImg $entryImg)
{
$this->entriesImg[] = $entryImg;
return $this;
}
public function removeEntryImg(\AppBundle\Entity\EntryImg $entryImg)
{
$this->entriesImg->removeElement($entryImg);
}
// also don't forget to implement classic getters and setters for the $entriesImg property
Don't forget to implement a toString method for both your entities, you will need it for Crud operation.
Then, later in your repository, you forgot to use the ->addSelect(); method like this
class EntriesRepository extends EntityRepository
{
public function findAllEntries()
{
$query = $this->createQueryBuilder('e')
->leftJoin('e.entriesImg','i');
->addSelect('i')
// whatever from your logic ....

Symfony API with FOSRestBundle : circular reference has been detected

I'm working on a symfony project to build a rest API, I have 4 entities related to each other like that :
I've installed FOSRestBundle to build just a GET web service, when i want to get a resource for example :
http://evaluation.dev/app_dev.php/api/families
I got this error :
message: "A circular reference has been detected (configured
limit:1).",
this is my controller :
<?php
namespace API\APIBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use FOS\RestBundle\Controller\Annotations as Rest;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;
class CartographyController extends Controller
{
/**
* #Rest\View()
* #Rest\Get("/posts")
* #param Request $request
* #return Response
*/
public function getPostsAction(Request $request)
{
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$posts = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Post')
->findAll();
return new Response($serializer->serialize($posts, 'json'));
}
/**
* #Rest\View()
* #Rest\Get("/employments")
* #param Request $request
* #return Response
*/
public function geEmploymentAction(Request $request)
{
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$employments = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Employment')
->findAll();
return new Response($serializer->serialize($employments, 'json'));
}
/**
* #Rest\View()
* #Rest\Get("/professions")
* #param Request $request
* #return Response
*/
public function geProfessionsAction(Request $request)
{
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$professions = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Profession')
->findAll();
return new Response($serializer->serialize($professions, 'json')); }
/**
* #Rest\View()
* #Rest\Get("/families")
* #param Request $request
* #return Response
*/
public function geFamiliesAction(Request $request)
{
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$serializer = new Serializer(array($normalizer), array($encoder));
$families = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Family')
->findAll();
return new Response($serializer->serialize($families, 'json'));
}
}
Family entity :
<?php
namespace EvalBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation as JMS;
/**
* Family
*
* #ORM\Table(name="family")
* #ORM\Entity(repositoryClass="EvalBundle\Repository\FamilyRepository")
*/
class Family
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* One Family has Many Professions.
* #ORM\OneToMany(targetEntity="Profession", mappedBy="family",orphanRemoval=true,cascade={"persist", "remove"},fetch="EAGER")
*/
protected $professions;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Family
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getProfessions()
{
return $this->professions;
}
/**
* #param mixed $professions
*/
public function setProfessions($professions)
{
$this->professions = $professions;
}
public function __toString() {
return $this->name;
}
}
Profession entity :
<?php
namespace EvalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Profession
*
* #ORM\Table(name="profession")
* #ORM\Entity(repositoryClass="EvalBundle\Repository\ProfessionRepository")
*/
class Profession
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* Many professions have One Family.
* #ORM\ManyToOne(targetEntity="Family", inversedBy="professions",cascade={"persist", "remove"})
*/
public $family;
/**
* One Profession has Many Employment.
* #ORM\OneToMany(targetEntity="Employment", mappedBy="profession",cascade={"persist", "remove"}, orphanRemoval=true,fetch="EAGER")
*/
private $employments;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Profession
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getEmployments()
{
return $this->employments;
}
/**
* #param mixed $employments
*/
public function setEmployments($employments)
{
$this->employments = $employments;
}
/**
* #return mixed
*/
public function getFamily()
{
return $this->family;
}
/**
* #param mixed $family
*/
public function setFamily($family)
{
$this->family = $family;
}
public function __toString() {
return $this->name;
}
}
Post entity
<?php
namespace EvalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Post
*
* #ORM\Table(name="post")
* #ORM\Entity(repositoryClass="EvalBundle\Repository\PostRepository")
*/
class Post
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* Many Posts have One Employment.
* #ORM\ManyToOne(targetEntity="Employment", inversedBy="posts", fetch="EAGER")
* #ORM\JoinColumn(name="employment_id", referencedColumnName="id",nullable=false)
*/
public $employment;
/**
* One Post has Many LevelRequired.
* #ORM\OneToMany(targetEntity="RequiredLevel", mappedBy="post")
*/
private $requiredLevels;
/**
* One Post has Many PostEvaluation.
* #ORM\OneToMany(targetEntity="PostEvaluation", mappedBy="post")
*/
private $postEvaluations;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Post
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getEmployment()
{
return $this->employment;
}
public function getProfession(){
return $this->employment->profession;
}
public function getFamily(){
return $this->employment->profession->family;
}
/**
* #param mixed $employment
*/
public function setEmployment($employment)
{
$this->employment = $employment;
}
public function __toString() {
return $this->name;
}
}
employment entity
<?php
namespace EvalBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Employment
*
* #ORM\Table(name="employment")
* #ORM\Entity(repositoryClass="EvalBundle\Repository\EmploymentRepository")
*/
class Employment
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=255, unique=true)
*/
private $name;
/**
* Many Employments have One Profession.
* #ORM\ManyToOne(targetEntity="Profession", inversedBy="employments",fetch="EAGER")
*/
public $profession;
/**
* One Employment has Many post.
* #ORM\OneToMany(targetEntity="Post", mappedBy="employment",cascade={"persist", "remove"}, orphanRemoval=true,cascade={"persist", "remove"})
*/
private $posts;
/**
* One Employment has One Grid.
* #ORM\OneToOne(targetEntity="Grid", mappedBy="employment")
*/
private $grid;
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* Set name
*
* #param string $name
*
* #return Employment
*/
public function setName($name)
{
$this->name = $name;
return $this;
}
/**
* Get name
*
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return mixed
*/
public function getProfession()
{
return $this->profession;
}
/**
* #param mixed $profession
*/
public function setProfession($profession)
{
$this->profession = $profession;
}
/**
* #return mixed
*/
public function getPosts()
{
return $this->posts;
}
/**
* #param mixed $posts
*/
public function setPosts($posts)
{
$this->posts = $posts;
}
/**
* #return mixed
*/
public function getGrid()
{
return $this->grid;
}
/**
* #param mixed $grid
*/
public function setGrid($grid)
{
$this->grid = $grid;
}
public function __toString() {
return $this->name;
}
}
any solution please ?
For you example, you can avoid the CircularReference like this
$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceLimit(1);
$normalizer->setCircularReferenceHandler(function ($object) {
return $object->getName();
});
$serializer = new Serializer(array($normalizer), array(new JsonEncoder()));
var_dump($serializer->serialize($org, 'json'));
But in your example, you don't use the FOSRestBundle for the view in your controller. The FOSRestController give you a handleView() and a view() method. Like this
class CartographyController extends FOSRestController
{
public function getPostsAction(Request $request)
{
$posts = $this->get('doctrine.orm.entity_manager')
->getRepository('EvalBundle:Post')
->findAll();
$view = $this->view($posts, 200);
return $this->handleView($view);
}
In this case, the serialiser is a service, this service is activated in config.yml :
In your app/config/config.yml
framework:
serializer: { enabled: true }
In order to avoid circularReference, you can do this.
In your app/config/services.yml
circular_reference_handler:
public: false
class: callback
factory: [AppBundle\Serializer\CircularHandlerFactory, getId]
serializer.normalizer.object:
class: Symfony\Component\Serializer\Normalizer\ObjectNormalizer
arguments: ["#serializer.mapping.class_metadata_factory", null, "#serializer.property_accessor"]
public: false
tags: [serializer.normalizer]
calls:
- method: setCircularReferenceLimit
arguments: [1]
- method: setCircularReferenceHandler
arguments: ["#circular_reference_handler"]
The factory can be like this:
namespace AppBundle\Serializer;
class CircularHandlerFactory
{
/**
* #return \Closure
*/
public static function getId()
{
return function ($object) {
return $object->getId();
};
}
}
Another idea is to use the GROUPS for the serializer. There is an annotation given by FosRestBundle :
#Rest\View(serializerGroups={"user"})
More information here:
https://symfony.com/doc/current/serializer.html#using-serialization-groups-annotations
Symfony2, FOSRestBundle. How to use group with JMSSerializerBundle?

Create login with groups (with roles) and users with revoke roles - Symfony2

I there,
I Want create a custom authentication that provides user roles, groups and roles groups. The login that i want to create is the next:
Users have groups and groups have roles;
Users have roles. This roles is for to revoke roles from groups.
ie:
Groups:
Group1: ROLE_WRITE, ROLE_READ
Group2: ROLE_CHECK, ROLE_NEW
Users:
Groups:
Group1
Group2
Roles
ROLE_CHECK
With above example the user can only use 3 roles ROLE_WRITE, ROLE_READ and ROLE_NEW
I made the following classes
Roles.php
namespace Test\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Role\RoleInterface;
/**
* Test\OverSkyBundle\Entity\Roles
*
* #ORM\Table(name="roles")
* #ORM\Entity()
*/
class Roles implements RoleInterface{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=70, unique=true)
*/
private $role;
public function __construct( $role )
{
$this->role = $role;
}
public function getId(){
return $this->id;
}
public function getRole(){
return $this->role;
}
public function setRole($role){
$this->role = $role;
}
public function __toString()
{
return (string) $this->role;
}
}
Groups.php
namespace Test\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Test\OverSkyBundle\Entity\Groups
*
* #ORM\Table(name="groups")
* #ORM\Entity()
*/
class Groups {
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=70, unique=true)
*/
private $groupname;
/**
* #ORM\ManyToMany(targetEntity="Roles")
*
*/
private $roles;
public function __construct()
{
$this->roles = new ArrayCollection();
}
public function __toString()
{
return $this->groupname;
}
public function getId(){
return $this->id;
}
public function getRoles()
{
return $this->roles->toArray();
}
public function setRoles($roles)
{
$this->roles = $roles;
}
}
Users.php
<?php
namespace Test\UserBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Test\OverSkyBundle\Entity\Users
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="Test\UserBundle\Entity\UsersRepository")
*/
class Users implements UserInterface, \Serializable{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", length=25, unique=true)
*/
private $username;
/**
* #ORM\ManyToMany(targetEntity="Roles")
*
*/
private $roles;
/**
* #ORM\ManyToMany(targetEntity="Groups")
*
*/
private $groups;
/**
* #ORM\Column(type="string", length=32)
*/
private $salt;
/**
* #ORM\Column(type="string", length=140)
*/
private $password;
/**
* #ORM\Column(type="string", length=140, unique=true)
*/
private $email;
/**
* #ORM\Column(name="is_active", type="boolean")
*/
private $isActive;
public function __toString()
{
return $this->name;
}
public function __construct() {
$this->roles = new ArrayCollection();
$this->groups = new ArrayCollection();
$this->isActive = true;
$this->salt = md5(uniqid(null, true));
}
public function getId()
{
return $this->id;
}
public function getRoles()
{
return $this->roles->toArray();
}
public function setRoles($roles)
{
$this->roles = $roles;
}
public function getGroups()
{
return $this->groups->toArray();
}
public function setGroups($groups)
{
$this->groups = $groups;
}
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getSalt()
{
return $this->salt;
}
public function setSalt($salt)
{
$this->salt = $salt;
return $this;
}
public function getPassword()
{
return $this->password;
}
public function setPassword($password)
{
$this->password = $password;
return $this;
}
public function getEmail()
{
return $this->email;
}
public function setEmail($email)
{
$this->email = $email;
return $this;
}
public function getIsActive()
{
return $this->isActive;
}
public function setIsActive($isActive)
{
$this->isActive = $isActive;
return $this;
}
public function eraseCredentials() {
}
public function serialize() {
return serialize(array(
$this->id,
));
}
public function unserialize($serialized) {
list (
$this->id,
) = unserialize($serialized);
}
}
index.html.twig
{% if is_granted('ROLE_ZAP') %}
dasdsa
{% endif %}
{% for groups in app.user.groups %}
<li> groups </li>
{% endfor %}
In twig file i can access to user roles but not the groups roles. How can i merge both and revoke with the roles present in users?
If i try to execute for groups in app.user.groups.roles,I receive an error that roles is not found.
When execute if is_granted, i recieve the user roles.
is_granted() calls the user's getRoles() method and checks wether the given argument role is inside the returned array of roles. ( simplified - the security provider calls getRoles and adds them to the security-context then is_granted checks the security-context to be more precise )
Now if you want to return the roles inherited from the user's groups you will have to merge those.
Though i don't know why you're trying to revoke the user's roles from those provided by the groups ...
( normally user's roles extend their group-provided roles - if you don't want certain roles, don't add the user to the group )
... you can do something like this:
Users.php
public function getRoles()
{
$groupRoles = array();
// add all roles provided by groups
foreach ($this->getGroups() as $group) {
foreach ($group->getRoles() as $role) {
$groupRoles[] = $role;
}
}
// - remove dublicates
// - revoke user's roles
// - return remaining roles
return array_unique(array_diff($groupRoles, $this->getRoles()));
}