Предположим, что у меня есть объекты Product объекты Review прикрепленные к продуктам.  Можно ли прикрепить поля к объекту Product на основе некоторого результата, возвращаемого SQL-запросом?  Подобно прикреплению поля ReviewsCount равного COUNT(Reviews.ID) as ReviewsCount . 
Я знаю, что это можно сделать в такой функции, как
 public function getReviewsCount() { return count($this->Reviews); } 
  Но я хочу сделать это с помощью SQL, чтобы свести к минимуму количество запросов к базе данных и повысить производительность, так как обычно мне может не понадобиться загружать сотни отзывов, но все равно нужно знать номер.  Я думаю, что запуск COUNT SQL будет намного быстрее, чем через 100 продуктов и вычисление 100 отзывов для каждого.  Более того, это просто пример, на практике мне нужны более сложные функции, и я думаю, что MySQL будет работать быстрее.  Поправьте меня если я ошибаюсь. 
Вы можете сопоставить результат одного столбца с полем сущности – посмотрите на собственные запросы и ResultSetMapping для достижения этого. В качестве простого примера:
 use Doctrine\ORM\Query\ResultSetMapping; $sql = ' SELECT p.*, COUNT(r.id) FROM products p LEFT JOIN reviews r ON p.id = r.product_id '; $rsm = new ResultSetMapping; $rsm->addEntityResult('AppBundle\Entity\Product', 'p'); $rsm->addFieldResult('p', 'COUNT(id)', 'reviewsCount'); $query = $this->getEntityManager()->createNativeQuery($sql, $rsm); $results = $query->getResult(); 
  Затем в вашем объекте Product у вас будет поле $reviewsCount и счет будет сопоставлен с этим.  Обратите внимание, что это будет работать только в том случае, если у вас есть столбец, определенный в мясной таблице Doctrine, например: 
 /** * @ORM\Column(type="integer") */ private $reviewsCount; public function getReviewsCount() { return $this->reviewsCount; } 
  Это то, что предлагает документация Aggregate Fields Doctrine.  Проблема заключается в том, что вы, по сути дела, делаете Doctrine, reviews_count , что у вас есть другой столбец в вашей базе данных, называемый reviews_count , чего вы не хотите.  Таким образом, это будет работать без физического добавления этого столбца, но если вы когда-нибудь запустите doctrine:schema:update она добавит этот столбец для вас.  К сожалению, Doctrine действительно не разрешает виртуальные свойства, поэтому другим решением было бы написать собственный пользовательский гидратор или, возможно, подписаться на событие loadClassMetadata и вручную добавить отображение самостоятельно после загрузки вашей конкретной сущности (или сущностей). 
  Обратите внимание, что если вы делаете что-то вроде COUNT(r.id) AS reviewsCount вы больше не сможете использовать COUNT(id) в своей функции addFieldResult() и вместо этого должны использовать alias reviewsCount для этого второго параметра. 
  Вы также можете использовать ResultSetMappingBuilder в качестве начала использования сопоставления набора результатов. 
Мое фактическое предложение состоит в том, чтобы сделать это вручную вместо того, чтобы проходить через все эти дополнительные вещи. По существу создайте обычный запрос, который возвращает как вашу сущность, так и скалярные результаты в массив, затем установите скалярный результат в соответствующее, неотображенное поле на вашей сущности и верните объект.
После подробного расследования я обнаружил, что есть несколько способов сделать что-то близкое к тому, что я хотел, включая перечисленные в других ответах, но все они имеют некоторые минусы. Наконец, я решил использовать CustomHydrators . Кажется, что свойства, не управляемые с помощью ORM, не могут быть сопоставлены с ResultSetMapping как поля, но могут быть получены как скаляры и привязаны к объекту вручную (так как PHP позволяет привязывать свойства объекта на лету). Однако результат, полученный вами от доктрины, остается в кеше. Это означает, что свойства, установленные таким образом, могут быть сброшены, если вы сделаете другой запрос, который также будет содержать эти сущности.
Другой способ сделать это – добавить это поле непосредственно в кэш метаданных доктрины. Я попытался сделать это в CustomHydrator:
 protected function getClassMetadata($className) { if ( ! isset($this->_metadataCache[$className])) { $this->_metadataCache[$className] = $this->_em->getClassMetadata($className); if ($className === "SomeBundle\Entity\Product") { $this->insertField($className, "ReviewsCount"); } } return $this->_metadataCache[$className]; } protected function insertField($className, $fieldName) { $this->_metadataCache[$className]->fieldMappings[$fieldName] = ["fieldName" => $fieldName, "type" => "text", "scale" => 0, "length" => null, "unique" => false, "nullable" => true, "precision" => 0]; $this->_metadataCache[$className]->reflFields[$fieldName] = new \ReflectionProperty($className, $fieldName); return $this->_metadataCache[$className]; } 
