Symfony правильное построение запроса к сторонней таблице

bars80081

Новичок
Добрый день,

преследую фундаментальный вопрос: как ЭТО правильно сделать?

по мотивам Создание блога на Symfony 2.8 lts пытаюсь понять практическую пользу symfony.

Имеется блог, в котором есть сущности Блог и Комментарии, а также БлогРепозиторий. в репозитории задаётся метод на получение последних записей из блога. по задаче мы хотим также указать число комментариев к данному блогу. в статье предлагается такой вариант:

PHP:
namespace Blogger\BlogBundle\Entity\Repository;

/**
 * BlogRepository
 *
 * This class was generated by the Doctrine ORM. Add your own custom
 * repository methods below.
 */
class BlogRepository extends \Doctrine\ORM\EntityRepository
{
    public function getLatestBlogs($limit = null)
    {
		$qb = $this->createQueryBuilder('b')
				   ->select('b, c')
				   ->leftJoin('b.comments', 'c')
				   ->addOrderBy('b.created', 'DESC');

		if(false === is_null($limit)) {
			$qb->setMaxResults($limit);
		}

		$result = $qb->getQuery()->getResult();
		
		return $result;
    }
}
до появления необходимости указать число комментариев к записи в блоге всё шло отлично. но тут автор со своим leftJoin предлагает очевидный перерасход ресурсов, так как он выбирает все комментарии, относящиеся к записи в блоге только для того, чтобы потом посчитать их число.
захотелось мне сделать это отдельным запросом, но не так всё оказалось просто. то ли опять не нашёл в документации волшебного объяснения как всё сделать просто, то ли в принципе осуществляю неправильный подход. в итоге пришёл к такому виду:

PHP:
		$qb = $this->createQueryBuilder('b')
				   ->select('b, c')
				   ->leftJoin('b.comments', 'c')
				   ->addOrderBy('b.created', 'DESC');

		if(false === is_null($limit)) {
			$qb->setMaxResults($limit);
		}

		$result = $qb->getQuery()->getResult();
		
		$idis = array();
		foreach($result as $key => $bg) {
			$id = $bg->getId();
			$idis[] = $id;
		}
		
		if($idis) {
			$qc = $this->_em->createQuery('SELECT COUNT(c.id) cnt, c.blog_id FROM Blogger\BlogBundle\Entity\Comment AS c WHERE c.blog_id IN (?1) GROUP BY c.blog_id')
					   ->setParameter(1, $idis);

			$qr = $qc->getResult();
			dump($qr);
		}
что мне не нравится по ряду моментов. в итоге возникли следующие вопросы:

