OpenStats - logování informací
Pokud vás projekt OpenStats zaujal a nainstalovali jste si ho (viz. instalace a konfigurace), není nic jednoduššího než si vyzkoušet zalogování několika sessions, akcí a parametrů ;-)
Logovací knihovna je tvořena jedinou třídou - OpenStats_Logging, využívající knihovnu OpenStats_DB (a související třídu OpenStats_DB_ResultSet) která pouze zapouzdřuje přístup k databázi a je společná pro všechny části projektu OpenStats.
Podívejme se tedy na jednotlivé třídy - v následujícím textu naleznete pouze stručný popis jednotlivých metod, budete-li potřebovat podrobnější informace, nahlédněte přímo do komentářů v knihovně.
OpenStats_DB
Vzhledem k tomu že projekt OpenStats vznikl ještě před tím než se v PHP objevilo PDO, napsali jsme si vlastní vrstvu která měla za úkol odstiňovat aplikační kód od volání databáze - součástí této vrstvy je také následující třída (OpenStats_DB_ResultSet).
Tato vrstva není zodpovědná za vytváření nebo uzavírání DB spojení - jen a pouze provádí DB dotazy, a případně vytváří a uzavírá transakce.
Součástí projektu je pouze PostgreSQL varianta této vrstvy, nicméně díky tomuto rozdělení je relativně jednoduché portovat projekt i na jiné databáze (například MySQL).
Veřejné metody třídy jsou:
- __construct($connection) - konstruktor, parametrem je spojení na databázi
- query($query) - spustí dotaz a výsledek vrátí zabalený jako OpenStats_DB_ResultSet
- execute($query) - spustí dotaz, a vrátí pouze počet upravených řádek
- begin() - vytvoří novou transakci
- commit() - potvrdí transakci
- rollback() - zruší transakci
- escape($value) - ošetří hodnotu pro použití v SQL dotazu
- getLastId($sequence) - vrátí poslední použitou hodnotu ze sekvence
- getNextId($sequence) - vrátí následující hodnotu ze sekvence
Varování: Tato knihovna sice do jisté míry odstiňuje aplikační kód od vlastního volání databáze, v žádném případě se však nejedná o odstínění kompletní. Nelze očekávat že pro portování na jinou databázi (např. MySQL) stačí přepsat pouze tuto třídu - stále existuje mnoho implementačních specifik která tato třída neřeší. Mezi hlavní patří rozdíly v podporovaných kontrukcích SQL (a jejich optimálnosti na různých databázích - co běží rychle na jedné databázi může na jiné běžet pomalu), práci se sekvencemi (např. MySQL namísto nich používá AUTOINCREMENT), apod.
Note: One of the possibilities provided by this encapsulation is logging of SQL queries along with their duration - see the PGMon project.
OpenStats_DB_ResultSet
Tato třída zapouzdřuje výsledky dotazů (SELECT) jako iterátor. Díky tomu není aplikační kód závislý na konkrétní databázi a lze ho tak jednoduše portovat i na jiné databáze.
Veřejné metody třídy jsou:
- __construct($result) - konstruktor, obalí výsledek dotazu
- hasNext() - je ve výsledku ještě alespoň jeden řádek
- fetchNext($mapper = null) - vrátí další řádek z výsledku (případně objekt vytvořený mapperem)
- free() - uvolní výsledek z paměti
OpenStats_Logging
Tato knihovna je zodpovědná za samotné samotné logování statistik - vytváření sezení (sessions), akcí náležících do těchto sezení, a parametrů pro jednotlivé akce.
Ve svém aplikačním kódu budete aktivně pracovat pouze s touto třídou (pro předchozí dvě třídy budete pouze vytvářet instance).
Veřejné metody třídy jsou:
- __construct($db) - konstruktor, parametrem je instance třídy OpenStats_DB
- getVisitorId() - vrátí náhodně vygenerované ID návštěvníka
- createSession($visitorId, $ip, $forwardedFor, $referer, $userAgent, $language) - vytvoří session
- createAction($sessionId, $language, $typeId, $pageId, $parameters = null) - vytvoří akci
- createParameter($sessionId, $actionId, $name, $value) - vytvoří parametr
- createParameters($sessionId, $actionId, $parameters) - vytvoří několik parametrů najednou
Základní použití
Následující kus kódu předvádí vytvoření potřebných instancí tříd, vytvoření nové session, vytvoření jedné akce v rámci session, a následně zalogování několika parametrů pro tuto akci:
<?php
// načtení potřebných knihoven
require_once('openstats/openstats_logging.php');
// spuštění session
session_start();
// definice užitečných konstant
define('TYPE_PAGEVIEW', 1);
define('PAGE_HOMEPAGE', 1);
// inicializace proměnných
$visitorId = null;
$ip = $_SERVER['REMOTE_ADDR'];
$forwardedFor = $_SERVER['HTTP_X_FORWARDED_FOR'];
$referer = $_SERVER['HTTP_REFERER'];
$userAgent = $_SERVER['HTTP_USER_AGENT'];
$language = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
// vytvoření instance DB vrstvy
$conn = pg_open(...);
$db = new OpenStats_DB($conn);
// vytvoření instance logovací knihovny
$stats = new OpenStats_Logging($db);
// načtení předchozí hodnoty visitorId z COOKIE
if (isset($_COOKIE['visitorId'])) {
$visitorId = $_COOKIE['visitorId'];
} else {
$visitorId = $stats->getVisitorId();
setcookie('visitorId', $visitorId, time() + 31*24*60*60);
}
// zalogování session (pokud je to třeba)
if (! isset($_SESSION['sessionId'])) {
$sessionId = $stats->createSession($visitorId, $ip, $forwardedFor,
$referer, $userAgent, $language);
$_SESSION['sessionId'] = $sessionId;
} else {
$sessionId = $_SESSION['sessionId'];
}
// zalogování akce
$actionId = $stats->createAction($sessionId, 'cs',
TYPE_PAGEVIEW, PAGE_HOMEPAGE);
// zalogování parametru
$parameterId = $stats->createParameter($sessionId, $actionId,
'myParameter', 'my value');
?>
Tento příklad poměrně dobře ilustruje použití logovací knihovny - věřím že nebude příliš obtížné přizpůsobit ho vašim potřebám. V zájmu stručnosti jsem si však dovolil několik zjednodušení, která bych v následujícím odstavci rád uvedl na pravou míru.
Několik doporučení
Zaprvé některé proměnné načítané z pole $_SERVER (zejména se to týká HTTP_ proměnných) nemusí být definovány, a tudíž se v logu (nebo jako součást HTML výstupu pokud nedej bože nemáte definován vlastní error handler) může objevit hláška o jejich neexistenci. Nejedná se sice o fatální chybu - kód bude fungovat - ale je to přinejmenším otravné.
Lepší ošetření této situace by tak mohlo vypadat například následovně:
<?php $referer = (isset($_SERVER['HTTP_REFERER'])) ? $_SERVER['HTTP_REFERER'] : null; ?>
Zadruhé je vhodné lépe ošetřit hodnotu načtenou z cookie - výše uvedený kód je zranitelný, neboť pro útočníka není obtížné podstrčit libovolnou hodnotu. Sice nezpůsobí žádnou výraznou škodu - při nejhorším poněkud zmate detekci unikátních návštěvníků - ale proč to neošetřit, že?
Jednou z možností je neposílat pouze ID návštěvníka, ale doplnit ho o hodnotu která znemožní jeho úpravu, například hash s využitím salt hodnoty (a tuto hodnotu při načítání zkontrolovat):
<?php
// definice salt hodnoty (tajná hodnota)
define('SALT', '3s8f9df');
// načtení předchozí hodnoty visitorId z COOKIE
if (isset($_COOKIE['visitorId'])) {
$tmp = explode(':', $_COOKIE['visitorId]);
if ($tmp[1] == md5(SALT . $tmp[0]) {
$visitorId = $tmp[0];
}
}
// pokud ID návštěvníka není načteno, vygeneruj nové
if (! isset($_COOKIE['visitorId'])) {
$visitorId = $stats->getVisitorId();
$cookieValue = $visitorId . ':' . md5(SALT . $visitorId);
setcookie('visitorId', $cookieValue, time() + 31*24*60*60);
}
?>
Další možností je šifrovat hodnotu (ale i tak je třeba kontrolovat zda hodnota nebyla změněna "naslepo").
A poslední doporučení - při volání metody createAction nepoužívejte přímo numerické hodnoty, neboť je velmi obtížné se v takto zadaných hodnotách zorientovat. Raději v samostatném souboru definujte konstanty pro stránky a typy akcí, a následně používejte tyto konstanty.




