Метрическая аналитика
У Яндекса есть полезная система статистики, похожая частью на Google Analytics, частью — на счетчик Liveinternet. Называется Яндекс.Метрика.

Яндекс.Метрика
Все как обычно — устанавливаете код на страницы своего сайта и наблюдаете статистику по посетителям. В отличие от GA, которая обновляется раз в сутки, Метрика показывает результаты практически в реальном времени, как Liveinternet — это гораздо удобнее.
Если сайт рекламируется с помощью Яндекс.Директ и на рекламируемом сайте установлен код для сбора статистики для Директа — это и есть код Метрики! В этом случае можно уже не напрягаться, а смотреть отчеты.
UPDATE нельзя INSERT
Задача очень простая: обновить запись в таблице, а если подходящей записи нет, добавить новую. Кажется вполне тривиальной задачей. Особенно, если условия, по которым выбирается запись для обновления сделать первичным или уникальным ключом.
Эта тема пару месяцев назад обсуждалась в ЖЖ сообществе ru_php, но как-то вяло. Попробую этим небольшим постом обобщить разные методики.
Самый простой метод: сначала SELECT COUNT(*), а потом, по результатам, либо INSERT, либо UPDATE. Если речь идет об обновлении одной записи на этом методе можно остановиться.
Увы, если надо вставить много записей, этот метод начинает изрядно тормозить. Для такого случая у MySQL есть несколько разных методов.
REPLACE — наиболее очевидный и самый неудобный. Сервер пытается вставить новую строку, если возникает ошибка с повторяющимся уникальным ключом, запись удаляется и потом снова добавляется новая. Увы, при этом значения тех полей, которые обновлять не надо сбрасываются в дефолтные. Использовать эту команду можно, если обновляется полностью вся запись, что случается редко. Например, если в таблице есть колонка `created`, содержащая дату создания записи, то REPLACE уже не подходит. Кроме того, в случае, если большинство записей заменяется, а не добавляется, получается конструкция INSERT-(ошибка)-DELETE-INSERT — три запроса!
INSERT … ON DUPLICATE KEY UPDATE … — этот вариант сильно интереснее. Сервер пытается вставить запись, если получает ошибку c дублирующимся ключом, обновляет поля, указанные в после UPDATE. Работает отлично, в документации сказано, что эта конструкция специально задумана для вставки множества записей. Но пара неприятных моментов есть.
- Первый, как мне казалось, довольно экзотичный, но я с ним все-таки столкнулся. В конструкции INSERT можно в качестве источника данных использовать подзапрос SELECT. Но, в случае с INSERT … ON DUPLICATE KEY UPDATE, в подзапросе не должно быть группировки GROUP BY.
- Второй момент для многих неважен. Если записей для обновления сильно больше чем новых, то получается все равно много лишних обращений к БД — ведь вся конструкция это сочетание INSERT-(ошибка)-UPDATE. Лучше, чем с REPLACE, но все же…
Долгое время я пользовался UPDATE, а потом проверял mysql_affected_rows() (в CakePHP это $DataSource->lastAffected(). То есть проверял количество обновленных записей и, если получал 0, добавлял запись. У этого метода есть очень существенный недостаток: если среди данных для обновления встречаются две одинаковых строки, то при повторном UPDATE сервер, как самый умный, не обновляет запись. Соответственно число обновленных записей, affected rows, при построчном переборе, будет равно 0 несмотря на то, что такая строка в таблице есть. Соответственно решение о вставки новой строки будет ошибочным.
Устав мириться с ожидаемым, но неприятным поведением mysql_affected_rows() я порылся в документации на MySQL и нашел еще одну функцию: mysql_info(). Она выдает обычную строчку со статистикой последнего запроса к БД, причем для разных типов запросов строчки разные. Для UPDATE выглядит так:
Rows matched: 40 Changed: 40 Warnings: 0
Зато содержит чрезвычайно ценную информацию: количество записей, которые попадают под условие для обновления. Это те цифры, что после слова ‘matched:‘. Ну, регулярное выражение для получения циферок из строки сами напишите? ;-)
P.S. У меня ощущение deja vu — мне кажется, что я уже писал похожий пост. Перерыл дневники — нет такого. Или это черновик был…
Поиск по дате
Вот уж не подумал бы, что на совершенно тривиальных запросах к MySQL можно наступить на детские грабли.
Я не люблю обновлять MySQL ни на своей машине, ни на сервере. Каждый раз гадаешь: “понадобится бэкап или или нет?” Поэтому имеет место некоторая несогласованность версий, на которую я не слишком сильно обращал внимание. До сегодняшнего вечера. Итак у меня в наличии на домашней машине MySQL 5.1.22-rc-community, win32. На сервере соответственно MySQL 5.0.45 из бокса CentOS 5.
Самая обычная таблица в БД, с самым обычным полем created типа DATETIME.
Однако запрос
SELECT COUNT(*) AS `count` FROM `offers` AS `Offer` WHERE `Offer`.`supplier_id` = 3 AND DATE(`Offer`.`created`) = '2009-03-12'
работает на них совершенно по-разному!
На домашней машине все, как ожидается, возвращается количество записей за 12-е число. На сервере — 0 записей. Читать полностью »
Производительность pagination в CakePHP 1.2
Хотя использование разбивки на страницы (pagination) вполне заслуживает отдельного, обстоятельного поста, хочу остановиться лишь на паре не слишком очевидных моментов. Я предполагаю, что у читающих этот пост есть определенный навык использования CakePHP, моделей вообще и pagination в частности. Этот пост и так получается длинным.
Известно, что метод контроллера paginate() вызывает последовательно два метода модели — первый для подсчета общего числа записей, удовлетворяющих заданным условиям, и второй на выборку указанного числа записей начиная с заданной. Т.е. первый это фактически функция SQL COUNT(*), второй — SELECT … LIMIT n,m.
Именно из-за того, что вызовов методов больше одного, не срабаывает временная привязка/отвязка подчиненных моделей с помощью bindModel()/unbindModel(). Этим методам, для корректной работы с paginate() приходится передавать второй параметр, равный false. К счастью, еть ContainableBehavior, решающий эту проблему.
Эти запросы не всегда оптимальны и есть возможность улучшить производительность метода paginate() именно за счет оптимизации собственно самих запросов. Читать полностью »
Методы модели findBy и findAllBy
В документации как-то очень мельком упомянуты эти методы. Кроме того описание этих методов выглядит так:
findBy<fieldName>(string $value)
Хотя на самом деле это не совсем соответствует действительности. Для поиска одной записи по значению одного поля описание должно выглядеть так:
findBy<fieldName>(string $value, $fields=null, $order=null, $recursive=null)
и
finAlldBy<fieldName>(string $value, $fields=null, $order=null, $limit=null, $page=null, $recursive=null)
Разница есть. Лично я очень обрадовался параметру $recursive — не надо отдельно, перед вызовом метода устанавливать это значение или ображаться к Containable.
Но и это еще не все! Читать полностью »
Добавляем правила проверки данных на лету
Все в общем-то, тривиально и не стоило бы, наверное, пост городить. Но рассматривая код некоторых проектов на CakePHP я обнаружил удивительную вещь — во многих случаях данные формы проверяются не встроенным валидатором, а специально написанным кодом. Зачем? Непонятно.
Во-первых правила проверки — это просто ассоциативный массив, в который вполне можно добавлять элементы, прямо из метода контроллера. Никуда они при инициализации модели, не парсятся и не обрабатываются, пока не будет вызван валидатор.
Во-вторых валидатор отлично справляется с полями формы, которым нет соответствия в модели. А метод модели save() записывает только те поля, которым соответствуют колонки в таблице.
Вот, например, простая форма регистрации пользователя. Читать полностью »
Вечный логин
Используя встроенный в CakePHP компонент для аутентификации пользователей, AuthComponent, можно легко обеспечить возможность ввода логина и пароля до морковкиного заговения. :-)
Самым нетерпеливым раскрываю суть: запретите доступ неавторизованному пользователю к действию logout. :-)
Схема работы методов компонента простая. Если пользователь неавторизован, то URL, который он запрашивает сохраняется в сессии, а сам пользователь перенаправляется на страницу авторизации. При выходе (вызов метода logout) — все наоборот: запоминается referer, данные о пользователе убираются из сессии, он становится неавторизованным и перенаправляется обратно, на тот URL, с которого пришел.
Если неавторизованному пользователю запретить доступ к logout, при попытке обращения к этому действию, пользователь будет перенаправлен на страницу входа, наберет логин-пароль и отправится сразу на запрашиваемую им страницу с выходом, там его авторизация закончится и он снова будет отправлен на страницу входа…
С Новым, 1969-м, годом!
Это почти не прикол. В новой версии PHP 5.3 добавлен новый оператор. GOTO называетя. Я вполне серьезно, можете сами посмотреть анонс. Там английским по белому написано:
Added “jump label” operator (limited “goto”). (Dmitry, Sara)
Это, конечно, не вчера случилось, анонс от 1 августа. Просто эти длинные списки мало кто внимательно читает, но Johannes Schlüter внимание свое на эту строчку обратил.
А мы тут, понимаешь, сожалеем об отсутствии множественного наследования…
SluggableBehavior — помощник в создании ЧПУ
После выхода стабильной версии CakePHP количество постов в разных блогах, посвященных этому фреймворку, сократилось. Даже в Bakery тишина. Либо Рождество с Новым годом, либо все, засучив рукава, занялись разработкой.
В помощь неутомимым пекарям я решил рассказать об удобном расширении модели (behavior). Вещь, на мой взгляд, полезная. Работает отлично, я этим behavior пользуюсь уже почти год. Он помогает автоматически, при записи, сгенерировать slug для строки таблицы.
Вот, кстати, мне всегда было интересно, как правильно перевести на русский слово slug в этом контексте. Ярлык?
Этот behavior написал Mariano Iglesias. Это часть его проекта Cake Syrup. О другой интересной составляющей, SoftDeletableBehavior, недавно, кстати, можно прочесть здесь.
Это расширение не использует Inflector::slug() — когда оно создавалось, этого метода еще не было. Зато поддержка транслита русских букв в UTF-8 уже встроена, не без помощи Вашего покорного слуги. ;-) Читать полностью »
Индекс Яндекса
Весь декабрь мучился вопросом — есть-ли ограничение у Яндекса на количество страниц с одного сайта в индексе. Никак не попадало в индекс страниц, более чем 100 тысяч. Это был повод для некоторой грусти. Сегодня увидел, что барьер в 100 тыс. страниц преодолен. Ура!




