Создаем статистику для сайта своими руками
17-02-2022Reading time ~ 6 min.Albireo Framework / CMS, SQL 480
Сегодня я расскажу как создать статистику для своего сайта на Albireo Framework. Для хранения статистики будет использоваться база SQLite. Я покажу базовый пример, который вы сможете расширить под свою задачу. Лично мне нужна была статистика по страницам с группировкой по дате (посуточно).
Общая схема работы
В момент загрузки любой страницы, выполняется код, который вносит в базу текущий адрес и дату. При желании вы можете также сохранять IP, браузер и любые другие параметры из $_SERVER
, например referer
, чтобы узнать откуда был переход. То есть в базе можно хранить любые данные.
Для просмотра статистики делается отдельная страница (произвольно), где формируется выборка из базы. У меня стоит группировка по датам и адресам, но если её убрать, то можно посмотреть любой URL. Если нужно сразу несколько отчётов, то можно сделать ещё одну страницу, где сформировать нужную выборку.
Поскольку данных будет много, то должна быть пагинация страниц. У нас это простой get-параметр вида адрес?page=N
, где N
— номер страницы пагинации.
Сбор статистики
Поскольку статистику нужно собирать для всех страниц, то код разместим в отдельном файле: albireo-data/my/stat.php
и подключим в конфигурации: albireo-data/config/config.php
в ключе functions
:
return [ ... // файл для всех страниц 'functions' => DATA_DIR . 'my/stat.php', ];
Albireo этот файл подключает через обычный require
, поэтому в нём можно разместить как отдельную функцию, или сразу нужный код сбора статистики.
Файл stat.php
Вначале проверяем, что у нас не 404-страница, поскольку нет смысла их отслеживать. После этого мы получаем данные текущей страницы. Это массив и в нём мы смотрим ключ stat
, если он равен «-», то это значит, что статистику для страницы сохранять не нужно.
После этого определяем текущий URL относительно корня сайта. Если адрес пустой, значит это главная, вместо этого будем использовать символ «/».
Теперь у нас есть возможность внести данные в базу. Для этого создаём PDO-соединение и выполняем запрос по созданию таблицы с проверкой, что её нет.
В конце просто добавляем данные с помощью SQL.
Вот полный код файла с комментариями.
<?php if (!defined('BASE_DIR')) exit('No direct script access allowed'); if (getVal('is404', false)) return; // не учитываем 404-страницы $pageData = getVal('pageData'); // данные текущей страницы if (isset($pageData['stat']) and $pageData['stat'] == '-') return; // если статистика отключена $currentUrl = getVal('currentUrl'); $url = $currentUrl['url']; // текущий url if ($url === '') $url = '/'; // главная $pdo = Pdo\PdoConnect::getInstance(); // PDO-подключение $db = $pdo->connect([ 'dsn' => 'sqlite:' . DATA_DIR . 'storage' . DIRECTORY_SEPARATOR . 'stat.sqlite' ]); if (empty($db)) { return; // выходим, поскольку нет возможности работы с базой } // создаём таблицу, если её ещё нет Pdo\PdoQuery::query($db, " CREATE TABLE IF NOT EXISTS stat ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, date TEXT NOT NULL DEFAULT '', url TEXT NOT NULL DEFAULT '' ); "); // добавляем данные Pdo\PdoQuery::insert($db, 'stat', [ 'date' => date("Y-m-d"), 'url' => $url, ]); # end of file
Обратите внимание на размещение sqlite-файла в каталоге storage
. Это стандартное размещение для Albireo, но если по какой-то причине возникает ошибка, то возможно у этого каталога нет прав на запись. Вручную укажите «777».
Пробуем обновить страницы сайта, должен появиться файл stat.sqlite
. Статистика собирается, подумаем как её вывести.
Вывод статистики
Для вывода будем использовать любую обычную страницу Albireo. Я разместил её по адресу stat
. Параметры страницы у меня такие:
title: Статистика description: Статистика сайта slug: stat sitemap: - stat: - head[]: <meta name="robots" content="noindex, nofollow">
Ключом sitemap
мы отключаем добавление страницы в xml-карту сайта. Ключ stat
отключаем сбор статистики этой страницы. А для того, чтобы страница не индексировалась поисковиками прописываем «noindex» для секции HEAD.
Вначале нужно получить доступ к базе данных.
$pdo = Pdo\PdoConnect::getInstance(); $db = $pdo->connect([ 'dsn' => 'sqlite:' . DATA_DIR . 'storage' . DIRECTORY_SEPARATOR . 'stat.sqlite' ]); if (empty($db)) { echo '<div class="t-red600 t-center mar10">Ошибка соединения с БД</div>'; return; // выходим, поскольку нет возможности работы с базой }
Получить выборку из базы мы должны с помощью пагинации вида адрес?page=7
. То есть в итоговом SQL-запросе будут использоваться LIMIT
и OFFSET
. Чтобы их корректно сформировать нам потребуется узнать
- текущую страницу пагинации,
- число записей на одну страницу пагинации,
- общее количество записей в таблице.
Текущая пагинация у нас хранится в get-параметре page
— получим его из данных страницы:
$currentUrl = getVal('currentUrl'); // текущий адрес $current = (int) ($currentUrl['queryData']['page'] ?? 1); // текущая страница пагинации if ($current < 1) $current = 1; // исправим, если нужно
Количество записей на страницу зададим жестко, например 40:
$limit = 40; // записей на одну страницу пагинации
Теперь нам нужно определить общее число записей в таблице, как будто бы мы не используем LIMIT
и OFFSET
. Проблема в том, что мы будем использовать sql-запрос с группировкой записей, поэтому мы получим не всего-всего записей в таблице, а именно сгруппированные записи. Чтобы было понятно, давайте рассмотрим sql-запрос с LIMIT
и OFFSET
.
$rows = Pdo\PdoQuery::fetchAll($db, ' SELECT date, url, count(url) as count FROM stat GROUP BY date, url ORDER BY date DESC, count DESC LIMIT :limit OFFSET :offset', [ ':limit' => $pag['limit'], ':offset' => $pag['offset'] ]);
Обратите внимание, что записи группируются по дате и URL — это позволяет сделать подсчёт количества посещений данного URL по дате. Число строк такого запроса будет меньше, чем обычным
$rows = Pdo\PdoQuery::fetchAll($db, 'SELECT * FROM stat');
Поэтому при подсчёте данных для пагинации, нам нужно узнать количество строк с учётом точно такой же группировки. Поэтому мы будем использовать основной sql-запрос, только уберём получение ненужных данных:
$allRows = Pdo\PdoQuery::fetchAll($db, 'SELECT date, url, count(url) as count FROM stat GROUP BY date, url');
От $allRows
нам нужно только количество записей. Теперь мы можем получить массив пагинации:
$pag = Pdo\PdoQuery::getPagination($db, 'stat', $limit, $current, count($allRows)); // массив данных
И используем их в основном запросе (продублирую код):
$rows = Pdo\PdoQuery::fetchAll($db, 'SELECT date, url, count(url) as count FROM stat GROUP BY date, url ORDER BY date DESC, count DESC LIMIT :limit OFFSET :offset', [':limit' => $pag['limit'], ':offset' => $pag['offset']]);
Теперь в массиве $rows
данные для вывода. Вы можете написать свой цикл обхода, я же использую табличный вывод:
echo Pdo\PdoQuery::outTableRows($rows);
Естественно, может потребоваться указать какое-то своё оформление. Поэтому обрамляющий HTML будет у вас свой.
Вывод пагинации
Все данные для пагинации у нас в массиве $pag
. По сути пагинация — это блок ссылок на все доступные страницы. Поэтому можно их вывести в простом цикле for
. Если это первая страница, то можно get-параметр не указывать. Также, если это текущая страница пагинации, то можно её оформить не ссылкой, а элементом SPAN
. Ну и само оформление может быть произвольным.
// блок ссылок для пагинации if ($pag['max'] > 1) { echo '<div class="mar30-tb">'; for ($i = 1; $i <= $pag['max']; $i++) { if ($i > 1) $queryUrl = $currentUrl['urlFull'] . '?page=' . $i; else $queryUrl = $currentUrl['urlFull']; if ($i == $current) echo '<span class="b-inline pad10-rl pad5-tb mar5-r mar10-b bg-teal600 t-white" style="cursor: default">' . $i . '</span>'; else echo '<a class="b-inline pad10-rl pad5-tb mar5-r mar10-b hover-no-underline bg-teal100 hover-bg-teal700 hover-t-teal50" href="' . $queryUrl . '">' . $i . '</a> '; } echo '</div>'; }
Вообще вариантов пагинации может быть множество. Если страниц будет очень много, то возможно более удобным будет сделать выпадающий SELECT с номером страницы. Для перехода можно прицепить несложный js-скрипт. Или сделать только две ссылки «Назад-Вперёд». Во всех случаях информации из $pag
будет достаточно.