PHP. Клонирование объектов и ООП-метод __clone()
Сегодня столкнулся с одной интересной задачей: мне надо было дважды подсчитать количество документов по условию и с группировкой данных. Вот запрос (запрос с искажениями, сокращен для лучшей читабельности):
// Формируем запрос
$q = $modx->newQuery('modResource');
$q->select(array( 'modResource.*'));
// Добавляем группировку
$q->groupby('parent');
// Считаем
$count = $modx->getCount('modResource', $q);
// Добавляем новую группировку
$q->groupby('pagetitle');
// Опять считаем
$count = $modx->getCount('modResource', $q);
Казалось бы, все замечательно, но давайте сформируем конечный SQL-запрос после второго метода группировки:
SELECT modResource.* FROM `modx_site_content` AS `modResource` GROUP BY parent, pagetitle
И вот здесь уже и кроется проблема. Метод xPDOQuery::groupby() не замещает поле, по которому надо сгруппировать, а добавляет его. То есть вместо того, чтобы сначала посчитать с группировкой по полю parent, а потом по полю pagetitle, после второго вызова ->groupby() у нас в условии группировки появилось два поля. А нам нужно только одно.
Конечно, если у нас запрос не большой, то можно и просто второй запрос накидать. А если запрос большой? Да еще и условия всякие? А еще и лень справедливо давит… В общем неплохо было бы просто объект q2, и только после этого q2 сгруппировать по pagetitle и посчитать результат с новым условием группировки. Тогда мы посчитаем количество записей в q.
Давайте попробуем:
// Формируем запрос
$q = $modx->newQuery('modResource');
$q->select(array( 'modResource.*'));
// Выводим SQL для $q
$q->prepare();
print $q->toSQL();
print '<br /><br />';
// Копируем объект-переменную
$q2 = $q;
// Добавляем новую группировку
$q2->groupby('pagetitle');
// Выводим SQL для $q
$q2->prepare();
print $q2->toSQL();
print '<br /><br />';
// Добавляем группировку
$q->groupby('parent');
// Опять выводим SQL для $q
$q->prepare();
print $q->toSQL();
print '<br /><br />';
// Опять выводим SQL для $q2
$q2->prepare();
print $q2->toSQL();
print '<br /><br />';
То есть мы сформировали запрос, потом скопировали q2, после чего уже добавили группировку отдельно для q2. По идее мы должны для разных объектов получить различные условия группировки. Но что мы видим?
SELECT modResource.* FROM `modx_site_content` AS `modResource`
SELECT modResource.* FROM `modx_site_content` AS `modResource` GROUP BY pagetitle
SELECT modResource.* FROM `modx_site_content` AS `modResource` GROUP BY pagetitle, parent
SELECT modResource.* FROM `modx_site_content` AS `modResource` GROUP BY pagetitle, parent
То есть мы видим, что на выходе мы получили абсолютно одинаковые SQL-запросы для обоих объектов. Почему так произошло? Потому что в современном PHP при присвоении одного объекта из переменной другой переменной, объект не копируется (то есть не передается значение), а присваивается только ссылка на объект, и меняя любую из переменных, меняется единственный общий объект. В общем все почти как в Javascript. Так вот, для копирования (клонирования) объектов существует специальный операнд clone. Вот так будет выглядеть наш конечный запрос:
// Формируем запрос
$q = $modx->newQuery('modResource');
$q->select(array( 'modResource.*'));
// Выводим SQL для $q
$q->prepare();
print $q->toSQL();
print '<br /><br />';
// Копируем объект-переменную
$q2 = clone $q;
// Добавляем новую группировку
$q2->groupby('pagetitle');
// Выводим SQL для $q
$q2->prepare();
print $q2->toSQL();
print '<br /><br />';
// Добавляем группировку
$q->groupby('parent');
// Опять выводим SQL для $q
$q->prepare();
print $q->toSQL();
print '<br /><br />';
// Опять выводим SQL для $q2
$q2->prepare();
print $q2->toSQL();
print '<br /><br />';
Вот теперь мы получили уникальные SQL-запросы для каждого объекта в отдельности.
SELECT modResource.* FROM `modx_site_content` AS `modResource`
SELECT modResource.* FROM `modx_site_content` AS `modResource` GROUP BY pagetitle
SELECT modResource.* FROM `modx_site_content` AS `modResource` GROUP BY parent
SELECT modResource.* FROM `modx_site_content` AS `modResource` GROUP BY pagetitle
И теперь мы можем посчитать собственные результаты для этих запросов.
И напоследок кратко о методе __clone(); Если этот метод объявлен в классе, то он будет вызван на клонируемом объекте автоматически (именно на копии). Это может понадобиться для автоматического переопределения каких-либо свойств склонированного объекта.
Документация по clone: http://www.php.net/manual/ru/language.oop5.cloning.php