Web Design Blog / Programowanie:

Zapisywanie sesji PHP w bazie MySQL

Data publikacji: 6 maja 2019

Kiedy już uruchomimy portal napisany w PHP na wielu serwerach, będziemy musieli rozwiązać problem współdzielenia sesji. Każdy serwer PHP ma swoje osobne sesje zapisywane domyślnie w systemie plików.

Istnieje kilka technik, które pozwalają współdzielić sesje przez serwery oraz kilka obejść tego problemu. Najpopularniejsze rozwiązanie to zapamiętywanie na poziomie load balancera (tak naprawdę wykorzystujemy cookie) z jakim serwerem użytkownik łączył się wcześniej i przekierowywanie go do tego samego lub stworzenie współdzielonego zasobu sieciowego za pomocą NFS.

Problem ze współdzielonym zasobem sieciowym przechowywanym na jednej maszynie polega na tym, że wkrótce może się ona stać wąskim gardłem. Mając za sobą doświadczenia związane z utrzymywaniem bazy danych na jednej maszynie wiemy doskonale, że to krótkowzroczna strategia.

Sesje (z technicznego punktu widzenia) to cookie zapisywane po stronie serwera. Powiązanie konkretnych sesji z użytkownikiem odbywa się za pomocą standardowych cookie PHPSESSID lub za pomocą parametrów przekazywanych w adresach URL, np:

https://superdziewczyny.gov.org/najpopularniejsze.php?SESSID=abcdef123456789…

Zapisywanie sesji w bazie danych

Jedną z propozycji, która wydaje się łatwo skalowalna jest zapisywanie sesji w bazie danych MySQL. W myśl partycjonowania funkcjonalnego (czyli tutaj skalowania pionowego) może to być osobna baza danych, serwer lub grupa serwerów, bowiem danych sesyjnych nigdy nie będziemy łączyć z innymi tabelami w kwerendach MySQL.

Ponieważ korzystamy tylko z jednej tabeli pojawiają się także propozycje, aby przechowywać sesje w pamięci podręcznej typu Memcached za pomocą identycznej metody jaką przedstawiam poniżej.

PHP ma wbudowaną funkcję do zapisywania sesji w sposób zdefiniowany przez użytkownika. Argumenty tej funkcji to wywołania zwrotne (ang. callback) do kolejnych funkcji, które będą otwierać, zamykać, odczytywać zapisywać, niszczyć i czyścić sesje.

Poniższy kod to przeróbka kodu z książki PHP Essential Security autorstwa Chrisa Shifletta. W książce Pan Shiflett posługuje się kodem dla PHP 5. Przeróbka polegała na zamienieniu funkcji mysql na odpowiedniki mysqli, dodałem obiekt bazy i ostatnią instrukcję register_shutdown_function(), która była wymagana w późniejszych wersjach PHP.

Krok 1: Utworzenie tabeli do przechowywania sesji.

Potrzebujemy bardzo prostej tabeli, która będzie się składać z identyfikatora sesji, pieczątki czasu, kolumny typu tekst dla właściwej zawartości sesji i klucza głównego.

CREATE TABLE sessions
(
id varchar(32) NOT NULL,
access int(10) unsigned,
data text,
PRIMARY KEY (id)
);

Krok 2: użycie funkcji session_set_save_handler()

Na samym początku skryptu, który będzie mógł zapisać sesję w bazie danych wywołujemy najważniejszą funkcję:

session_set_save_handler('_open', '_close', '_read', '_write', '_destroy', '_clean');

Zawiera ona odwołania do poszczególnych procedur.

Krok 3: Uzupełniamy ciała funkcji aby cały zapis przebiegał w bazie danych

Otwieranie danych sesji. Tutaj łączymy się standardowo z bazą danych.

function _open(){
global $_sess_db;
$db_user = "root";
$db_pass = "haslo";
$db_host = '10.10.10.10';
$db_name = "nazwa_bazy";
if ($_sess_db = mysqli_connect($db_host, $db_user, $db_pass)){
return mysqli_select_db($_sess_db, $db_name);
}
return FALSE;
}

Zamykanie, czyli standardowe zwolnienie połączenia z bazą danych za pomocą mysqli_close.

function _close(){
global $_sess_db;
return mysqli_close($_sess_db);
}

Czytanie danych zapisanych w sesji (do funkcji jest przekazywany argument z id sesji). Funkcja session_set_save_handler() sama go tam umieści:

function _read($id){
global $_sess_db;
$id = mysqli_real_escape_string($_sess_db, $id);
$sql = "SELECT data FROM sessions WHERE id = '$id'";
if ($result = mysqli_query($_sess_db, $sql)){
if (mysqli_num_rows($result)) {
$record = mysqli_fetch_assoc($result);
return $record['data'];
}
}
return '';
}

Zapis danych (REPLACE INTO to równoważnik DELETE i INSERT rekordu o tym samym ID):

function _write($id, $data, $_sess_db=""){
global $_sess_db;
$access = time();
$id = mysqli_real_escape_string($_sess_db, $id);
$access = mysqli_real_escape_string($_sess_db, $access);
$data = mysqli_real_escape_string($_sess_db, $data);
$sql = "REPLACE INTO sessions VALUES ('$id', '$access', '$data')";
return mysqli_query($_sess_db, $sql);
}

Tutaj funkcja aby programista PHP mógł zniszczyć sesję, np. w razie chęci wylogowania użytkownika:

function _destroy($id){
global $_sess_db;
$id = mysqli_real_escape_string($_sess_db, $id);
$sql = "DELETE FROM sessions WHERE id = '$id'";
return mysqli_query($_sess_db, $sql);
}

Czyszczenie sesji następuje w przypadku przeterminowania:

function _clean($max) {
global $_sess_db;
$old = time() - $max;
$old = mysqli_real_escape_string($_sess_db, $old);
$sql = "DELETE FROM sessions WHERE access < '$old'";
return mysqli_query($_sess_db, $sql);
}

Krok 4: Dodatkowa instrukcja która pozwala utrzymać połączenie przy zapisie i usunięciu sesji. Link do źródeł na końcu artykułu.

register_shutdown_function('session_write_close');

Krok 5: Standardowe rozpoczęcie sesji

To wszystko od teraz można używać sesji w standardowy sposób. Otwierając standardową sesję poleceniem:

session_start();

Sprawdźmy czy działa

Stwórzmy dwie zmienne sesyjne:

$_SESSION["test"]="Witaj Świecie";
$_SESSION["user_id"]="1024";

Sprawdźmy teraz zawartość tabeli sessions (zrzut z phpMyAdmin):

Jak widać działa. Po wywołaniu session_destroy dane są usuwane. Każdorazowy odczyt powinien aktualizować pieczątkę czasu.

Wersja z wykorzystaniem OOP i PDO

Tutaj dominicklee zrobił to samo ale z wykorzystaniem paradygmatu obiektowego i PDO:

https://github.com/dominicklee/PHP-MySQL-Sessions

Podsumowanie

Wbrew pozorom poinstruowanie serwera aby przechowywał sesje w bazie danych nie jest bardzo skomplikowanym zadaniem. Taki zabieg pozwoli nam powielać serwery HTTP bez potrzeby stosowania mechanizmu klejących sesji czy zaawansowanych rozwiązań polegających na konfiguracji load balancerów.

Źródła

Shiflett C. Essential PHP Security, O’Reilly Media, 2005.

https://www.php.net/manual/en/function.session-set-save-handler.php

https://www.php.net/manual/en/function.register-shutdown-function.php


Komentarze

Brak komentarzy.

Dodaj swój komentarz