Вот что я сделал, чтобы решить эту проблему без дополнительных запросов:
Проблема
Необходимо добавить пользовательское поле COUNT в типичный набор результатов, используемый с пейджером Symfony. Однако, как мы знаем, Propel не поддерживает это из коробки. Поэтому простое решение - просто сделать что-то подобное в шаблоне:
foreach ($pager->getResults() as $project):
echo $project->getName() . ' and ' . $project->getNumMembers()
endforeach;
Где getNumMembers()
запускает отдельный запрос COUNT для каждого объекта $project
. Конечно, мы знаем, что это крайне неэффективно, потому что вы можете выполнить COUNT на лету, добавив его в качестве столбца к исходному запросу SELECT, сохранив запрос для каждого отображаемого результата.
У меня было несколько разных страниц, на которых отображался этот набор результатов, и все они использовали разные критерии. Поэтому написание собственной строки SQL-запроса напрямую с помощью PDO было бы слишком хлопотным занятием, так как мне пришлось бы влезать в объект Criteria и возиться с попытками сформировать строку запроса на основе того, что в ней было!
Итак, то, что я сделал в итоге, позволяет избежать всего этого, позволяя нативному коду Propel работать с критериями и создавать SQL как обычно.
1 - Сначала создайте эквивалентные методы доступа / мутатора [get / set] NumMembers () в модельном объекте, который возвращается методом doSelect (). Помните, что метод доступа больше не выполняет запрос COUNT, он просто хранит его значение.
2 - Перейдите в класс равноправного узла и переопределите родительский метод doSelect () и скопируйте весь код из него в точности так, как он есть
3 - Удалите этот бит, потому что getMixerPreSelectHook является закрытым методом базового однорангового узла (или скопируйте его в свой одноранговый узел, если он вам нужен):
// symfony_behaviors behavior
foreach (sfMixer::getCallables(self::getMixerPreSelectHook(__FUNCTION__)) as $sf_hook)
{
call_user_func($sf_hook, 'BaseTsProjectPeer', $criteria, $con);
}
4 - Теперь добавьте свое пользовательское поле COUNT в метод doSelect в вашем классе пира:
// copied into ProjectPeer - overrides BaseProjectPeer::doSelectJoinUser()
public static function doSelectJoinUser(Criteria $criteria, ...)
{
// copied from parent method, along with everything else
ProjectPeer::addSelectColumns($criteria);
$startcol = (ProjectPeer::NUM_COLUMNS - ProjectPeer::NUM_LAZY_LOAD_COLUMNS);
UserPeer::addSelectColumns($criteria);
// now add our custom COUNT column after all other columns have been added
// so as to not screw up Propel's position matching system when hydrating
// the Project and User objects.
$criteria->addSelectColumn('COUNT(' . ProjectMemberPeer::ID . ')');
// now add the GROUP BY clause to count members by project
$criteria->addGroupByColumn(self::ID);
// more parent code
...
// until we get to this bit inside the hydrating loop:
$obj1 = new $cls();
$obj1->hydrate($row);
// AND...hydrate our custom COUNT property (the last column)
$obj1->setNumMembers($row[count($row) - 1]);
// more code copied from parent
...
return $results;
}
Вот и все. Теперь у вас есть дополнительное поле COUNT, добавленное к вашему объекту, без выполнения отдельного запроса, чтобы получить его, когда вы выплевываете результаты. Единственным недостатком этого решения является то, что вам пришлось скопировать весь родительский код, потому что вам нужно добавить биты прямо в его середине. Но в моей ситуации это казалось небольшим компромиссом - сохранить все эти запросы, а не писать собственную строку запроса SQL.