The exam ple uses
session_register
and t hus assum es t hat
register_globals
is enabled. Lat er on, well discuss how t o avoid t his lim it at ion.
19.3.5 Specifying a User-Defined Storage Module
The PHP session m anagem ent int erface j ust described m akes no reference t o any kind of backing st ore. That is, t he descript ion specifies not hing about how session inform at ion act ually
get s saved. By default , PHP uses t em porary files t o st ore session dat a, but t he session int erface is ext ensible so t hat ot her st orage m odules can be defined. To override t he default
st orage m et hod and st ore session dat a in MySQL, you m ust do several t hings:
•
Set up a t able t o hold session records and writ e t he rout ines t hat im plem ent t he st orage m odule. This is done once, prior t o writ ing any script s t hat use t he new
m odule.
•
Tell PHP t hat youre supplying a user- defined st orage m anager. You can do t his globally in php.ini in which case you m ake t he change once , or wit hin individual
script s in which case it s necessary t o declare your int ent in each script .
•
Regist er t he st orage m odule rout ines wit hin each script t hat want s t o use t he m odule.
19.3.5.1 Creating the session table
Any MySQL-based st orage m odule needs a dat abase t able in which t o st ore session inform at ion. Creat e a t able nam ed
php_session
t hat includes t he following colum ns: CREATE TABLE php_session
id CHAR32 NOT NULL, data BLOB,
t TIMESTAMP NOT NULL, PRIMARY KEY id
;
Youll recognize t he st ruct ure of t his t able as quit e sim ilar t o t he
sessions
t able used by t he Apache: : Session Perl m odule. The
id
colum n holds session ident ifiers, which are unique 32- charact er st rings t hey look suspiciously like Apache: Session ident ifiers, which is not
surprising, given t hat PHP uses MD5 values, j ust like t he Perl m odule .
data
holds session inform at ion. PHP serializes session dat a int o a st ring before st oring it , so
php_session
needs only a large generic st ring colum n t o hold t he result ing serialized value. The
t
colum n is a
TIMESTAMP
t hat MySQL updat es aut om at ically whenever a session record is updat ed. This colum n is not required, but it s useful for im plem ent ing a garbage collect ion policy based
on each sessions last updat e t im e. A sm all set of queries suffices t o m anage t he cont ent s of t he
php_session
t able as w e have defined it :
•
To ret rieve a sessions dat a, issue a sim ple
SELECT
based on t he session ident ifier: SELECT data FROM php_session WHERE id =
sess_id
;
•
To writ e session dat a, a
REPLACE
serves t o updat e an exist ing record or t o creat e a new one if no such record exist s :
REPLACE INTO php_session id,data VALUES
sess_id
,
sess_data
;
REPLACE
also updat es t he t im est am p in t he record when creat ing or updat ing a record, which is im port ant for garbage collect ion.
Som e st orage m anager im plem ent at ions use a com binat ion of
INSERT
and a fallback t o
UPDATE
if t he
INSERT
fails because a record wit h t he given session I D already exist s or an
UPDATE
w it h a fallback t o
INSERT
if t he
UPDATE
fails because a record wit h t he I D does not exist . I n MySQL, a dual-query approach is
unnecessary;
REPLACE
perform s t he required t ask wit h a single query.
•
To dest roy a session, delet e t he corresponding record: DELETE FROM php_session WHERE id =
sess_id
;
•
Garbage collect ion is perform ed by rem oving old records. The following query delet es records t hat have a t im est am p value m ore t han
sess_life
seconds old:
•
DELETE FROM php_session WHERE t DATE_SUBNOW ,INTERVAL
sess_life
SECOND; These queries form t he basis of t he rout ines t hat m ake up our MySQL- backed st orage m odule.
The prim ary funct ion of t he m odule is t o open and close MySQL connect ions and t o issue t he proper queries at t he appropriat e t im es.
19.3.5.2 Writing the storage management routines
User- defined session st orage m odules have a specific int erface, im plem ent ed as a set of handler rout ines t hat you regist er wit h PHPs session m anager by calling
session_set_save_handler
. The form at of t he funct ion is as follows, where each argum ent is a handler rout ine nam e specified as a st ring:
session_set_save_handler mysql_sess_open, function to open a session
mysql_sess_close, function to close a session mysql_sess_read, function to read session data
mysql_sess_write, function to write session data
mysql_sess_destroy, function to destroy a session mysql_sess_gc function to garbage-collect old sessions
;
You can nam e t he handler rout ines as you like; t hey need not necessarily be nam ed
mysql_sess_open
,
mysql_sess_close
, and so fort h. They should, however, be writ t en according t o t he following specificat ions:
mysql_sess_open save_path, sess_name
Perform s what ever act ions are necessary t o begin a session.
save_path
is t he nam e of t he locat ion where sessions should be st ored; t his is useful for file st orage
only.
sess_name
indicat es t he nam e of t he session ident ifier for exam ple,
PHPSESSID
. For a MySQL- based st orage m anager, bot h argum ent s can be ignored. The funct ion should ret urn
TRUE
or
FALSE
t o indicat e whet her or not t he session was opened successfully.
mysql_sess_close
Closes t he session, ret urning
TRUE
for success or
FALSE
for failur e.
mysql_sess_read sess_id
Ret rieves t he dat a associat ed wit h t he session ident ifier and ret urns it as a st ring. I f t here is no such session, t he funct ion should ret urn an em pt y st ring. I f an error
occurs, it should ret urn
FALSE
.
mysql_sess_write sess_id, sess_data
Saves t he dat a associat ed wit h t he session ident ifier, ret urning
TRUE
for success or
FALSE
for failure. PHP it self t akes care of serializing and unserializing t he session cont ent s, so t he read and writ e funct ions need deal only wit h serialized st rings.
mysql_sess_destroy sess_id
Dest roys t he session and any dat a associat ed wit h it , ret urning
TRUE
for success or
FALSE
for failure. For MySQL- based st orage, dest roying a session am ount s t o delet ing t he record from t he
php_session
t able t hat is associat ed wit h t he session I D.
mysql_sess_gc gc_maxlife
Perform s garbage collect ion t o rem ove old sessions. This funct ion is invoked on a probabilist ic basis. When PHP receives a request for a page t hat uses sessions, it calls
t he garbage collect or wit h a probabilit y defined by t he
session.gc_probability
configurat ion direct ive in php.ini. For exam ple, if t he probabilit y value is 1 t hat is, 1 , PHP calls t he collect or approxim at ely once
every hundred request s. I f t he value is 100, it calls t he collect or for every request — which probably would result in m ore processing overhead t han youd want .
The argum ent t o
gc
is t he m axim um session lifet im e in seconds. Sessions older t han t hat should be considered subj ect t o rem oval. The funct ion should ret urn
TRUE
for success or
FALSE
for failure. The handler rout ines are regist ered by calling
session_set_save_handler
, which should be done in conj unct ion wit h inform ing PHP t hat youll be using a user- defined
st orage m odule. The default st orage m anagem ent m et hod is defined by t he
session.save_handler
configurat ion direct ive. You can change t he m et hod globally by m odifying t he php.ini init ializat ion file, or wit hin individual script s:
•
To change t he st orage m et hod globally, edit php.ini. The default direct ive set t ing specifies t he use of file- based session st orage m anagem ent :
session.save_handler = files; Modify t his t o indicat e t hat sessions will be handled by a user- level m echanism :
session.save_handler = user; I f youre using PHP as an Apache m odule, youll need t o rest art Apache aft er m odifying
php.ini so t hat PHP not ices t he changes. The problem wit h m aking a global change is t hat every PHP script t hat uses sessions
will be expect ed t o provide it s own st orage m anagem ent rout ines. This m ay have unint ended side effect s for ot her script writ ers if t hey are unaware of t he change. For
exam ple, ot her developers t hat use t he web server m ay wish t o cont inue using file- based sessions.
•
The alt ernat ive t o m aking a global change is t o specify a different st orage m et hod by calling
ini_set
on a per - script basis: ini_set session.save_handler, user;
ini_set
is less int rusive t han a global configurat ion change. The st orage m anager well develop here uses
ini_set
so t hat dat abase-backed session st orage is t riggered only for t hose script s t hat request it .
To m ake it easy t o access an alt ernat ive session st orage m odule, it s useful t o creat e a library file, Cookbook_Session.php. Then t he only t hing a script need do t o use t he library file is t o
include it prior t o st art ing t he session. The out line of t he file looks like t his: ?php
Cookbook_Session.php - MySQL-based session storage module include_once Cookbook.php;
Define the handler routines function mysql_sess_open save_path, sess_name ...
function mysql_sess_close ... function mysql_sess_read sess_id ...
function mysql_sess_write sess_id, sess_data ... function mysql_sess_destroy sess_id ...
function mysql_sess_gc gc_maxlife ... Initialize connection identifier, select user-defined session
handling and register the handler routines mysql_sess_conn_id = FALSE;
ini_set session.save_handler, user; session_set_save_handler
mysql_sess_open, mysql_sess_close,
mysql_sess_read, mysql_sess_write,
mysql_sess_destroy, mysql_sess_gc
; ?
The library file includes Cookbook.php so t hat it can access t he connect ion rout ine for opening a connect ion t o t he
cookbook
dat abase. Then it defines t he handler rout ines well get t o t he det ails of t hese funct ions short ly . Finally, it init ializes t he connect ion ident ifier, t ells PHP
t o get ready t o use a user-defined session st orage m anager, and regist ers t he handler funct ions. Thus, a PHP script t hat want s t o st ore sessions in MySQL perform s all t he necessary
set up sim ply by including t he Cookbook_Session.php file: include_once Cookbook_Session.php;
The int erface provided by t he Cookbook_Session.php library file exposes a global dat abase connect ion ident ifier variable
mysql_sess_conn_id
and a set of handler rout ines nam ed
mysql_sess_open
,
mysql_sess_close
, and so fort h. Script s t hat use t he library should avoid using t hese global nam es for ot her purposes.
Now let s see how t o im plem ent each handler rout ine.
•
Ope n in g a se ssion .
PHP passes t wo argum ent s t o t his funct ion: t he save pat h and t he session nam e. The save pat h is used for file- based st orage, and we dont need t o know t he session nam e,
so bot h argum ent s are irrelevant for our purposes and can be ignored. The funct ion t herefore need do not hing but open a connect ion t o MySQL:
function mysql_sess_open save_path, sess_name {
global mysql_sess_conn_id; open connection to MySQL if its not already open
mysql_sess_conn_id or mysql_sess_conn_id = cookbook_connect ;
return TRUE; }
•
Closin g a se ssion .
The close handler checks whet her or not a connect ion t o MySQL is open, and closes it if so:
function mysql_sess_close {
global mysql_sess_conn_id; if mysql_sess_conn_id close connection if its open
{ mysql_close mysql_sess_conn_id;
mysql_sess_conn_id = FALSE; }
return TRUE; }
•
Re a din g se ssion da t a .
The
mysql_sess_read
funct ion uses t he session I D t o look up t he dat a for t he corresponding session record and ret urns it . I f no such record exist s, it ret urns
t he em pt y st ring: function mysql_sess_read sess_id
{ global mysql_sess_conn_id;
sess_id = addslashes sess_id; query = SELECT data FROM php_session WHERE id = sess_id;
if res_id = mysql_query query, mysql_sess_conn_id {
list data = mysql_fetch_row res_id; mysql_free_result res_id;
if isset data return data;
} return ;
}
•
W r it in g se ssion da t a .
mysql_sess_write
updat es a session record or creat es one if t here is no record for t he session yet :
function mysql_sess_write sess_id, sess_data {
global mysql_sess_conn_id; sess_id = addslashes sess_id;
sess_data = addslashes sess_data; query = REPLACE php_session id, data
VALUESsess_id,sess_data; return mysql_query query, mysql_sess_conn_id;
}
•
D e st r oyin g a se ssion .
When a session is no longer needed,
mysql_sess_destroy
r em oves t he corresponding record:
function mysql_sess_destroy sess_id {
global mysql_sess_conn_id; sess_id = addslashes sess_id;
query = DELETE FROM php_session WHERE id = sess_id; return mysql_query query, mysql_sess_conn_id;
}
•
Pe r for m in g ga r ba ge colle ct ion .
The
TIMESTAMP
colum n
t
in each session record indicat es when t he session was last updat ed.
mysql_sess_gc
uses t his value t o im plem ent garbage collect ion. The argum ent
sess_maxlife
specifies how old sessions can be in seconds . Older sessions are considered expired and candidat es for rem oval, which is
easily done by delet ing session records having a t im est am p t hat differs from t he current t im e by m ore t han t he allowed lifet im e:
function mysql_sess_gc sess_maxlife {
global mysql_sess_conn_id; query = sprintf DELETE FROM php_session
WHERE t DATE_SUBNOW ,INTERVAL d SECOND,
sess_maxlife; mysql_query query, mysql_sess_conn_id;
return TRUE; ignore errors }
19.3.5.3 Using the storage module
I nst all t he Cookbook_Session.php file in a public library direct ory accessible t o your script s. On m y syst em , I put PHP library files in usr local apache lib php. To t ry out t he st orage
m odule, inst all t he following exam ple script , sess_t rack.php, in your web t ree and invoke it a few t im es t o see how t he inform at ion display changes or, rat her, t o see if it changes; under
som e circum st ances, t he script will fail, as well discuss short ly : ?php
sess_track.php - session request countingtimestamping demonstration assumes that register_globals is enabled
include_once Cookbook_Session.php; include_once Cookbook_Webutils.php; needed for make_unordered_list
title = PHP Session Tracker; Open session and register session variables
session_start ; session_register count;
session_register timestamp; If the session is new, initialize the variables
if isset count count = 0;
if isset timestamp timestamp = array ;
Increment counter, add current timestamp to timestamp array ++count;
timestamp[ ] = date Y-m-d H:i:s T; if count = 10 destroy session variables after 10 invocations
{ session_unregister count;
session_unregister timestamp; }
Produce the output page ?
html head
title?php print title; ?title head
body bgcolor=white
?php printf pThis session has been active for d requests.p\n, count;
print pThe requests occurred at these times:p\n; print make_unordered_list timestamp;
? body
html
The script includes t he Cookbook_Session.php library file t o enable t he MySQL-based st orage m odule, t hen uses t he PHP session m anager int erface in t ypical fashion. First , it opens t he
session, regist ers t he session variables, and init ializes t hem if t he session is new. The scalar variable
count
st art s out at zero, and t he non-scalar variable
timestamp
st ar t s out as an em pt y array. Then t he script increm ent s t he count er, adds t he current t im est am p t o t he
end of t he t im est am p array, and produces an out put page t hat displays t he count and t he access t im es.
I f t he session lim it of 10 invocat ions has been reached, t he script unregist ers t he session variables, which causes
count
and
timestamp
not t o be saved t o t he session record. The effect is t hat t he session rest art s on t he next request .
sess_t rack.php does not call
session_write_close
explicit ly; t hat is unnecessary because PHP saves t he session aut om at ically when t he script t erm inat es.
The out put page is produced only aft er updat ing t he session record because PHP m ight det erm ine t hat a cookie cont aining t he session I D needs t o be sent t o t he client . That
det erm inat ion m ust be m ade before generat ing t he page body because cookies are sent in t he headers.
The problem wit h sess_t rack.php as writ t en is t hat it works only if PHPs
register_globals
configurat ion set t ing is enabled. I f t hat is so, regist ering session variables nam ed
count
and
timestamp
causes t heir values t o be m ade available as t he PHP global variables
count
and
timestamp
. However, when
register_globals
is disabled,
session_register
does not hing and sess_t rack.php will not work properly t he count will always be one, and only a single
t im est am p will be shown . The issue is a significant one because t he PHP developers now recom m end t hat
register_globals
be t urned off for securit y reasons. That m eans
session_register
is essent ially obsolet e and t hat exist ing session-based applicat ions t hat rely on it will begin t o fail as m ore and m ore sit es follow t he recom m endat ion
t o disable
register_globals
. To deal wit h t his problem and writ e code t hat works
regardless of t he
register_globals
set t ing, w e need t o get session variables anot her way. The t wo possiblit ies are t o use t he
HTTP_SESSION_VARS
global array or as of PHP 4.1 t he
_SESSION
superglobal array. For exam ple, a session variable nam ed
count
w ill be available as
HTTP_SESSION_VARS[count]
or
_SESSION[count]
. I t s possible t o m odify t he sess_t rack.php script relat ively easily so t hat it does not rely on t he
set t ing of
register_globals
, but st ill allows you t o work wit h sim ple variable nam es t o m anipulat e session variables:
•
Dont use
session_register
. I nst ead, copy session variables direct ly from a global session variable array int o t he
count
and
timestamp
variables.
•
Aft er youre done using your session variables, copy t hem back int o t he session variable array. Do t his before writ ing t he session, if you call
session_write
explicit ly. This approach does require t hat you det erm ine which global array t o use for session variable
st orage, which m ay depend on your version of PHP. I nst ead of doing t his each t im e you want t o access a session variable, it s easier t o writ e a couple of ut ilit y funct ions t hat do t he work:
function get_session_val name {
global HTTP_SESSION_VARS; unset val;
if isset _SESSION[name] val = _SESSION[name];
else if isset HTTP_SESSION_VARS[name] val = HTTP_SESSION_VARS[name];
return val; }
function set_session_val name, val {
global HTTP_SESSION_VARS; if PHP_VERSION = 4.1
_SESSION[name] = val; HTTP_SESSION_VARS[name] = val;
}
These rout ines can be found in t he Cookbook_Webut ils.php library file, along wit h t he rout ines t hat get ot her kinds of web param et er values see
Recipe 18.6 . They are in
Cookbook_Webut ils.php rat her t han in Cookbook_Session.php so t hat you can call t hem even if you elect not t o use t he MySQL-based session st orage t hat Cookbook_Session.php
im plem ent s.
The follow ing script , sess_t rack2.php, shows how avoid reliance on
register_globals
by m aking only sm all changes t o t he m ain logic of t he script : ?php
sess_track2.php - session request countingtimestamping demonstration does not rely on register_globals
include_once Cookbook_Session.php; include_once Cookbook_Webutils.php; needed for make_unordered_list
get_session_val , set_session_val
title = PHP Session Tracker; Open session and extract session values
session_start ; count = get_session_val count;
timestamp = get_session_val timestamp; If the session is new, initialize the variables
if isset count count = 0;
if isset timestamp timestamp = array ;
Increment counter, add current timestamp to timestamp array ++count;
timestamp[ ] = date Y-m-d H:i:s T; if count 10 save modified values into session variable array
{ set_session_val count, count;
set_session_val timestamp, timestamp; }
else destroy session variables after 10 invocations {
session_unregister count; session_unregister timestamp;
} Produce the output page
? html
head title?php print title; ?title
head body bgcolor=white
?php printf pThis session has been active for d requests.p\n, count;
print pThe requests occurred at these times:p\n; print make_unordered_list timestamp;
? body
html
sess_t rack2.php is ident ical t o sess_t rack.php, wit h t wo except ions:
•
sess_t rack.php calls
session_start
t o open a session, but t hat is not st rict ly required, because it uses
session_register
, which im plicit ly opens t he session for you. sess_t rack2.php does not use
session_register
. I nst ead, it get s t he variable values direct ly from global session variable st orage. Wit h t hat approach, you m ust call
session_start
first t o open t he session explicit ly.
•
I f t he session lim it of 10 request s has not yet been reached, sess_t rack2.php explicit ly st ores t he
count
and
timestamp
session values back int o t he global session variable arrays by invoking
set_session_val
.
19.4 Using MySQL for Session BackingStore with Tomcat