extend UserManager to search User by relation (symfony3) - doctrine-orm

I installed FOSUserbundle and HWI Oauth bundle.
My problem is: I want to access data from my user entity that is stored in a relation. I'd like to access the data from the fields social_network_slug and social_identifier from UserInSocialNetworks within the FOSUserProvider.
The idea was, that one user can have more that one social network logins. (1:n)- When I log in with my google/facebook etc login, I want to check the table user_in_social_networks if the Id with the social network already exists.
/*
* This is the User class, depending on fos_userBundle
*/
namespace AppBundle\Entity\Registration;
use Doctrine\Common\Collections\ArrayCollection as ArrayCollection;
use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="user")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* One User can have many social networks
* #ORM\OneToMany(targetEntity="UserInSocialNetworks", mappedBy="user", cascade={"remove"})
*/
private $socialnetworks; ....
the Entity Class to store all User's social media logins:
<?php
namespace AppBundle\Entity\Registration;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* UserInSocialNetworks
*
* #ORM\Table(name="user_in_social_networks")
* #ORM\Entity(repositoryClass="AppBundle\Repository\Registration\UserInSocialNetworksRepository")
*/
class UserInSocialNetworks
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* Many Socialnetwork Logins have one User
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Registration\User", inversedBy="socialnetworks")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*
*/
private $user;
/**
* #var int
*
* #ORM\Column(name="social_network_slug", type="string", length=255, nullable=true)
*/
private $socialNetworkSlug;
/**
* #var string
*
* #ORM\Column(name="social_identifier", type="string", length=255, nullable=true)
*/
private $socialIdentifier;
The extended FOSUBUserProvider class:
<?php
namespace AppBundle\Entity\Registration;
use HWI\Bundle\OAuthBundle\OAuth\Response\UserResponseInterface;
use HWI\Bundle\OAuthBundle\Security\Core\User\FOSUBUserProvider as BaseClass;
use Symfony\Component\Security\Core\User\UserInterface;
class FOSUBUserProvider extends BaseClass
{
/**
* {#inheritDoc}
*/
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
// get user_id and socialnetworkname from response
$userIdInSocialNetwork = $response->getUsername();
$socialnetwork = $response->getResourceOwner()->getName();
// Here I'd like to search for an existing $userIdInSocialNetwork
What I checked since now: I can't access the entitymanager in FOSUBUserProvider class, and I can't search that way:
$user = $this->userManager->findUserBy(array(
'socialIdentifier' => $userIdInSocialNetwork,
'social_network_slug' => $socialnetwork)
because it's a relation.
Thanks for any idea!

As you mentioned that you have extended FOSUBUserProvider i assume you have defined a new service for this, If so then you can pass doctrine's entity manager to your class #doctrine.orm.entity_manager. Following HWIOAuthBundle documentation for FOSUserBundle you can pass entity manager as
services:
my.custom.user_provider:
class: MyBundle\Security\Core\User\MyFOSUBUserProvider
arguments: ['#fos_user.user_manager', { facebook: facebook_id }, #doctrine.orm.entity_manager]
And then in your class you can use this service as
use Doctrine\ORM\EntityManager;
use FOS\UserBundle\Model\UserManagerInterface;
//.... other use statements
class FOSUBUserProvider extends BaseClass
{
private $em;
public function __construct(UserManagerInterface $userManager, array $properties, EntityManager $em)
{
$this->em = $em;
parent::__construct($userManager, $properties); /* pass dependencies to parent */
}
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$this->em->getRepository('AppBundle\Entity\Registration\UserInSocialNetworks')->findBy(....);
/* Do your stuff here */
}
}

Get error on my implementation but resolve this by adding quote to the third argument like bellow:
services:
my.custom.user_provider:
class: MyBundle\Security\Core\User\MyFOSUBUserProvider
arguments: ['#fos_user.user_manager', { facebook: facebook_id }, '#doctrine.orm.entity_manager']

Related

Overriding discriminator mapping on join table

The project works with multiple user logins (plumber, builder), the relevant information for each entity is saved in their respective tables. The structure for them is supplied below.
We've been storing the information for suppliers and have now gathered a few hundred entries. Suppliers are now requiring a login to access the system.
The current flow with the discriminator map is it creates an entry in the Users table and then saves in the respective user type table with the id being that of the user.id.
User.id = 5 => plumber.id = 5
User.id = 6 => builder.id = 6
User.id = 7 => plumber.id = 7
Suppliers have had their own table with their own incrementing ids which would cause clashes with the DiscriminatorMap(). Is there a way to have suppliers be unique and connect on a supplier.user_id instead of supplier.id, like the other tables?
<?php
namespace Project\Entities;
abstract class BaseEntity
{
public static $idCol = 'id';
public static $joins = [];
public static $orderBy = [];
}
?>
<?php
namespace Project\Entities;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping AS ORM;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
/**
* #ORM\Entity(repositoryClass="Project\Repositories\User\UserRepository")
* #ORM\Table(name="user")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({"user"="User", "plumber"="Plumber", "builder"="Builder"})
* #ORM\HasLifecycleCallbacks()
*/
class User extends BaseEntity implements Authenticatable
{
use \LaravelDoctrine\ORM\Auth\Authenticatable;
use \Project\Entities\Traits\HasContactDetails;
const TYPE_USER = "user";
const TYPE_PLUMBER = "plumber";
const TYPE_BUILDER = "builder";
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var int
*/
protected $id;
/**
* #ORM\Column(type="string", unique=true, nullable=false)
* #var string
*/
protected $login;
/**
* #ORM\Column(type="text")
* #var string
*/
protected $password;
/**
* #return int
*/
public function getId() {
return $this->id;
}
/**
* #return string
*/
public function getLogin() {
return $this->login;
}
/**
* #param string $login
*/
public function setLogin($login) {
$this->login = $login;
}
public function getAuthPassword() {
return $this->password;
}
/**
* Encrypt password when inserting
* #ORM\PrePersist
*/
public function onPrePersist() {
$this->encryptPassword();
}
/**
* Encrypt password when updating
* #ORM\PreUpdate
*/
public function onPreUpdate(\Doctrine\ORM\Event\PreUpdateEventArgs $event) {
if ($event->hasChangedField('password')) {
$this->encryptPassword();
}
}
/**
*
* #return string
*/
public function getType() {
if ($this instanceof \Project\Entities\Plumber) {
return self::TYPE_PLUMBER;
}
if ($this instanceof \Project\Entities\Builder) {
return self::TYPE_BUILDER;
}
return self::TYPE_USER;
}
public function getAuthIdentifierName() {
return "login";
}
.....
}
?>
User roles extending
<?php
namespace Project\Entities;
use Doctrine\ORM\Mapping AS ORM;
/**
* #ORM\Entity(repositoryClass="Project\Repositories\Plumber\PlumberRepository")
* #ORM\Table(name="plumber")
*/
class Plumber extends User
{
.....
}
?>
<?php
namespace Project\Entities;
use Doctrine\ORM\Mapping AS ORM;
/**
* #ORM\Entity(repositoryClass="Project\Repositories\Builder\BuilderRepository")
* #ORM\Table(name="builder")
*/
class Builder extends User
{
.....
}
?>
The current supplier entity.
<?php
namespace Project\Entities;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping AS ORM;
/**
* #ORM\Entity(repositoryClass="Project\Repositories\Supplier\SupplierRepository")
* #ORM\Table(name="supplier")
*/
class Supplier extends BaseEntity
{
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var int
*/
protected $id;
/**
* #ORM\Column(type="string")
* #var string
*/
protected $name;
/**
* #ORM\ManyToOne(targetEntity="Region")
* #var Region
*/
protected $region;
public function getId() {
return $this->id;
}
public function getName() {
return $this->name;
}
public function getRegion() {
return $this->region;
}
public function setId($id) {
$this->id = $id;
}
public function setName($name) {
$this->name = $name;
}
/**
* #param Region $region
*/
public function setRegion($region) {
$this->region = $region;
}
.....
}
?>

Clean way for many to many relation in doctrine

Is there a good way for managing this kind of relation in doctrine:
class Picture
{
private $id;
private $name;
}
class Product
{
private $id;
private $pictures;
}
class Article
{
private $id;
private $pictures;
}
Everytime i read many to many article (even here) i end with
class Picture
{
private $id;
private $name;
/**
*
* #ORM\ManyToMany(targetEntity="Product", mappedBy="pictures")
*/
private $products;
/**
*
* #ORM\ManyToMany(targetEntity="Article", mappedBy="pictures")
*/
private $articles;
}
class Product
{
private $id;
/**
* #var arraycollection
*
* #ORM\ManyToMany(targetEntity="Picture", inversedBy="products")
*/
private $pictures;
}
class Article
{
private $id;
/**
* #var arraycollection
*
* #ORM\ManyToMany(targetEntity="Picture", inversedBy="articles")
*/
private $pictures;
}
What is bother me is :
in the scope of one article there is no sense that my entity Picture have a attribute $products
in the scope of one product there is no sense that my entity Picture have a attribute $articles
So yea i can remove my attributes ($products and $articles) but in that case how i write :
Find all picture associated to a article
SELECT *
FROM picture
INNER JOIN article_picture ON picture.id = article_picture.picture_id
I hope i'm clear and Thank you

Symfony/FosUserBundle - Store Roles in DB

How can I store roles in DB?
I tried this method http://blog.jmoz.co.uk/symfony2-fosuserbundle-role-entities/
USER.php
/**
* #ORM\Entity
* #ORM\Table(name="`user`")
*/
class User extends BaseUser
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\OneToMany(targetEntity="Post", mappedBy="author")
*/
public $article;
/**
* #var string
*
* #ORM\Column(name="full_name", type="string", length=255, nullable=true)
*/
public $name;
/**
* #ORM\ManyToMany(targetEntity="Role")
* #ORM\JoinTable(name="users_roles")
*/
protected $roles;
public function __construct()
{
parent::__construct();
$this->roles = new ArrayCollection();
}
....settings...
/**
* Returns an ARRAY of Role objects with the default Role object appended.
* #return array
*/
public function getRoles()
{
return array_merge($this->roles->toArray(), array(new Role(parent::ROLE_DEFAULT)));
}
/**
* Returns the true ArrayCollection of Roles.
* #return ArrayCollection
*/
public function getRolesCollection()
{
return $this->roles;
}
/**
* Pass a string, get the desired Role object or null.
* #param string $role
* #return Role|null
*/
public function getRole($role)
{
foreach ($this->getRoles() as $roleItem) {
if ($role == $roleItem->getRole()) {
return $roleItem;
}
}
return null;
}
/**
* Pass a string, checks if we have that Role. Same functionality as getRole() except returns a real boolean.
* #param string $role
* #return boolean
*/
public function hasRole($role)
{
if ($this->getRole($role)) {
return true;
}
return false;
}
/**
* Adds a Role OBJECT to the ArrayCollection. Can't type hint due to interface so throws Exception.
* #throws Exception
* #param Role $role
*/
public function addRole($role)
{
if (!$role instanceof Role) {
throw new \Exception("addRole takes a Role object as the parameter");
}
if (!$this->hasRole($role->getRole())) {
$this->roles->add($role);
}
}
/**
* Pass a string, remove the Role object from collection.
* #param string $role
*/
public function removeRole($role)
{
$roleElement = $this->getRole($role);
if ($roleElement) {
$this->roles->removeElement($roleElement);
}
}
/**
* Pass an ARRAY of Role objects and will clear the collection and re-set it with new Roles.
* Type hinted array due to interface.
* #param array $roles Of Role objects.
*/
public function setRoles(array $roles)
{
$this->roles->clear();
foreach ($roles as $role) {
$this->addRole($role);
}
}
/**
* Directly set the ArrayCollection of Roles. Type hinted as Collection which is the parent of (Array|Persistent)Collection.
* #param Collection $role
*/
public function setRolesCollection(Collection $collection)
{
$this->roles = $collection;
}
}
ROLE.php
/**
* Role Entity
*
* #ORM\Entity
* #ORM\Table(name="roles")
*/
class Role implements RoleInterface
{
/**
* #ORM\Id
* #ORM\Column(type="integer", name="id")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(type="string", name="role", unique=true, length=70)
*/
private $role;
/**
* Populate the role field
* #param string $role ROLE_FOO etc
*/
public function __construct($role)
{
$this->role = $role;
}
/**
* Return the role field.
* #return string
*/
public function getRole()
{
return $this->role;
}
/**
* Return the role field.
* #return string
*/
public function __toString()
{
return (string)$this->role;
}
}
I'm always getting this error.
[Doctrine\ORM\Mapping\MappingException]
Property "roles" in "***\Entity\User" was already
declared, but it must be declared only once
Could someone help me please?
Thanks
You need to get rid of everything related to roles inside your User Entity. Roles are supported by the bundle out of the box.
I just setup the bundle and everything is working fine. This is what my User Entity looks like which extends the BaseUser
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use FOS\UserBundle\Model\User as BaseUser;
/**
* User
*
* #ORM\Table(name="user")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User extends BaseUser
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
public function __construct()
{
parent::__construct();
// your own logic
}
/**
* Get id
*
* #return int
*/
public function getId()
{
return $this->id;
}
}
Once the bundle was setup, I ran the schema update command.
As you can see the roles column is added automatically via the bundle.
You can play around with bundle using the command is comes with to create the user.
The documentation of the bundle can be found here. Here is another good resource to get you started.

