Unlink role - BjyAuthorize Doctirne - doctrine-orm

BjyAuthorize modifies the User entity and provides an addRole() method. This accepts a role object and populates the user_role_linker_table
How is it possible to remove a role once it is added to a user?
The associations are set in User:
/**
* #var \Doctrine\Common\Collections\Collection
* #ORM\ManyToMany(targetEntity="Application\Entity\Role")
* #ORM\JoinTable(name="user_role_linker",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id")}
* )
*/
protected $roles;

After hours of struggle I came up with the following solution:
$userDetails = $em->getRepository('Application\Entity\UserDetails')->findOneBy(['id' => $data['user-details-id']]);
$user = $userDetails->getUser();
$roleRepo = $em->getRepository('Application\Entity\Role');
$roleResult = $roleRepo->findOneBy(['id' => $id]); //$id is the role to delete
$user->removeRole($roleResult);
$em->merge($user);
$em->flush();
In the User entity I added the method:
public function removeRole($role)
{
return $this->roles->removeElement($role);
}
Not sure if this is the approach that the authors of BjyAuthorize intended but it works for me...

Looks good to me. Just want to add that you should first check if the roles contains that role you want to delete.
Such as this:
public function removeRole($role)
{
if (!$this->roles->contains($role))
{
return;
}
$this->roles->removeElement($role);
}

Related

How to join column and target entity manually in manytoone relation?

I have two fields : account_type_id and account_id.
How can i manually map doctrine TargetEntity to join Company Entity if accountTypeId = 1 OR join User Entity if account_type_id = 2 ?
<?php
/** #Entity */
class Accounts
{
// 1= Company, 2 = User
private $accountType;
/**
* #ManyToOne(targetEntity="Companies")
*/
private $company;
/**
* #ManyToOne(targetEntity="Users")
*/
private $user;
//...
}
Unfortunately, joining different columns on the fly cannot be done automatically, but you can have both fields set as nullable and only set the correct one when persisting the Account entity.
This would be the annotation:
/**
* #ORM\ManyToOne(targetEntity="Users", inversedBy="users")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=true)
*/
private $user;
Keep in mind that nullable=true is the default anyway, I'm just being specific here.
If you want to go defensively about this, you can have an additional check in getter
/**
* #return User
* #throws \Exception
*/
public function getUser()
{
if ($this->accountType !== 2) {
throw new \Exception("Entity is not of type 'user'");
}
return $this->user;
}

Query many to many relation using slim framework and doctrine

I am using Slim Framework with Doctrine. I have three Tables
id | username | password | name
--------------------------------
1 | Lorel | ******** | Lorel
id | permission | description
-------------------------------
2 | READ_ACCESS | Lorel Ipsum
id | user_id | permission_id
-----------------------------
X | 1 | 2
Is there anyway using doctrine through which I can find out, suppose if user '1' has permission '2'.
I'm assuming you're looking to do Authorization. I've got a setup which does that, in Zend Framework 3 with Doctrine 2. The relations are the same, just not sure how to translate it to Slim Framework. But here goes nothing ;-)
User Entity has a relation to Roles:
/**
* #var Collection|ArrayCollection|Role[]
* #ORM\ManyToMany(targetEntity="User\Entity\Role", inversedBy="users", fetch="LAZY")
* #ORM\JoinTable(
* name="user_user_roles",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id")}
* )
*
*/
protected $roles;
Role Entity has Routes and the inverse side to User
/**
* #var Collection|ArrayCollection|Route[]
* #ORM\ManyToMany(targetEntity="User\Entity\Route", inversedBy="roles", fetch="EAGER")
* #ORM\JoinTable(
* name="user_role_routes",
* joinColumns={#ORM\JoinColumn(name="role_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="route_id", referencedColumnName="id")}
* )
*/
protected $routes;
/**
* #var Collection|ArrayCollection|User[]
* #ORM\ManyToMany(targetEntity="User\Entity\User", mappedBy="roles", fetch="LAZY")
*/
protected $users;
Route Entity just has the inverse to Role
/**
* #var Collection|ArrayCollection|Role[]
* #ORM\ManyToMany(targetEntity="User\Entity\Role", mappedBy="routes", fetch="LAZY")
*/
protected $roles;
Notice that it concerns 2 relationships:
User <-> Role
Role <-> Route
Make sure to initialize each Collection in the __construct, like so:
// Initialize only those within the Entity
public function __construct()
{
$this->users = new ArrayCollection();
}
Generate your getter method (setter not required!). Create Adder/Remover methods instead of a setter, like so (this is within Route Entity):
/**
* #param Collection|ArrayCollection|Role[] $roles
*
* #return Route
*/
public function addRoles(Collection $roles) : Route
{
foreach ($roles as $role) {
if ( ! $this->getRoles()->contains($role)) {
$this->getRoles()->add($role);
}
if ( ! $role->getRoutes()->contains($this)) {
$role->getRoutes()->add($this);
}
}
return $this;
}
/**
* #param Collection|ArrayCollection|Role[] $roles
*
* #return Route
*/
public function removeRoles(Collection $roles) : Route
{
foreach ($roles as $role) {
if ($this->getRoles()->contains($role)) {
$this->getRoles()->remove($role);
}
if ($role->getRoutes()->contains($this)) {
$role->getRoutes()->remove($this);
}
}
return $this;
}
So, there you go, that's the setup. I would advise you to include Gedmo Doctrine extensions and apply the #Gedmo\Tree(type="nested") to your Role Entity. Makes managing (nested/inherited) roles easy. See Managing Hierarchical Data in MySQL (and Gedmo Tree docs)
To next check if a User has access to a certain Route you need some form of AuthenticationService. Because I don't know Slim, make sure you fill this in with something from that framework. The logic is the same though. I use a service to be included/used on route access that checks if the User is known (Authenticated), and if not assigns a Guest Role, and then checks if the Route to be accessed is known to any of the assigned roles.
/**
* #param string $route
*
* #return bool
* #throws Exception
*/
public function isGranted(string $route) : bool
{
// Get assigned Role[] array or set Guest Role
if ($this->getAuthenticationService()->hasIdentity()) {
/** #var User $user */
$user = $this->getAuthenticationService()->getIdentity();
/** #var Collection|Role[] $roles */
$roles = $user->getRoles();
} else {
$roles = new ArrayCollection(
[
$this->getObjectManager()->getRepository(Role::class)->findOneBy(['name' => Role::NO_ACCOUNT_ROLE]),
]
);
}
foreach ($roles as $role) {
if ($this->checkRoutes($role, $route)) {
return true;
}
}
return false;
}
So, all of the above should get you more than going I'd say.
GL & HF

