i need convert SQL query to Doctrine Query Builder - symfony

I have a relation ManyToMany and the tables in my databases are :
article : (id,titre,description,category,createAt)
article_groupe_auteur: (article_id,groupe_auteur_id)
groupe_auteur : (id,nom,prenom,adress,institution,city)
i need to to retrive the authors relied to an article.
Sql Query
use RevueProject;
select nom_auteur,prenom_auteur,nom_ins,adresse_ins from groupe_auteur ga
INNER JOIN
article_groupe_auteur aga on groupe_auteur.id = aga.groupe_auteur_id
INNER JOIN article a on aga.article_id = a.id
where a.id=21;
Dql Query
public function SelectArticleAuthors2(int $Id){
return $this->createQueryBuilder('GA');
->innerJoin('article_groupe_auteur','aga', Join::WITH , 'aga = ga.groupe_auteur_id')
->innerJoin('article','a',Join::WITH , 'a = aga.article_id')
->where('aga.article_id = : Id')
->setParameter('Id', $Id);
->getQuery()->getResult()
}

Its how i get the authors relied to an specify article by a relation ManyToMany
class GroupeAuteurRepository extends ServiceEntityRepository
{
/**
* #return mixed
*/
public function SelectArticleAuthors(int $id)
{
// ga (alias) : GroupeAuteur Entity
return $this->createQueryBuilder('ga')
// articles (collection)
->innerJoin('ga.articles','a','WITH','a.id=:id')
->setParameter('id', $id)
->getQuery()->getResult();
}
}

Related

Doctrine hydrate not mapped field

I am making a query with Doctrine which calculates a custom field using a CASE WHEN like this:
public function findLatestPaginator($page = 1, $itemsPerPage)
{
$qb = $this->createQueryBuilder('n');
$qb = $qb
->select(['n AS news', 'CASE WHEN lu.id IS NOT NULL THEN 1 ELSE 0 END AS n.liked'])
->leftJoin('n.likingUsers', 'lu')
;
$qb = $qb
->orderBy('n.date', 'DESC')
->setFirstResult($itemsPerPage * ($page - 1))
->setMaxResults($itemsPerPage)
->getQuery()
;
return $qb->getResult();
}
In my entity I have a field called $liked which is not mapped. Is it possible to make the query (or the hydrator?) automatically set the field on the resulting entity?
Right now I am making a foreach loop and manually setting the property:
/**
* #return News[]
*/
private function convertNews(array $records)
{
$newsList = [];
foreach ($records as $record) {
if (isset($record['liked'], $record['news'])) {
/** #var News */
$news = $record['news'];
$news->liked = boolval($record['liked']);
$newsList[] = $news;
}
}
return $newsList;
}
Maybe a DTO could be useful here : doctrine documentation
Basically, you define a PHP class which is not mapped to your model.
You can then select what you need and trigger the instantiation of an object of this PHP class from the DQL query.
Hope this helps !
Default hydrator does not include unmapped properties (wow, right?).
You want to override/extend doctrine/orm/lib/Docrtrine/ORM/Internal/Hydration/AbstractHydrator
line 386 where it only checks for $classMetadata fieldMappings
Or you can hack around by using DTO and query in transformer, if performance is not needed

JOIN DQL SYMFONY

i want to count how many baby for a parent
class abonnementRepository extends \Doctrine\ORM\EntityRepository
{
public function SumEnfantDQL($id)
{
$entityManager = $this->getEntityManager();
$query = $entityManager->createQueryBuilder();
$query->select('sum(o.id) AS somme');
$query->from('AppBundle:enfant', 'o');
$query->join('AppBundle:User','p')->where('p.id = :id');
$rez = $query->getQuery()->getResult();
return $rez;
}
}
the entity enfant had matricul_prt and entity user had enfant_id and $id parameter is the parent id
i don't know how it work with join or innerJoin .So what i want to do is
SELECT SUM(*)
FROM enfant e
WHERE e.matricul_prt = $id;
Thank you so much
First of all, you should create a Repository class for AppBundle:enfant, this repo does not look like created fot 'enfant'.
Next the method should look like below, but only if there is valid association between 'enfant' and 'User'.
public function SumEnfant(int $id): int
{
return $this->createQueryBuilder('e') <- alias for 'enfant'
->select('sum(e.id)')
->join('e.user', 'u') <- join by entity property
->where('u.id = :id') <- condition for associated entity
->setParameter('id' , $id) <- parameter
->getQuery()
->getSingleScalarResult();
}
Try this, read doc once again and modify for your case.

Doctrine many-to-many relationship: failed to get data automatically from database

