Hello Silex (and Symfony) experts,
I need to implement a database authentification User/Role model via Doctrine /ORM.
This is my silex composer setup:
"require": {
"silex/web-profiler": "^2.0",
"monolog/monolog": "1.13.*",
"symfony/twig-bridge": "^3.2",
"symfony/monolog-bridge": "^3.2",
"symfony/console": "^3.2",
"symfony/yaml": "^3.2",
"symfony/security-bundle": "^3.2",
"doctrine/orm": "^2.5",
"dflydev/doctrine-orm-service-provider": "^2.0",
"symfony/form": "^3.2",
"symfony/validator": "^3.2",
"symfony/config": "^3.2",
"symfony/doctrine-bridge": "^3.2",
"doctrine/migrations": "^1.5"
},
Users can register. Registered users can login and logout. Non registered visitors have anonymous role.
The symfony profiler is working, so I can see the security status (authentification/authoriszation). I also track the apache logfile for php errors.
I started from here https://github.com/fredjuvaux/silex-orm-user-provider (User from db, roles as array) and tried to expand it to get user roles from database via doctrine many-to-many relation.
There are:
class MyUserController (different user actions like user,edit, register,... )
class MyUserManager implements UserProviderInterface (loadUserByUsername, ...)
class MyUserServiceProvider implements ServiceProviderInterface, ControllerProviderInterface, BootableProviderInterface (controller routing and template setting)
The ORM entities are:
User:
/**
* MyUser
*
* @Entity
* @Table(name="myuser")
*/
class MyUser implements UserInterface, \Serializable
{
....
/**
* @ManyToMany(targetEntity="MyRole", inversedBy="users")
*
*/
private $roles;
...
* Constructor.
*
* @param string $email
*/
public function __construct($email)
{
$this->email = $email;
$this->created = time();
$this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16, 36);
$this->roles = new ArrayCollection();
}
...
/**
*
* @return ArrayCollection list of the user's roles.
*/
public function getRoles()
{
$result = $this->roles->toArray(); // throws error for login:
// $result = $this->roles; // test // thhrows error : null object
dump($this->roles);
// $result = array("ROLE_USER", "ROLE_OTHER"); // static setting and
works for login
return $result;
}
...
}
Roles (implements Roleinterface)
/**
* MyRole
*
* @Entity
* @Table(name="myrole")
*/
class MyRole implements RoleInterface
{
/**
* @var string
* @Column(name="role", type="string", length=20, unique=true)
*/
private $role;
/**
* @ManyToMany(targetEntity="MyUser", mappedBy="roles")
*/
private $users;
...
/*
* methods for RoleInterface
* @return string|null A string representation of the role, or null
*/
public function getRole()
{
$result = $this->role;
return $result;
}
}
When a user registers, he gets for that session the ROLE_USER role, authentification and authorisation are ok and a user is created in the database.
Then I can assign new roles ("role_test1", "role_test2") in the controller for the new user, the many-to-many table myuser_myrole is filled (myuser_id myrole_id). When I change the roles, they are correctly updated by the entity manager.
When I access the user Entity from the userController to work on it, I can access the assigned roles:
// MyUserController.php
$user = $em->getRepository('MyEntities\MyUser')->find($id);
$roles= $user->getRoles()
$role_length = count($roles);
$role_list = array();
for ($i=0; $i <$role_length ; $i++)
{
array_push($role_list,$roles[$i]->getRole()); // MyRole::getRole() prints out something to screen.
}
printf("<br> role-list:"); dump($role_list);
Calling this controller prints out the assigned roles via MyRole::getRole(), so ORM access works here.
Now comes the strange:
I want to login the new user with the login form.
When I use
// MyUser::getRoles()
return $this->roles;
It throws:
Argument 4 passed to Symfony\\Component\\Security\\Core\\Authentication\\Token\\UsernamePasswordToken::__construct() must be of the type array, object given,
Ok, makes maybe sense because the $roles is an Doctrine ArrayCollection.
When I use
// MyUser::getRoles()
return $this->roles->toArray();
I can login with user password,but am not authenticated (yellow status). Dumping out the roles, I receive an empty array ArrayCollection.
roles:
ArrayCollection {#388 ▼
-elements: []
}
The UsernamePasswordToken has an empty role-array.
When I use
// MyUser::getRoles()
return array("ROLE_HELLO1", "ROLE_HELLO2"); // static role array with strings
I can login and am authenticated with these roles:
Roles
array:2 [▼
0 => "ROLE_HELLO1"
1 => "ROLE_HELLO2"
]
There are old docs about this (Managing Roles in the Database) for symfony 2 http://symfony.com/doc/2.0/cookbook/security/entity_provider.html, but it doesnt work in symfony3.
Here they use
//class User
public function getRoles()
{
return $this->groups->toArray();
}
//class Group extends Role (not RoleInterface, old?)
public function getRole()
{
return $this->role;
}
The actual symfony docs for user management do not show how to use roles stored in database.
In summary:
Login and user/role do not work as expected:
MyUser::getRoles()
does not receive the Roles from database via doctrine ORM.
has to return a string array of roles for login.
delivers the correct role association in another controller.
Questions:
(1) Is this a Silex specific issue?
(2) How to use it correctly or where is a good link/doc for a workaround?
(3) Does the method LoadUserByUsername() interfere with all this?
(4) Do I need a class MyUserRepository extends EntityRepository {} to do the query and get the Role List?
(5) Do I need to use the Role Hierarchy service?
(6) Are there special naming conventions(tablename or class name) for "user" and "role"?
I found many posts asking the same/similar but they do not help here.
Thank you for help, I am really stuck on that!
dirk