Use permission/authorization checking scheme for object other than user? - symfony

I'm a bit new to this, and I haven't found a good way to make this work.
Lets say I have a user who is an employee of 1 company. This employee may decide to put his company's stocks for sales on the stock market.
Obviously we need to check for 2 permissions:
Does this employee have the right to put the stocks on stock market?
Are this company's stocks authorized to be put on the stock market?
The first check is simple, we simply use voter or ACL to do so. The second check is what I'm trying to figure out because so far in all the documents I have read the roles/permissions are only associated with user, not with arbitrary object (the company in this case)

Why not:
/**
* #ORM\Table(name="company")
* #ORM\Entity(")
*/
class Company
{
...
/**
* #ORM\Column(name="sellable", type="boolean", nullable=true)
*/
private $sellable;
...
}
Then something like:
if ($user->hasRightToSell() && $company->isSellable())?

Related

Symfony Doctrine - In a many to many relation why is the patch working in one direction but not the other

I'm on a project where I have a many to many relationship between team and agent. Because my teams can have multiple agents and my agents can have multiple teams.
I'm in a situation where I'm doing a patch so I can add multiple agents to a team (which is working) but I cannot do a working patch to add multiple teams to an agent.
Is it because of mapped by and inversed by?
UPDATE
In my TEAM entity here is the relation
/**
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Agent", inversedBy="teams")
*/
private $agents;
Here is the relation in my AGENT entity
/**
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Team", mappedBy="agents")
*/
private $teams;
In my team controller, when I want to give my team some new agents I'm using this piece of code and it works. I can see all the agents associated to the team in the database.
$team->setAgents($theAgents);
But when I want to do the opposite in my agent controller (assigning some teams to a new agent) the agent is created in the database but it's not assigned to any team in the association table. Even if I'm using this:
$agent->setTeams($theTeams);
Hence, is it maybe because it's not possible with Doctrine? Or perhaps I'm missing something.
This is the expected behavior.
For your ManyToMany relation you have the owning side:
/**
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Agent", inversedBy="teams")
*/
private $agents;
and the inverse side:
/**
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Team", mappedBy="agents")
*/
private $teams;
which are defined by the settings inversedBy and mappedBy respectively.
For a ManyToMany relation, you can chose which entity is the owning and which the inverse side, either of them can be defined.
Doctrine only checks the owning side for association changes. Check Working with associations, which means on your case, only $agents of Teams is checked for any changes to be persisted in the database.
On Agents, any changes in teams are ignored.
Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)
It is your responsibility to include these changes on the owning side also.
So in setTeams, do something like:
public function setTeams($teams) {
$this->teams = $teams;
foreach($teams as $team) {
$team->addAgent($this);
}
}
Note that in addAgent you have to check if the agent already exists in the collection.
In the end the solution I found is similar to Jannes'.
In my Agent entity I added this function:
/**
* #param mixed $team
*/
public function addTeam(Team $team)
{
$this->teams[] = $team;
$team->addAgent($this);
}
and in my Agent controller:
$teams = $request->get('teams');
foreach ($teams as $team){
$myTeam = $em->getRepository('MyBundle:Team')
->find($team["id"]);
$agent->addTeam($myTeam);
}
By doing so, I was I able to have a working post on both sides!
Thank you all again for your help!

Symfony 3 - Update Many-To-Many