In my Symfony code I've used Doctrine. In an Entity ( AppBundle\Entity\Core\User ) I defined a column foodTypes, which is associated with another Entity (AppBundle\Entity\FoodRecording\FoodType). I've defined an Many-to-Many relationship between User and FoodType, with a linking table foodrecording_user, joining User.username and FoodType.foodtype_code. The code is shown below.
// Entity\Core\User
namespace AppBundle\Entity\Core;
......
class User implements AdvancedUserInterface, \Serializable {
......
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\FoodRecording\FoodType")
* #ORM\JoinTable(name="foodrecording_user",
* joinColumns={#ORM\JoinColumn(name="username", referencedColumnName="username", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="foodtype_code", referencedColumnName="code", onDelete="CASCADE")}
* )
*/
private $foodTypes;
public function getFoodTypes()
{
$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
return $this->foodTypes;
}
However, as I wanted to get directly all the food types of a certain user, using
$userFoodTypes = $this->get('security.token_storage')->getToken()->getUser()->getFoodTypes();
then I got
$userFoodTypes =====> array[]
I expected that as I've created the M-M relationship, Doctrine would automatically fetch the data I need, but it is not the case!
Therefore, I have to write my own code to retrieve the data from the DB / table like following:
public function fetchUserFoodTypes()
{
global $kernel;
$container = $kernel->getContainer();
$em = $container->get('doctrine.orm.entity_manager');
$conn = $em->getConnection();
$sql = 'SELECT * FROM foodrecording_user where username = :username';
$stmt = $conn->prepare($sql);
$stmt->execute([
'username' => $this->getUsername(),
]);
$data = $stmt->fetchAll();
$res = [];
foreach ($data as $item) {
$foodtype = $em->getRepository('AppBundle\Entity\FoodRecording\FoodType')->findByCode($item['foodtype_code']);
$res[] = $foodtype;
}
return $res;
}
public function getFoodTypes()
{
$this->foodTypes = $this->fetchUserFoodTypes();
//$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
return $this->foodTypes;
}
Only in this way I am able to get the food types associated with a user.
Could anyone explain to me, why I can't simply use the M-M definition and let doctrine do all the thing automatically for me? Why should I explicitly write my own function to retrieve data from DB? Is Doctrine not smart enough?
This part:
$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
Belongs to the __construct method, not getter.. You see, like this, every time you call your getter, you reset the property foodTypes to an empty instance of an ArrayCollection

Symfony2 - Many to Many relationship and LIMIT

Im use many-to-many relationship;
User entity;
/**
* #ORM\ManyToMany(targetEntity="Conversation", inversedBy="users")
*/
protected $conversations;
Conversation entity;
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="conversations")
* #ORM\JoinTable(name="user_conversation")
*/
protected $users;
When, I work this function;
$user->getConversations();
Symfony work this sql code in background;
SELECT
t0.id AS id1,
t0.conversationid AS conversationid2
FROM
Conversation t0
INNER JOIN user_conversation ON t0.id = user_conversation.conversation_id
WHERE
user_conversation.user_id = ?
And select all conversation. This will be performance problem. So, I work with repository class. But, I can't work many-to-many and limit function with together. What should I do? What I write to repository class?
If you want to optimize access to large collections in doctrine just use Criteria (That only works on OneToMany associations.)
Example:
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Criteria;
/**
* #ORM\Entity
* #ORM\Table
*/
class User
{
....
public function getLatestConversation()
{
$criteria = Criteria::create()
->setMaxResults(10);
return $this->conversations->matching($criteria);
}
}
For ManyToMany I think you must create a custom query:
public function getLatestConversations($user)
{
$qb = $this->createQueryBuilder("c");
$qb
->leftjoin("c.users", "u")
->where("u = :user")
->setParameter("user", $user)
->setMaxResults(2);
return $qb->getQuery()->getResult();
}
You can convert it to Arraycollection Object:
$input = $repo->getConversations(); //manytomany relation
$arr = new ArrayCollection();
foreach ($input as $e){
$arr->add($e);
}
And then use your Criteria on $arr
Try
$repository = $em->getRepository('YourBundle:Conversations');
$query = $repository->createQueryBuilder('C')
->join('C.Users', 'U')
->where('U.id = :uid')
->setMaxResults(20) //set your amount for limit
->setParameter('uid', $user_id)
->getQuery();
$result = $query->getResults();

FOS bundle - How to select users with a specific role?