Однако этот метод также имел проблемы с сбросом свойств объектов. Итак, моим окончательным решением было просто использовать stdClass для получения той же структуры, но не управляемой доктриной:
 namespace SomeBundle; use PDO; use Doctrine\ORM\Query\ResultSetMapping; class CustomHydrator extends \Doctrine\ORM\Internal\Hydration\ObjectHydrator { public function hydrateAll($stmt, $resultSetMapping, array $hints = array()) { $data = $stmt->fetchAll(PDO::FETCH_ASSOC); $result = []; foreach($resultSetMapping->entityMappings as $root => $something) { $rootIDField = $this->getIDFieldName($root, $resultSetMapping); foreach($data as $row) { $key = $this->findEntityByID($result, $row[$rootIDField]); if ($key === null) { $result[] = new \stdClass(); end($result); $key = key($result); } foreach ($row as $column => $field) if (isset($resultSetMapping->columnOwnerMap[$column])) $this->attach($result[$key], $field, $this->getPath($root, $resultSetMapping, $column)); } } return $result; } private function getIDFieldName($entityAlias, ResultSetMapping $rsm) { foreach ($rsm->fieldMappings as $key => $field) if ($field === 'ID' && $rsm->columnOwnerMap[$key] === $entityAlias) return $key; return null; } private function findEntityByID($array, $ID) { foreach($array as $index => $entity) if (isset($entity->ID) && $entity->ID === $ID) return $index; return null; } private function getPath($root, ResultSetMapping $rsm, $column) { $path = [$rsm->fieldMappings[$column]]; if ($rsm->columnOwnerMap[$column] !== $root) array_splice($path, 0, 0, $this->getParent($root, $rsm, $rsm->columnOwnerMap[$column])); return $path; } private function getParent($root, ResultSetMapping $rsm, $entityAlias) { $path = []; if (isset($rsm->parentAliasMap[$entityAlias])) { $path[] = $rsm->relationMap[$entityAlias]; array_splice($path, 0, 0, $this->getParent($root, $rsm, array_search($rsm->parentAliasMap[$entityAlias], $rsm->relationMap))); } return $path; } private function attach($object, $field, $place) { if (count($place) > 1) { $prop = $place[0]; array_splice($place, 0, 1); if (!isset($object->{$prop})) $object->{$prop} = new \stdClass(); $this->attach($object->{$prop}, $field, $place); } else { $prop = $place[0]; $object->{$prop} = $field; } } } 
С помощью этого класса вы можете получить любую структуру и прикрепить любые объекты, как вам нравится:
 $sql = ' SELECT p.*, COUNT(r.id) FROM products p LEFT JOIN reviews r ON p.id = r.product_id '; $em = $this->getDoctrine()->getManager(); $rsm = new ResultSetMapping(); $rsm->addEntityResult('SomeBundle\Entity\Product', 'p'); $rsm->addFieldResult('p', 'COUNT(id)', 'reviewsCount'); $query = $em->createNativeQuery($sql, $rsm); $em->getConfiguration()->addCustomHydrationMode('CustomHydrator', 'SomeBundle\CustomHydrator'); $results = $query->getResult('CustomHydrator'); 
Надеюсь, что это может помочь кому-то 🙂
  Да, возможно, вам нужно использовать QueryBuilder для достижения этого: 
 $result = $em->getRepository('AppBundle:Product') ->createQueryBuilder('p') ->select('p, count(r.id) as countResult') ->leftJoin('p.Review', 'r') ->groupBy('r.id') ->getQuery() ->getArrayResult(); 
и теперь вы можете сделать что-то вроде:
 foreach ($result as $row) { echo $row['countResult']; echo $row['anyOtherProductField']; } 
Если вы используете Doctrine 2.1+, подумайте об использовании ассоциаций EXTRA_LAZY :
Они позволяют реализовать такой метод, как ваш, в вашей сущности, делая прямой подсчет в ассоциации вместо того, чтобы извлекать все объекты в нем:
 /** * @ORM\OneToMany(targetEntity="Review", mappedBy="Product" fetch="EXTRA_LAZY") */ private $Reviews; public function getReviewsCount() { return $this->Reviews->count(); }