API Platform - PATCH and ArrayCollection

I'm using API-Platform and faced an issue with updating many-to-many with an empty value.
Here is the small example:
/**
* Many Organizations have Many Followers.
* #ORM\ManyToMany(targetEntity="App\Entity\User\User", inversedBy="organizations")
* #ORM\JoinTable(name="organizations_followers",
* joinColumns={#ORM\JoinColumn(name="organization_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id", unique=true)}
* )
*/
protected $followers;
/**
* #return Collection
*/
public function getFollowers(): Collection
{
return $this->followers;
}
/**
* #param array $followers
*/
public function setFollowers(array $followers): void
{
$this->followers = $followers;
}
/**
* Organization constructor.
*/
public function __construct()
{
$this->id = Uuid::uuid4();
$this->followers = new ArrayCollection();
}
So, when I'm trying to delete all followers (PATCH request with empty followers in the relationships field) I always get one undeleted record. What am I doing wrong? Any Ideas?

Doctrine2: Many-to-Many from Object X to Object X

i try to set up a "following" feature for users, so users can follow users.
I tried doing this by adding a Many-to-many Relation:
#ORM\ManyToMany(targetEntity="User",inversedBy="User",fetch="LAZY",cascade={"persist"})
#ORM\JoinTable=(name="user_followers",
joinColumns={
#ORM\JoinColumn(name="user_id", referencedColumnName="id")
},
inverseJoinColumns={#JoinColumn(name="follower_id",referencedColumnName="id")}
)
Now doctrine creates a table user_user with just one field user_id.
I really have no idea how to declare this.
Any thoughts?
I found the Solution myself.
You can actually use a self refferencing Many-to-Many Solution. From the Doctrine Website:
<?php
/** #Entity */
class User
{
// ...
/**
* #ManyToMany(targetEntity="User", mappedBy="myFriends")
*/
private $friendsWithMe;
/**
* #ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
private $myFriends;
public function __construct() {
$this->friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection();
$this->myFriends = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
http://docs.doctrine-project.org/projects/doctrine-orm/en/2.0.x/reference/association-mapping.html#many-to-many-self-referencing

Doctrine 2 - Insert new item in database

I'm trying to make something very simple.. but I do wrong, and I don't know what is the problem. Just I'm trying to insert new item to database with Doctrine 2:
$favouriteBook = new UserFavouriteBook;
$favouriteBook->user_id = 5;
$favouriteBook->book_id = 8;
$favouriteBook->created_at = new DateTime("now");
$this->_em->persist($favouriteBook);
$this->_em->flush();
As you can see.. is very simple, but that, give me next error:
Error: Message: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'user_id' cannot be null
Obviosly, if I make a "dump" before "persist" and "flush" of $favouriteBook, all looks be correct..
This is my "favouriteBook" entity:
/** #Column(type="integer")
* #Id
*/
private $user_id;
/** #Column(type="integer")
* #Id
*/
private $book_id;
/**
* #ManyToOne(targetEntity="Book", inversedBy="usersFavourite")
* #JoinColumn(name="book_id", referencedColumnName="id")
*/
private $book;
/**
* #ManyToOne(targetEntity="User", inversedBy="favouriteBooks")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
/** #Column(type="datetime") */
private $created_at;
public function __get($property) {
return $this->$property;
}
public function __set($property, $value) {
$this->$property = $value;
}
Anyone can image what is the problem? .. I don't know what else try.. Thanks
I think what beberlei is saying is that within your favouriteBook entity, you don't need to define the user_id and book_id as class properties, b/c the book and user properties you have set already recognize these as the relevant join columns. Also, your attempt to persist the favouriteBook entity failed because you need to set the book and user entity associations within the favouriteBook entity, not the foreign keys. So it would be:
$favouriteBook = new UserFavouriteBook;
$favouriteBook->book = $book;
$favouriteBook->user = $user;
$favouriteBook->created_at = new DateTime("now");
$this->_em->persist($favouriteBook);
$this->_em->flush();
You are mapping foreign keys and the associations. You have to modify the association not the foreign key field. Its bad-practice to map them both, you should remove $book_id and $user_id completly.