Specifying a User-Defined Storage Module

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