I try to make a social network with symfony2. I am not sure of how to modelize a user and his friends in the User entity.
I mean a friend is also a user so I have a User entity linked to another User entity.
How in the annotation of the User entity can I express that kind of relationship?
For the moment I have something like :
the User entity :
...
/**
* #ORM\ManyToMany(targetEntity="User")
*
*/
private $friends;
...
with $friends as an arraycollection. Is it correct?
That is a self-referencing ManyToMany relationship. It's actually the example used in the documentation.
Basically, you would so something like this:
<?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();
}
// ...
}
Note that it's set as a bidirectional relation. That is, you can get the list of friends and the list of people that are friends with a User.
Related
I have a problem with establishing a bidirectional One-To-One relationship between two entities.
I have an entity "Campaign" that CAN reference another entity "DonationData". A donation data that is created is always linked to a campaign. I want a bidirectional relationship because I want to be able to find the related campaign from a DonationData entity.
This is my code for the Campaign Entity :
<?php
/**
* #ORM\Entity
* ...
*/
class Campaign
{
...
/**
* #ORM\OneToOne(targetEntity="DonationData", mappedBy="campaign", cascade={"persist", "remove"})
*/
protected $donationData;
...
/**
* Set donationData
*
* #param DonationData $donationData
* #return Campaign
*/
public function setDonationData(DonationData $donationData = null)
{
$this->donationData = $donationData;
return $this;
}
...
?>
And the related code for my DonationData entity :
<?php
/**
* #ORM\Entity
* ...
*/
class DonationData
{
...
/**
* #ORM\OneToOne(targetEntity="Campaign")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="campaign_id", referencedColumnName="id")
* })
*/
protected $campaign;
...
/**
* Set campaign
* #param Campaign $campaign
*
* #return DonationData
*/
public function setCampaign($campaign)
{
$this->campaign = $campaign;
return $this;
}
...
?>
The piece of code where I'm adding my DonationData form in my Campaign Form (CampaignType.php)
<?php
...
->add('donationData', new DonationDataType(), array(
'label' => false
))
...
?>
And in the CampaignController.php side, when I'm handling the creation operations, I didn't change anything : I'm just persisting the related entity binded from the request, and then flushing the Entity Manager :
<?php
...
$em->persist($campaign);
$em->flush();
...
?>
My problem is when I want to persist a Campaign entity form that has an embedded DonationData form inside. I successfully persist both the Campaign and the DonationData entities, but there is only the donation_data_id reference in the Campaign entry that is persisted. When I look in database to the persisted DonationData, the campaign_id is always set to NULL.
Do you see any explanation for that?
Thank you.
In class DonationData, field $campaign you don't specify the inversedBy property, should be
#ORM\JoinColumn(name="campaign_id", referencedColumnName="id", inversedBy="donationData")
and did you set the cascade_validation default option in the parent form?
as in http://symfony.com/doc/current/reference/forms/types/form.html
I want to implement friends list of particular user in Symfony2.1 and Doctrine.
Lets say friends table:
User1 User2 Status //0-pending request,1-accepted
A B 0
A C 1
D A 1
E A 1
Now I want to get A's friends name in the list. For this SQL query can be implemented using UNION as read in many other answers. But I want to implement this in doctrine query builder.
One option is like query separately for two columns and combine the result and sort. But this takes more time to execute and get result. I want to get quick response as soon as possible. Is there any way to query it?
You don't need any additional effort, e.g. by using Doctrine Query Builder!
Simply design the entity class User to have a many-to-many self-reference with User, e.g.:
* #ORM\Table()
* #ORM\Entity()
*/
class User
{
....
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", unique=true, length=255)
*
*/
private $name;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="myFriends")
**/
private $friendsWithMe;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* #ORM\JoinTable(name="friends",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\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();
}
}
Then you can simply get the User entity and obtains all the friends as follows:
$user = $this->getDoctrine()
->getRepository('AcmeUserBundle:User')
->findOneById($anUserId);
$friends = $user->getMyFriends();
$names = array();
foreach($friends as $friend) $names[] = $friend->getName();
I'm a bit disconcerted by the way access control lists are implemented in Symfony2.
In Zend Framework (versions 1 & 2), a list of resources and a list of roles are defined and each role is assigned a subset of resources it's allowed to access. Resources and roles are therefore the main vocabulary of ACL implementation, which is not the case in Symfony2, where only roles rule.
In a legacy app database, I have tables defining a list of roles, a list of resources and a list of allowed resources for each role (many-to-many relationship). Each user is assigned a role (admin, super admin, editor, and such).
I need to make use of this database in a Symfony2 application.
My resources look like this : ARTICLE_EDIT, ARTICLE_WRITE, COMMENT_EDIT, etc.
My User entity in Symfony implements the Symfony\Component\Security\Core\User\UserInterface interface and therefore has a getRoles) method.
I intend to use this method to define the allowed resources, which means I use roles as resources (I mean that what's called resources in Zend Framework is called roles here).
Do you confirm that I should use this method ?
This means I don't care anymore about the role (admin, editor, ...) of each user, but only about its resources.
I would then use $this->get('security.context')->isGranted('ROLE_ARTICLE_WRITE') in my controllers.
Is this the right way to do it and wouldn't it be a circumvented way to use roles in Symfony?
To answer this question years later, it was pretty easy to solve.
The solution is to mix the notions of roles and resources.
Let's assume a role table, a resource table and and role_resource many to many relation are defined.
Users are stored in a user table.
Here are the corresponding Doctrine entities:
User:
use Symfony\Component\Security\Core\User\UserInterface;
class User implements UserInterface
{
/**
* #Id #Column(type="integer")
* #GeneratedValue
*/
private $id;
/**
* #ManyToOne(targetEntity="Role")
* #JoinColumn(name="role_id", referencedColumnName="id")
**/
private $role;
// ...
}
Role:
class Role
{
/**
* #Id #Column(type="integer")
* #GeneratedValue
*/
private $id;
/** #Column(type="string") */
private $name;
/**
* #ManyToMany(targetEntity="Resource")
* #JoinTable(name="role_resource",
* joinColumns={#JoinColumn(name="role_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="resource_id", referencedColumnName="id")}
* )
**/
private $resources;
// ...
}
Resource:
class Resource
{
/**
* #Id #Column(type="integer")
* #GeneratedValue
*/
private $id;
/** #Column(type="string") */
private $name;
// ...
}
So now the solution is to implement the getRoles of UserInterface this way:
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Role\Role;
class User implements UserInterface
{
// ...
/**
* #var Role[]
**/
private $roles;
/**
* {#inheritDoc}
*/
public function getRoles()
{
if (isset($this->roles)) {
return $this->roles;
}
$this->roles = array();
$userRole = $this->getRole();
$resources = $userRole->getResources();
foreach ($resources as $resource) {
$this->roles[] = new Role('ROLE_' . $resource);
}
return $this->roles;
}
}
This way, resources attributed to the current user can be checked this way (considering there is a resource whose name is ARTICLE_WRITE):
$this->get('security.context')->isGranted('ROLE_ARTICLE_WRITE')
I think this will answer your question.
http://symfony.com/doc/current/cookbook/security/acl.html
http://symfony.com/doc/current/cookbook/security/acl_advanced.html
$builder = new MaskBuilder();
$builder
->add('view')
->add('edit')
->add('delete')
->add('undelete');
$mask = $builder->get(); // int(29)
$identity = new UserSecurityIdentity('johannes', 'Acme\UserBundle\Entity\User');
$acl->insertObjectAce($identity, $mask);
So, i have the following structure of entities:
/**
* #ORM\Entity
*/
class Group
{
/**
* Many-To-Many, Unidirectional
*
* #var ArrayCollection $permissions
*
* #ORM\ManyToMany(targetEntity="Permission")
* #ORM\JoinTable(name="group_has_permission",
* joinColumns={#ORM\JoinColumn(name="group_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="permission_id", referencedColumnName="id")}
* )
*/
protected $permissions;
public function __construct()
{
$this->permissions = new ArrayCollection();
}
}
/**
* #ORM\Entity
*/
class Permission {}
It's just an example, but i'm confused. I need another entity probably called "group_has_permission" with two fields: group_id and permission_id, right? Or am i wrong?
You don't need to create a new entity.
Doctrine will create for you a group table, a permission table & a join table in order to link a group to multiple permissions. This is transparent for you.
I wanted to have a created_by field for my model, say Product, that is automatically updated and I am using FOSUserBundle and Doctrine2. What is the recommended way of inputting the User id into Product?
Can I do it in the Product model? I am not sure how to do so and any help would be wonderful. Thanks!
I want to do something like this in the model, but I don't know how to get the user id.
/**
* Set updatedBy
*
* #ORM\PrePersist
* #ORM\PreUpdate
* #param integer $updatedBy
*/
public function setUpdatedBy($updatedBy=null)
{
if (is_null($updatedBy)) {
$updatedBy = $user->id;
}
$this->updatedBy = $updatedBy;
}
To relate the user to the product you want to associate the two entities:
http://symfony.com/doc/current/book/doctrine.html#entity-relationships-associations
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="products")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* You may need to use the full namespace above instead of just User if the
* User entity is not in the same bundle e.g FOS\UserBundle\Entity\User
* the example is just a guess of the top of my head for the fos namespace though
*/
protected $user;
and for the automatic update field you may be after lifecyclecallbacks:
http://symfony.com/doc/current/book/doctrine.html#lifecycle-callbacks
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Product
{
/**
* #ORM\PreUpdate
*/
public function setCreatedValue()
{
$this->created = new \DateTime();
}
}
EDIT
This discussion talks about getting the container in the entity in which case you could then get the security.context and find the user id from that if you mean to associate the current user to the product they edited:
https://groups.google.com/forum/?fromgroups#!topic/symfony2/6scSB0Kgds0
//once you have the container you can get the session
$user= $this->container->get('security.context')->getToken()->getUser();
$updated_at = $user->getId();
Maybe that is what you are after, not sure it is a good idea to have the container in the entity though, could you not just set the user on the product in the update action in your product controller:
public function updateAction(){
//....
$user= $this->get('security.context')->getToken()->getUser();
$product->setUser($user)
}