I have been looking around for a clean solution on how to update (keep in sync) a many to many relationship?
I have the following scenario:
A Sprint Entity owns the Many To Many relationship towards the Ticket entity.
When editing a Ticket (or Sprint, but I am not there yet), I want to be able to select (checkboxes) the Sprints that this ticket belongs to.
Upon persistance (save), I want to update my join table tickets_sprint (which is just a join table on ticket_id, sprint_id).
Adding Sprints to the Ticket seems easy enough, but removing Sprints from the Ticket is not reflected at all.
Code
Ticket Entity contains this method for adding a Ticket to a Sprint:
public function setSprints($sprints) {
/**
* #var $sprint \AppBundle\Entity\Sprint
*/
foreach ($sprints as $sprint) {
$this->sprints[] = $sprint;
$sprint->addTicket($this);
}
}
I have read here that the only way to go would be to remove all relations and re-save them upon persistance.
Coming from the Laravel world, this hardly feels like a good idea :)
This is how it is done in Laravel:
/**
* #param \App\User $user
* #param \App\Http\Requests\StoreUserRequest $request
* #return \Illuminate\Http\RedirectResponse
* Update the specified resource in storage.
*/
public function update(User $user, StoreUserRequest $request)
{
$user->fill($request->input());
$user->employee_code = strtolower($user->employee_code);
$user->roles()->sync($request->role ? : []);
$user->save();
\Session::flash('flash_message_success', 'The user was successfully updated.');
return redirect()->route('frontend::users.show', [$user]);
}
All suggestions are welcome!
The EntityType that you may use to create a multiple selectbox doesn't have a by_reference option like CollectionType.
If your Ticket Entity use the "inversedBy" side, you don't need to add the reference in the other object. So you can symply do this :
public function setSprints($sprints) {
$this->sprints = $sprints;
}
Maybe this will be enough to add and remove your elements automatically (Sorry didn't try).
Otherwise you have to do it manually and you can create a new method to remove elements returns by the difference between your new ArrayCollection and the old one.

Symfony Sonata OneToMany, Sum or Total of a field is not showing in the Admin List

I have 2 entities, Client and Campaign.
Client Entity
/**
* #ORM\OneToMany(targetEntity="Campaign", mappedBy="client")
*/
protected $campaign;
+++++++++++++++++++++++++++++
Campaign Entity
/**
* #var integer
*
* #ORM\Column(name="numberOfBid", type="integer", nullable=true)
*/
protected $numberOfBid;
/**
* #ORM\ManyToOne(targetEntity="Clients", inversedBy="campaign")
* #ORM\JoinColumn(name="client_id", referencedColumnName="client_id")
*/
protected $client;
/* Let's say
Client A has Campaign A, numberOfBid = 1
Client A has Campaign B, numberOfBid = 5
Client A has Campaign C, numberOfBid = 3
Client A has Campaign D, numberOfBid = 4
Total numberofBid = 13
*/
Problem: How do I get the sum of all numberOfBid and show it as 1 column in the Client Admin List Board? On the method configureListFields, I tried different ways like using sonata_type_model, doctrine_orm_callback, query but still didn't work.
Client ID | Campaign TotalBid
A | 13
Hoping for your feedback.
Thanks in advance.
First you should rename your
protected $campaign;
in
protected $campaigns;
cause it's a collection. One client has many campaigns.
To your problem: You could implement a method on your client entity something like this
class Client{
...
public function getTotalNumberOfBids() {
$i = 0;
foreach ($this->getCampaigns() as $campaign) {
$i += $campaign->getNumberOfBid();
}
return $i;
}
...
}
and add to your list view by
protected function configureListFields(ListMapper $list)
{
$list
...
->add('totalNumberOfBids');
}
The "magic getter" will automatic invoke the getTotalNumberOfBids method.
Note: Dependent on the number of objects holding by the campaigns collection, the summation of the numberOfBids could be slow due to the use of the foreach loop.
There are some conventions in symfony that can be found here http://symfony.com/doc/2.8/contributing/code/conventions.html ... so if you want to access a property from your entity and you dont get it explicit by calling it with an leading keyword like "get" or "is", symfony (or maybe a underlying framework) automatic tries to find a method starting with "get" or "has" and so on ... this is comfortable because you dont have to write the get, set, has and so on before your properties to call the getting and setting methods.
The tricky thing is, that sonata admin bundle doesn't give you an error in the list view if a property isn't found. For example totalnumberOfBids will left your column empty without any error, cause you dont match the camel-case for the part "number". So you should be aware of naming/calling your properties correct or you will end up in a long time of headache ...

Combining multiple one-to-one relationships in Doctrine on inverse side of relationship

Hoping for some help thinking this through. Say I'm working with two entities here - User and Group. For the purposes of this example, say each Group has a senior moderator and a junior moderator, both of whom are Users and are mutually exclusive (a user can only be in one group, and cannot be both the senior and junior moderator). So in Group, I might have something like:
class Group
{
/**
*
* #OneToOne(targetEntity="User")
*/
private $seniorModerator;
/**
*
* #OneToOne(targetEntity="User")
*/
private $juniorModerator;
}
Now in User, I don't care whether the user is a senior moderator or junior moderator. I just want to be able to return what group a user is in. I know I'll have to add mappedBys in the Group properties, but I'm sort of stumped because I know I can't have two mappedBys assigned to the same thing ($group for example).
Would the best solution here be to just create two separate inverse properties in User (like $group1 and $group2) and then create a getGroup() getter method that just checks whether either of those properties is populated and returns the one that is?
Is there a less hacky method I'm not thinking of? Appreciate any advice, thanks in advance.
I have similar situation so I have created a third table with manyToOne to User, manyToOne to Group and one field that says if this user for this group is manager. So you can do the same add and two fields that states if user is senior or junior.
i think the best solution is to do a "one to one Unidirectional" association in both group and user entities, so the code for your group will be :
class Group
{
private idGroup;
/**
*
* #OneToOne(targetEntity="User")
* #JoinColumn(name="seniorModerator_id", referencedColumnName="idUser")
*/
private $seniorModerator;
/**
*
* #OneToOne(targetEntity="User")
* #JoinColumn(name="juniorModerator_id", referencedColumnName="idUser")
*/
private $juniorModerator;
}
For your User :
class User{
private idUser;
/**
*
* #oneToOne(targetEntity="Group")
* #JoinColumn(name="group_id", referencedColumnName="idGroup")
*/
private $group;
}
if you want more information on Association Mapping hir are the documentation : http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-one-unidirectional

Definitions of one-to-many, many-to-many etc

I'm building a database application using Doctrine2. I'm getting somewhat confused by the foreign key mappings. I'm wondering, have I got these examples correct:
One-To-One: An X has exactly one Y.
One-To-Many: An X can have multiple Ys.
Many-To-One: Multiple Xs can have the same Y.
Many-To-Many: Multiple Xs can have multiple Ys.
This is the specific situation that got me confused:
A User has exactly one HomeTown. Many users can belong to the same home town, so the link for the User is:
/**
* #ORM\ManyToOne(targetEntity="HomeTown", inversedBy="localUsers")
*/
$homeTown;
And, the corresponding HomeTown link is:
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="homeTown")
*/
$localUsers;
OR is it:
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="homeTown")
*/
$localUsers;
Some clarification would be much appreciated!
I've been looking at http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html
When you have OneToMany association, the inverted has to be ManyToOne. Saying that, your second option is correct.
TIP: Using Doctrine CLI command orm:validate-schema might also help to identify this issue.
The full path in Symfony app: php app/console doctrine:schema:validate
If you want one city to have many users the mapping should be as it follows
Entity City
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="homeTown")
*/
private $users;
...
public function __construct()
{
$this->users = new ArrayCollection();
}
...
Entity User
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="users")
* #ORm\JoinColumn(name="home_town", referencedColumnName="id")
*/
private $homeTown;
This mapping shows us that the City which is owning side has On-To-Many relation with User(target Entity). Respectively the User which is inversed side has to be annotated with ManyToOne relation because many users have same city. Of course, here the target entity should be City. Its important to specify which column is pointing the foreignkey with the referencedColumnName attribute in JoinColumn annotation. It shows what column of other table points this key. In this example in table User there is column named "home_town" which is a foreign key pointing to column id of table City
In ManyToOne relation you shod use JoinColumn annotation
This mapping is also Bidirectional.
You can make id Unidirectional as in the User Entity do not use "inversedBy=" attribute and remove OneToMany annotation with $user property from the City entity. It is something like when you have to know the city of a particular user, but you do not need to know all users for a specific city

Resources