Eager fetching with Sonata list view

I'm using SonataAdmin and SonataDoctrineORMAdmin bundles to manage entities.
The problem is I can't figure out how to eager fetch the related entities in the list view and as the number of listed entities increase the number of queries executed increasing rapidly as well.
I tried adding `fetch="EAGER" to the relation annotations but the profiles show that Sonata executes the separate queries anyway.
Here's one relation worth of code:
Post
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* #ORM\Table()
* #ORM\Entity
*/
class Post
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="name", type="string", length=255)
**/
private $name;
/**
* #ORM\ManyToMany(targetEntity="Acme\AppBundle\Entity\Tag", fetch="EAGER")
* #ORM\JoinTable(name="join_post_to_tag",
* joinColumns={#ORM\JoinColumn(name="post_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
* )
**/
private $tags;
public function getId()
{
return $this->id;
}
public function setName($names)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setTags($tags)
{
$this->tags = $tags;
return $this;
}
public function __toString()
{
return $this->getName();
}
}
Tag
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Table()
* #ORM\Entity
*/
class Tag
{
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\Column(name="value", type="string", length=255)
*/
private $value;
public function getId()
{
return $this->id;
}
public function setValue($value)
{
$this->value = $value;
return $this;
}
public function getValue()
{
return $this->value;
}
public function __toString()
{
return ($this->getValue()) ? : '';
}
}
The first related query that is run is fetching all the posts:
SELECT DISTINCT p0_.id AS id0, p0_.id AS id1
FROM Post p0_
LEFT JOIN join_post_to_tag j1_ ON p0_.id = j1_.post_id
LEFT JOIN Tag p1_ ON p1_.id = j1_.target_id
ORDER BY p0_.id ASC
But this does not fetch the related tags or even if it does, it still queries it again:
SELECT t0.id AS id1, t0.value AS value2
FROM Tag t0
INNER JOIN join_post_to_tag ON t0.id = join_post_to_tag.tag_id
WHERE join_post_to_tag.post_id = ?
I tried to mess with the createQuery method in the admin class but could not really find a way to make the related entities fetched correctly.
Is there a way to force the list view to eager fetch the required related entities?
You are on the right track, using the createQuery($context) method.
I have achieved eager loading as following:
public function createQuery($context = 'list')
{
$query = parent::createQuery($context); // let sonata build it's default query for the entity
$rootEntityAlias = $query->getRootAlias(); // get the alias defined by sonata for the root entity
$query->join($rootEntityAlias.'.relationFieldName', 'relationFieldAlias'); // manualy define the join you need
$query->addSelect('relationFieldAlias'); // this is the key line. It is not enough to join a table. You have to also add it to the select list of the query, so that it's actualy fetched
// $query->join(...) // repeat the process of joining and selecting for each relation field you need
// $query->addSelect(...)
return $query; // return the altered query to sonata. this will only work for the "list" action.
}
If you're having trouble using this, let me know:)
Further reads on this topic:
SO question
docs

zf2 doctrine 2 - Output OnetoOne Unidirectional

After annotating an OneToOne Unidirectional, i want to output the joined column, without using a form.
One People Entity has got a column to store the id of Country Entity.
What i can do: I can store the id of the country into the People Entity using a form with a dropdown select, which is bound to the Country Entity.
Problem: I can not enter the value country of the, hopefully correct, joined table.
People Entity:
<?php
namespace People\Entity;
use Doctrine\ORM\Mapping as ORM;
// ...
/**
* A people entity.
*
* #ORM\Entity
* #ORM\Table(name="icd_people")
* #property int $id
// ...
* #property string $ic_hq_country
*/
class People implements InputFilterAwareInterface
{
protected $inputFilter;
/**
* #ORM\Id
* #ORM\Column(type="integer");
*/
protected $id;
/**
* #ORM\Column(type="integer")
* #ORM\OneToOne(targetEntity="Country")
* #ORM\JoinColumn(name="ic_hq_country", referencedColumnName="id")
*/
protected $ic_hq_country;
// getter and setter
}
The Country Entity:
<?php
namespace People\Entity;
use Doctrine\ORM\Mapping as ORM;
//...
/**
* A Country entity.
*
* #ORM\Entity
* #ORM\Table(name="pre_country")
* #property int $id
* #property string $country
*/
class Country implements InputFilterAwareInterface
{
protected $inputFilter;
/**
* #ORM\Id
* #ORM\Column(type="integer");
*/
protected $id;
/**
* #ORM\Column(type="string")
*/
protected $country;
/**
* Get id
*
* #return integer
*/
public function getId()
{
return $this->id;
}
/**
* Set country
*
* #param string $country
* #return Country
*/
public function setCountry($country)
{
$this->country = $country;
return $this;
}
/**
* Get country
*
* #return string
*/
public function getCountry()
{
return $this->country;
}
/**
* Convert the object to an array.
*
* #return array
*/
public function getArrayCopy()
{
return get_object_vars($this);
}
public function setInputFilter(InputFilterInterface $inputFilter)
{
throw new \Exception("Not used");
}
public function getInputFilter()
{
throw new \Exception("Not used");
}
}
The Controller Action:
public function indexAction()
{
$userid = $this->zfcUserAuthentication()->getIdentity()->getId();
return new ViewModel(array(
'pea' => $this->getEntityManager()->find('People\Entity\People', $userid),
));
}
The View giving the id of the country, but not the name:
<?php echo $this->escapeHtml($pea->ic_hq_country);?>
I actually expected something like this being possible, to output the country name and not the id:
<?php echo $this->escapeHtml($pea->country);?>
Thank you for reading, and for any help, which could lead me into the right direction!
You should not use the #Column anotation in the $ic_hq_country field of the Peopleentity.
/**
* #ORM\OneToOne(targetEntity="Country")
* #ORM\JoinColumn(name="ic_hq_country", referencedColumnName="id")
*/
protected $ic_hq_country;
like this, hopefully ic_hq_country will be a proxy to the entity instead of the id.
so in your view you can use:
<?php echo $pea->ic_hq_country->getId();?>
and also
<?php echo $this->escapeHtml($pea->ic_hq_country->getCountry());?>