1. пытался построить через createQueryBuilder - не удалось. можно ли через него собрать свободный запрос или он в принципе работает только с тем, что правильно оформлено в виде Entities и Repositories?
2. не получилось запросить данные сторонней сущности. $this->createQueryBuilder('c')->select('c') всё равно возвращают записи блога, а не комментариев. могу ли я через createQueryBuilder обратиться к сущности без необходимости начинать запрос с сущности блога и далее по взаимным связям?
3. не удалось прямо указать таблицу в FROM, createQuery требует указать класс сущности. получается, что без класса сущности я в принципе не могу построить запрос?
4. указание Blogger\BlogBundle\Entity\Comment как-то слишком длинно и зависимо от файловой системы. сами то через createQueryBuilder проще находят, что значит b и c. можно ли как-то указать что ли пространство имён в котором работаю заранее? и далее слать запросы с указанием коротких имён классов?
5. почему не удаётся в запросе прописать SELECT COUNT(*)? почему ругается на звёздочку?
6. почему не удаётся в запросе прописать `? почему ругается на апостроф? как он разбирает где служебные слова, а где имена полей?

ну и фундаментальный вопрос:
7. как ЭТО было бы правильно сделать?

спасибо
 

bars80081

Новичок
и ещё один вопрос.
из SELECT COUNT(c.id) cnt, c.blog_id FROM Blogger\BlogBundle\Entity\Comment AS c WHERE c.blog_id IN (?1) GROUP BY c.blog_id получается массив вида:


Код:
array:4 [▼
  0 => array:2 [▼
    "cnt" => "4"
    "blog_id" => 11
  ]
  1 => array:2 [▼
    "cnt" => "10"
    "blog_id" => 12
  ]
  2 => array:2 [▶]
  3 => array:2 [▶]
]
где blog_id возвращается в виде int, так как в классе сущности Comment указано свойство blog_id integer.
а вот cnt в строковом типе, так как в сущности Comment его нет.

8. можно ли как-то указать тип возвращаемого значения без указания этого свойства в классе сущности?
 

AmdY

Пью пиво
Команда форума
Вроде такой запрос должен быть, я добавил группировку с выборку count
PHP:
$qb = $this->createQueryBuilder('b')
                   ->select('b')
                   ->leftJoin('b.comments', 'c')
                   ->addSelect('COUNT(c.id) AS commentCount')
                   ->addOrderBy('b.created', 'DESC')
                   ->groupBy('c.id');
Вместо должны имён нужно обязательно использовать class это спасёт при рефаторинге
PHP:
use Blogger\BlogBundle\Entity\Comment;
 '....' . Comment::class . '....'
Ну и в реальном проекте, лучше расчёт каунта вынести в отдельный репозиторий, а не джойнить. Как правило каунтеры либо храняться в мемкеше или отдельном поле уже посчитанные.
 

bars80081

Новичок
спасибо за ответ. решил попробовать как надо в реальном проекте через репозиторий. получилось следующее (и даже работает):

BlogRepository.php
PHP:
class BlogRepository extends \Doctrine\ORM\EntityRepository
{
    public function getLatestBlogs($limit = null)
    {
        $qb = $this->createQueryBuilder('b')
                   ->select('b')
                   ->addOrderBy('b.created', 'DESC');

        if(false === is_null($limit)) {
            $qb->setMaxResults($limit);
        }

        $result = $qb->getQuery()->getResult();
        
        $idis = $com = array();
        foreach($result as $key => $barr) {
            $id = $barr->getId();
            $idis[] = $id;
            $com[$id] = $key;
        }
        
        if($idis) {
            $counts = $this->_em->getRepository('BloggerBlogBundle:CommentCount')->getCountByBlogs($idis);
            foreach($counts as $key => $val) {
                $result[$com[$key]]->setCommentsCount($val);
            }
        }        
        return $result;
    }
CommentCountRepository.php
PHP:
class CommentCountRepository extends \Doctrine\ORM\EntityRepository
{
    public function getCountByBlogs($blog_id)
    {
        if(empty($blog_id)) { return array(); }
        
        if(!is_array($blog_id)) {
            $blog_id = array($blog_id);
        }
        $data = $qr = array();
        if(!empty($blog_id)) {
            $blog_id = array_unique($blog_id);
        
            $qc = $this->createQueryBuilder('c')
                   ->select('COUNT(c.id) AS cnt, c.blog_id')
                   ->where('c.blog_id IN (:barr)')
                   ->groupBy('c.blog_id')
                   ->setParameter('barr', $blog_id);

            $qr = $qc->getQuery()->getResult();
        }
        foreach($qr as $v) {
            $data[$v['blog_id']] = intval($v['cnt']);
        }
        foreach($blog_id as $key) {
            $key = intval($key);
            if(!isset($data[$key])) { $data[$key] = 0; }
        }
        
        return $data;
    }
}
и ещё для поддержки сущность CommentCount:
PHP:
namespace Blogger\BlogBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Constraints\NotBlank;

/**
 * @ORM\Entity(repositoryClass="Blogger\BlogBundle\Entity\Repository\CommentCountRepository")
 * @ORM\Table(name="comment")
 * @ORM\HasLifecycleCallbacks
 */
class CommentCount
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="integer")
     */
    protected $blog_id;
    public function __construct() {}
}
плохо, конечно, что пришлось создавать лишнюю сущность, так как она всё-таки почти комментарий

теперь буду осмысливать, насколько полезно такие огороды городить
 
Сверху