I am using the FOS bundle and I want to retrieve all users with a given ROLE from the database.
What is the best way to do this?
Just add this in your UserRepository or replace $this->_entityName by YourUserBundle:User:
/**
* #param string $role
*
* #return array
*/
public function findByRole($role)
{
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->where('u.roles LIKE :roles')
->setParameter('roles', '%"'.$role.'"%');
return $qb->getQuery()->getResult();
}
If you are using FOSUser Groups you should use:
/**
* #param string $role
*
* #return array
*/
public function findByRole($role)
{
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->leftJoin('u.groups', 'g')
->where($qb->expr()->orX(
$qb->expr()->like('u.roles', ':roles'),
$qb->expr()->like('g.roles', ':roles')
))
->setParameter('roles', '%"'.$role.'"%');
return $qb->getQuery()->getResult();
}
Well, if there is no better solution, I think I will go to a DQL query:
$query = $this->getDoctrine()->getEntityManager()
->createQuery(
'SELECT u FROM MyBundle:User u WHERE u.roles LIKE :role'
)->setParameter('role', '%"ROLE_MY_ADMIN"%');
$users = $query->getResult();
If you have this requirement and your user list will be extensive, you will have problems with performance. I think you should not store the roles in a field as a serialized array. You should create an entity roles and many to many relationship with the users table.
As #Tirithen states, the problem is that you will not get the users that have an implicit role due to role hierarchy. But there is a way to work around that!
The Symfony security component provides a service that gives us all child roles for a specific parent roles. We can create a service that does almost the same thing, only it gives us all parent roles for a given child role.
Create a new service:
namespace Foo\BarBundle\Role;
use Symfony\Component\Security\Core\Role\RoleHierarchy;
use Symfony\Component\Security\Core\Role\Role;
/**
* ReversedRoleHierarchy defines a reversed role hierarchy.
*/
class ReversedRoleHierarchy extends RoleHierarchy
{
/**
* Constructor.
*
* #param array $hierarchy An array defining the hierarchy
*/
public function __construct(array $hierarchy)
{
// Reverse the role hierarchy.
$reversed = [];
foreach ($hierarchy as $main => $roles) {
foreach ($roles as $role) {
$reversed[$role][] = $main;
}
}
// Use the original algorithm to build the role map.
parent::__construct($reversed);
}
/**
* Helper function to get an array of strings
*
* #param array $roleNames An array of string role names
*
* #return array An array of string role names
*/
public function getParentRoles(array $roleNames)
{
$roles = [];
foreach ($roleNames as $roleName) {
$roles[] = new Role($roleName);
}
$results = [];
foreach ($this->getReachableRoles($roles) as $parent) {
$results[] = $parent->getRole();
}
return $results;
}
}
Define your service for instance in yaml and inject the role hierarchy into it:
# Provide a service that gives you all parent roles for a given role.
foo.bar.reversed_role_hierarchy:
class: Foo\BarBundle\Role\ReversedRoleHierarchy
arguments: ["%security.role_hierarchy.roles%"]
Now you are ready to use the class in your own service. By calling $injectedService->getParentRoles(['ROLE_YOUR_ROLE']); you will get an array containing all parent roles that will lead to the 'ROLE_YOUR_ROLE' permission. Query for users that have one or more of those roles... profit!
For instance, when you use MongoDB you can add a method to your user document repository:
/**
* Find all users with a specific role.
*/
public function fetchByRoles($roles = [])
{
return $this->createQueryBuilder('u')
->field('roles')->in($roles)
->sort('email', 'asc');
}
I'm not into Doctrine ORM but I'm sure it won't be so different.
You can use just this on your DQL:
SELECT u FROM YourFavouriteBundle:User u WHERE u.roles [NOT] LIKE '%ROLE_YOUR_ROLE%'
Of course with QueryBuilder it's more elegant:
// $role = 'ROLE_YOUR_ROLE';
$qb->where('u.roles [NOT] LIKE :role')
->setParameter('role', "%$role%");
Finally i solved it, following is an exact solution:
public function searchUsers($formData)
{
$em = $this->getEntityManager();
$usersRepository = $em->getRepository('ModelBundle:User');
$qb = $usersRepository->createQueryBuilder('r');
foreach ($formData as $field => $value) {
if($field == "roles"){
$qb->andWhere(":value_$field MEMBER OF r.roles")->setParameter("value_$field", $value);
}else{
$qb->andWhere("r.$field = :value_$field")->setParameter("value_$field", $value);
}
}
return $qb->getQuery()->getResult();
}
Cheers!
In case you need to filter users by role using a DQL filter in a YAML file (In EasyAdminBundle for instance)
entities:
Admin:
class: App\Entity\User
list:
dql_filter: "entity.roles LIKE '%%ROLE_ADMIN%%'"
Here I give an alternative solution :
I find users of roles for a given array
In controller I call the function like that
$users = $userRepository->findUsersOfRoles(['ROLE_ADMIN', 'ROLE_SUPER_USER']);
Then in my repository I make a loop to generate condition and set the parameters :
public function findUsersOfRoles($roles)
{
$condition = 'u.roles LIKE :roles0';
foreach ($roles as $key => $role){
if ($key !== 0){
$condition .= " OR u.roles LIKE :roles".$key;
}
}
$query = $this->createQueryBuilder('u')
->where($condition);
foreach ($roles as $key => $role){
$query ->setParameter('roles'.$key, '%"'.$role.'"%');
}
return $query->getQuery() ->getResult();
}

Resources