I'm trying to select multiples objects in a single array result, but i cant see how to select it ...
Well actually I have this DQL:
$dql = ' SELECT obj AS Object1, objExternalRef AS Object2 FROM MyEntity1 AS obj
INNER JOIN obj.objChild AS objChild
INNER JOIN MyEntity2 AS objExternalRef WITH objExternalRef.objChild= objChild
WHERE something
';
$result = $em->createQuery($dql)
->setParameter( ... )
->setHint(\Doctrine\ORM\Query::HINT_FORCE_PARTIAL_LOAD, 1)
->getResult();
returning this array[ Object1 , Object2, Object1, Object2, ... ]:
{
"Object1": {...}
},
{
"Object2": { ...}
},
And i would like to encapsule this in a new object containing theses 2 objects like:
{
"Object1" : { ... },
"Object2" : { ... }
},
{
"Object1" : { ... },
"Object2" : { ... }
},
I think I need to make a select of this select(query) to group theses 2 object in a new object, but i can't figure out how to make it.
I also know that if i just make a for($i = 0; i < sizeof($result); i += 2) i can concat these 2 objs in a new one, but I don't think this is so far a nice fix.
Can someone give-me a light here?
If these two objects are related good practice will be to define this association mapping ie one to many or many to one in your entity relationship. For example
In country entity/object --(of course also defined on state side)
/**
* #ORM\OneToMany(targetEntity="State", mappedBy="stateCountry")
*/
private $states;
You can simply do a
$country->getStates()
See Doctrine Association mapping for more
However if they are not and you want mixed results you will need to use result set mapping/scalar results. I usually use native queries for something like this. For example
$rsm = new ResultSetMapping();
$rsm->addScalarResult('name', 'name');
$rsm->addScalarResult('id', 'id');
$rsm->addScalarResult('slug', 'slug');
$rsm->addScalarResult('num', 'num');
$sql = "SELECT f.name, f.id, f.slug, COUNT( t.demo_id ) AS num FROM tag_demos t LEFT JOIN tag f ON f.id = t.tag_id " .
" GROUP BY t.tag_id ORDER BY COUNT( t.demo_id ) DESC LIMIT 20";
$query = $this->getEntityManager()->createNativeQuery($sql, $rsm);
return $query->getResult();
See Native Query-Result Set Mapping for more examples.
Related
Im trying to get the best rated movies by average from my database and hydrate them nicely into a DTO with doctrine so i can work well later with it and integrate them e.g. into my api with api platform.
I already managed to get it work with a raw SQL query but i cannot manage to get it work with hydration into a DTO with doctrine.
namespace App\Repository;
use App\Entity\Movie;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class MovieRepository extends ServiceEntityRepository
{
public function findBestRated()
{
$sql = "
SELECT m.*, AVG(Cast(r.rating as Decimal)) AS avg_rating, COUNT(r.id) AS count_rating
FROM `movie` m
JOIN rating r ON r.movie_id = m.id
GROUP BY m.id
ORDER BY avg_rating DESC
LIMIT 10
";
$connection = $this->getEntityManager()->getConnection();
$statement = $connection->prepare($sql);
$result = $statement->executeQuery();
return $result->fetchAllAssociative();
}
}
I would like to use a DTO like below:
namespace App\Dto;
use App\Entity\Movie;
class RatedMovie
{
public Movie $movie;
public float $averageRating;
public int $ratingCount;
public function __construct(Movie $movie, float $averageRating, int $ratingCount)
{
$this->movie = $movie;
$this->averageRating = $averageRating;
$this->ratingCount = $ratingCount;
}
}
I found some information on https://geek-week.imtqy.com/articles/en496166/index.html, but still i cannot get the hydration running. I already tried with ResultSetMapping and ResultSetMappingBuilder and a native doctrine query. With ResultSetMappingBuilder i can kind of simulate my raw sql query but the result is still an associative array and not mapped into the RatedMovie DTO.
public function findBestRated()
{
$rsm = new ResultSetMappingBuilder($this->getEntityManager());
$rsm->addScalarResult('id', 'movieId');
$rsm->addScalarResult('name', 'movieName');
$rsm->addScalarResult('avg_rating', 'averageRating', Types::FLOAT);
$rsm->addScalarResult('count_rating', 'countRating', Types::INTEGER);
$sql = "
SELECT m.*, AVG(r.rating) AS avg_rating, COUNT(r.id) AS count_rating
FROM `movie` m
JOIN rating r ON r.name_id = m.id
GROUP BY m.id
ORDER BY avg_rating DESC
LIMIT 10
";
$query = $this->getEntityManager()->createNativeQuery($sql, $rsm);
return $query->getResult();
}
I cannot get it running with newObjectMappings or the kind of strange syntax SELECT NEW DepartmentSalary(d.dept_no, avg_salary) FROM ... from the linked article. Any ideas?
I have two entities Article and Tag with a Many to Many relation. For a bulk edit I query some attributes from every Articles. In that query I also want to query all assigned Tag-IDs from every Article.
I read something about the function GROUP_CONCAT() but Doctrine doesn't support that function yet.
My Doctrine statement is currently like that:
$this->getEntityManager()->createQuery('SELECT e.articleId, e.articleTitle, t.tagId FROM App\Entity\Article e LEFT JOIN e.tags t');
It would be best to fetch those assigned Tag-IDs as an array.
So I extended Doctrine with GROUP_CONCAT function from github.com/beberlei/DoctrineExtensions and use following code to get all IDs as an array:
$query = $this->getEntityManager()->createQuery('SELECT e.articleId, e.articleTitle, GROUP_CONCAT(t.tagId) AS tagIds FROM App\Entity\Article e LEFT JOIN e.tags t GROUP BY e.articleId');
$articleArray = $query->execute();
for ($i = 0; $i < count($articleArray); $i++) {
if (strpos($articleArray[$i]['tagIds'], ',')) {
$arr = array_map('intval', explode(',', $articleArray[$i]['tagIds']));
$articleArray[$i]['tagIds'] = ($arr) ? $arr : [];
}
elseif (!is_null($articleArray[$i]['tagIds'])) $articleArray[$i]['tagIds'] = [(int) $articleArray[$i]['tagIds']];
else continue;
}
I'm about to output a list that includes several documents(called waiver). However not every user should be allowed to see all documents and therefore I've implemented an filter to check if the user has the same "airline" and "market" assigned. So every user should only see the documents that are assigned to his "airline" and "market".
This is f.e. the getter for the airline of the user entity:
/**
* Get airlines
*
* #return array
*/
public function getAirlines()
{
if($this->airlines != null)
{
$airlines = explode(",", $this->airlines);
return $airlines;
}
return Array();
}
This is the controller logic:
//Get User:
$user = $this->container->get('security.context')->getToken()->getUser();
// Gets an Array of User markets
$user_markets = $user->getMarkets();
// Gets an Array of User carriers
$user_airlines = $user->getAirlines();
if(!$this->ROLE_IS(Array( 'ROLE_XY'))){
$query = $em->createQuery(
'SELECT w
FROM WaiverBundle:Waiver w
WHERE w.carrier = :carrier
AND w.market = :market
ORDER BY w.id DESC'
)
->setFirstResult($page*10-10)
->setMaxResults(10)
// I wan't to get the whole array and not just one position here:
->setParameters(array(':carrier'=>$user_airlines[0],
':market'=>$user_markets[0],
));
}else{
$query = $em->createQuery(
'SELECT u
FROM WaiverBundle:Waiver u
ORDER BY u.id DESC'
)
->setFirstResult($page*10-10)
->setMaxResults(10);
}
Question: How do I manage to compare the DQL attributes with an array and not just a string as a parameter?
I think you want to use "IN" syntax, not "=" syntax:
'SELECT w
FROM WaiverBundle:Waiver w
WHERE w.carrier IN (:carrier)
AND w.market = :market
ORDER BY w.id DESC'
Your query is not complicated. I think you should consider QueryBuilder instead of DQL in this case. Something like this would do the trick:
$qb = $em->createQueryBuilder();
$qb->select('w')
->from('WaiverBundle:Waiver', 'w')
->where($qb->expr()->in('w.carrier', ':carrier'))
->andWhere($qb->expr()->eq('w.market', ':market'))
->orderBy('w.id', 'DESC')
->setParameters(
array(
'carrier'=>$user_airlines[0],
'market'=>$user_markets[0)
);
I have a couple of pretty complex queries, and for each of them I have to write a second query counting results. So for example, in the model:
$dql = "SELECT u FROM AcmeBundle:Users u LEFT JOIN AcmeBundle:Products p WITH u.id = p.id";
I would have to create a duplicate query like this:
$countingQuery = "SELECT COUNT(u.id) FROM AcmeBundle:Users u LEFT JOIN AcmeBundle:Products p WITH u.id = p.id";
The main problem with that is that with every change in the first query, I would have to change the second either.
So I came up with another idea:
$countingSelect = "SELECT COUNT(u.id)";
$noncountingSelect = "SELECT u";
$dql = " FROM AcmeBundle:Users u LEFT JOIN AcmeBundle:Products p WITH u.id = p.id";
return $this->getEntityManager()->createQuery($noncountingSelect . $dql)
->setHint('knp_paginator.count', $this->getEntityManager()->createQuery($countingSelect . $dql)->getSingleScalarResult());
It works of course, but the solution seems quite ugly with larger selects.
How can I solve this problem?
I believe the Doctrine\ORM\Tools\Pagination\Paginator will do what you're looking for, without the additional complexity.
$paginator = new Paginator($dql);
$paginator
->getQuery()
->setFirstResult($pageSize * ($currentPage - 1)) // set the offset
->setMaxResults($pageSize); // set the limit
$totalItems = count($paginator);
$pagesCount = ceil($totalItems / $paginator->getMaxResults());
Code yanked from: http://showmethecode.es/php/doctrine2/doctrine2-paginator/
You can create a customer repository as explained in the docs and add your query to that with a minor edit like..
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function findProducts()
{
return $this->findProductsOrCountProducts();
}
public function findCountProducts()
{
return $this->findProductsOrCountProducts(true);
}
private function findProductsOrCountProducts($count = false)
{
$queryBuilder = $this->createQueryBuilder('u');
if ($count) {
$queryBuilder->select('COUNT(u.id)');
}
$query = $queryBuilder
->leftJoin('AcmeBundle:Products', 'p', 'WITH', 'u.id = p.id')
->getQuery()
;
if ($count) {
return $query->getSingleScalarResult();
} else {
return $query->getResult();
}
}
}
Then you can call your method using...
$repository = $this->getDoctrine()
->getRepository('AcmeBundle:Users');
// for products
$products = $repository->findProducts();
// for count
$countProducts = $repository->findCountProducts();
Note:
I know it's not best practice to just say look at the docs for the customer repository bit s here' the YAML mapping...
# src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml
Acme\StoreBundle\Entity\Product:
type: entity
repositoryClass: Acme\StoreBundle\Entity\ProductRepository
# ...
First I will give an example with some pseudo code and then I will explain what is the problem. Let me say I have two entities User and Phonenumber. Their relation is one-to-many. In my UserRepository I can have something like that:
class UserRepository
{
public function getUser($id, $type)
{
$users = $this->createQuery("SELECT u, p FROM User u JOIN u.phonenumbers p
WHERE u.id = :id AND p.type = :type")
->setParameters(array(
'id' => $id,
'type' => $type,
))
->getResult();
return $users[0];
}
}
In my app if I have something like:
$user = $userRepo->getUser(1, 'home');
var_dump($user->getPhonenumbers()); // here phonenumbers collection is ok
$user = $userRepo->getUser(1, 'work');
var_dump($user->getPhonenumbers()); // Here phonenumbers collection is wrong.
// It's exactly the same as the previous one.
So my questions is: Is it possible to use fetch join (with different criteria) and to get the proper collection each time?
Fetch joining and filtering a collection are not things that work together quite well. Here's how you should do it:
SELECT
u, p
FROM
User u
JOIN
u.phonenumbers p
JOIN
u.phonenumbers p2
WHERE
u.id = :id
AND
p2.type = :type
This applies filtering on the second joined (and not hydrated) p2, which results in correct hydration and filtering.
Use querybuilder, it is much simpler.
public function getUser($id, $type)
{
return $this->createQueryBuilder("u")
->leftJoin("u.Phonenumbers", "p", "WITH", "p.type=:type")
->where("u.id=:id")
->setParameters(.....)
->getQuery()
->getOneOrNullResult() ;
}