PHP Doku:: Setzt benutzerdefinierte Session-Speicherfunktionen - function.session-set-save-handler.html

Verlauf / Chronik / History: (1) anzeigen

Sie sind hier:
Doku-StartseitePHP-HandbuchFunktionsreferenzSession-ErweiterungenSessionbehandlungSession-Funktionensession_set_save_handler

Ein Service von Reinhard Neidl - Webprogrammierung.

Session-Funktionen

<<session_set_cookie_params

session_start>>

session_set_save_handler

(PHP 4, PHP 5)

session_set_save_handlerSetzt benutzerdefinierte Session-Speicherfunktionen

Beschreibung

bool session_set_save_handler ( callback $open , callback $close , callback $read , callback $write , callback $destroy , callback $gc )

session_set_save_handler() setzt die benutzerdefinierten Session-Speicherfunktionen, die zur Speicherung und Wiederherstellung von zur Session gehörigen Daten verwendet werden. Dies ist äußerst nützlich, wenn eine andere als die mit PHP-Sessions zur Verfügung stehende Art der Speicherung, z.B. die Speicherung der Session-Daten in einer lokalen Datenbank, bevorzugt wird.

Parameter-Liste

open

Die öffnen-Routine funktionert wie ein Konstruktor in einer Klasse und wird ausgeführt, wenn die Session geöffnet wird. Sie erwartet zwei Parameter: als Ersten den Speicherpfad und als Zweiten den Namen der Session.

close

Die schließen-Routine funktionert wie ein Destruktor in einer Klasse und wird am Ende der Session ausgeführt.

read

Damit die Speicherroutine funktioniert wie erwartet, muss die lesen-Routine immer eine Zeichenkette zurückgeben. Wenn es keine Daten zu lesen gibt, geben Sie eine leere Zeichenkette zurück. Rückgabewerte anderer Routinen werden in boolesche Ausdrücke umgewandelt. TRUE für einen Erfolg, FALSE für einen Fehler.

write

Die schreiben-Routine, die aufgerufen wird, wenn Session-Daten gespeichert werden sollen. Diese Funktion erwartet zwei Parameter: eine Session-ID und die zugehörigen Daten.

Hinweis:

Die schreiben-Routine wird nicht ausgeführt, bevor die Ausgabe abgeschlossen ist. Deshalb werden auch niemals Fehlermeldungen der schreiben-Routine im Browser erscheinen. Wenn die Ausgabe von Fehlermeldungen nötig ist, sollte diese stattdessen in eine Datei geschrieben werden.

destroy

Die löschen-Routine wird ausgeführt, wenn eine Session mittels session_destroy() gelöscht wird. Sie erwartet die Session-ID als einzigen Parameter.

gc

Der Speicherbereiniger (garbage collector) wird ausgeführt, wenn der Session-Speicherbereiniger ausgeführt wird. Er erwartet die maximale Session-Verfallszeit als einzigen Parameter.

Rückgabewerte

Gibt bei Erfolg TRUE zurück. Im Fehlerfall wird FALSE zurückgegeben.

Beispiele

Beispiel #1 session_set_save_handler() Beispiel

Das folgende Beispiel liefert eine dateibasierte Session-Speicherung, die der Standard-Speicherroutine files der PHP-Sessions ähnlich ist. Dieses Beispiel kann problemlos erweitert werden, dass es Datenbank-Speicherung mittels ihrer bevorzugten PHP-gestützten Datenbank beinhaltet.

<?php
function oeffne($speicherpfad$session_name)
{
  global 
$sess_speicherpfad;

  
$sess_speicherpfad $speicherpfad;
  return(
true);
}

function 
schliesse()
{
  return(
true);
}

function 
lese($id)
{
  global 
$sess_speicherpfad;

  
$sess_datei "$sess_speicherpfad/sess_$id";
  return (string) @
file_get_contents($sess_datei);
}

function 
schreibe($id$sess_daten)
{
  global 
$sess_speicherpfad;

  
$sess_datei "$sess_speicherpfad/sess_$id";
  if (
$fp = @fopen($sess_datei"w")) {
    
$return fwrite($fp$sess_daten);
    
fclose($fp);
    return 
$return;
  } else {
    return(
false);
  }
}

function 
loesche($id)
{
  global 
$sess_speicherpfad;

  
$sess_datei "$sess_speicherpfad/sess_$id";
  return(@
unlink($sess_datei));
}

function 
gc($maxlifetime)
{
  global 
$sess_speicherpfad;

  foreach (
glob("$sess_speicherpfad/sess_*") as $dateiname) {
    if (
filemtime($dateiname) + $maxlifetime time()) {
      @
unlink($dateiname);
    }
  }
  return 
true;
}

session_set_save_handler("oeffne""schliesse""lese""schreibe""loesche""gc");

session_start();

// verfahren Sie mit Sessions wie gewohnt
?>

Anmerkungen

Warnung

Ab PHP 5.0.5 werden die Routinen write und close nach dem Zerstören von Objekten aufgerufen und können deshalb keine Objekte verwenden oder Exceptions (Fehler-, Ausnahmesituationen) auslösen. Objekt-Destruktoren können aber Sessions verwenden.

Es ist möglich, session_write_close() über den Destruktor aufzurufen, um dieses Henne-Ei Problem zu lösen.

Warnung

Bei manchen SAPIs ändert sich das aktuelle Arbeitsverzeichnis, falls die Session durch die Beendigung des Skripts geschlossen wird. Mittels session_write_close() ist es möglich, die Session schon früher zu schließen.

Siehe auch


74 BenutzerBeiträge:
- Beiträge aktualisieren...
dummynick at gmail dot com
24.05.2010 17:37
I was getting Fatal error: Exception thrown without a stack frame and it took days to figure out the reason. I am using memcache to store sessions and in my custom class I use Memcache class in write method.

I put the code in the write method inside try-catch block and it solved my problem.
newton at tricko dot com dot br
19.02.2010 17:46
I have been done with session problems creating a new and my own session system.

This is very simple, bellow the class file, a good pratice is include these file as your first line:

<?php
 
class SessionDB {
    private
$data=null;
    private
$session_id=null;
    private
$minutes_to_expire=3600; // TIME TO MAINTAIN DATA ON DB
   
   
public function __construct(){
      global
$SESSION;
     
      if (isset(
$_COOKIE['session_id'])){
       
$this->session_id = $_COOKIE['session_id'];
      } else {
       
       
$this->session_id = md5(microtime().rand(1,9999999999999999999999999)); // GENERATE A RANDOM ID
       
       
setcookie('session_id',$this->session_id);
       
       
$sql = "INSERT INTO `tb_session_db` (`session_id`, `updated_on`) VALUES ('{$this->session_id}', NOW())";
       
mysql_query($sql);
      }
     
     
$sql = "SELECT `value` FROM `tb_session_db` WHERE `session_id`='{$this->session_id}'";
     
$query = mysql_query($sql);
     
     
$this->data = unserialize(mysql_result($query, 0, 'value'));
     
$SESSION = $this->data;
    }
   
    private function
expire(){
     
$date_to_delete = date("Y-m-d H:i:s", time()-60*$this->minutes_to_expire);
     
$sql = "DELETE FROM `tb_session_db` WHERE `update_on` <= '$date_to_delete'";
     
mysql_query($sql);
    }
   
    public function
__destruct(){
      global
$SESSION;
     
     
$this->data = serialize($SESSION);
     
     
$sql = "UPDATE `tb_session_db` SET `value`='{$this->data}', `updated_on`=NOW() WHERE `session_id`='{$this->session_id}'";
     
mysql_query($sql);
     
     
$this->expire();
    }
  }
 
/*
TABLE STRUCTURE
  CREATE TABLE IF NOT EXISTS `tb_session_db` (
    `session_id` varchar(32) NOT NULL,
    `value` blob,
    `updated_on` datetime DEFAULT NULL,
    PRIMARY KEY (`session_id`)
  ) ENGINE=MyISAM DEFAULT CHARSET=latin1;
*/
?>

With this file included, connected to MySQL database who has the table `tb_session_db` I can easy do that into my config file:

<?php
 
require_once('session_db.php');
 
   
define('MYSQL_HOST','mysql.example.com');
   
define('MYSQL_USER','.......');
   
define('MYSQL_PASS','.......');
   
define('MYSQL_BASE','.......');
   
   
mysql_connect(MYSQL_HOST, MYSQL_USER, MYSQL_PASS) or die('Cannot connect');
   
mysql_select_db(MYSQL_BASE) or die('Cannot select DB');
   
mysql_set_charset('utf8_general_ci');
   
 
$SESSION = null;
  global
$SESSION;
 
$session_db = new SessionDB();
?>

To use $SESSION on a function/class method, you must call:
global $SESSION

For who will not work with this over classes or functions, it is a very simple/fast way to work!
bart2yk at yahoo dot com
22.01.2010 22:57
You can call the session_write in db object destructor to be shore that you still have a connection to mysql and the session is write.
joel the usual at sign then purerave.com
4.12.2009 19:53
When storing sessions in a DB, it's usually beneficial to use an existing custom DB object, but this creates problems with the latest version of PHP 5.3.1. This used to work fine on PHP 5.2.x (Linux and Windows).

The problem now is that session_write_close() is not automatically called when execution ends, but rather after all the objects have been destructed, including the DB object!

There are two ways around this, either manually calling session_write_close() at the end of your script(s), or not using the DB object.

I'm sure this is the intended behavior from the beginning.
skds1433 at hotmail dot com
3.08.2009 0:21
I pulled a really stupid move. If you are trying to debug your garbage collector, make sure you call the following >>> BEFORE <<< "session_start":

<?php
ini_set
('session.gc_probability', 100);
ini_set('session.gc_divisor', 100);
?>

I was sure it was a bug in PHP, but turned out (like 99% of the time) to be me own fault.
foddex at foddex dot net
8.06.2009 10:01
A quick note for people having problems storing binary session data. Using prepared statements solves all your quote-related issues! Start reading here http://php.net/manual/en/mysqli-stmt.prepare.php for more information!
yangqingrong at gmail dot com
22.04.2009 6:44
session_set_save_handler is used before session_start.if your session is setted as auto start. it will return FALSE value.so you need add session_write_close() before session_set_save_handler to cancel the session's auto start.it  likes this:

<?php
/*
qq:290359552
*/
session_write_close(); //cancel the session's auto start,important

function open()
{
   ...
}
....
session_set_save_handler( ... );
session_start();
?>
harald at hholzer at
13.03.2009 19:40
after spending 8 hours to find out whats going on..

just for the records, because php.net ignore the real world out there:

debian 5 installs by default the php-suhosin module, which changes the behavior of session_set_save_handler read/write function.

on calling the session write function the session data will be encrypted, and the returning string from the read function are decrypted and verified.

the encrypted data is no more compatible with session_encode/session_decode.

and breaks by default, subdomain handling and multiple host setups where different document roots are used.

for futher information look at:
http://www.hardened-php.net/suhosin/configuration.html

session sample data (debian 4):
test|s:3:"sdf";

session sample data (debian 5, with php-suhosin):
3GdlPEGr2kYgRFDs-pUSoKomZ4fN7r5BM5oKOCMsWNc...

i thing the suhosin patch should report a warning in case of invalid session data, to get a clue whats going wrong.
e dot sand at elisand dot com
5.03.2009 8:42
A bit more information on what patrick at eternalspace dot com stumbled upon (and I have just now as well)...

The "binary" data that is in the session data appears to surround class/object names, and if you pass your session data through a function to sanitize it for SQL injection, you may indeed run in to problems.

For example, using the PDO::quote() function to prepare the data for injection (in my case for SQLite3), it was stopping short as soon as it encountered the first bit of binary data, causing my session information to be corrupted.

This change *must* have happened somewhere in the 5.2 series, because I just started encountering this problem recently on a code base that had been tested & working on earlier versions of PHP 5.2.

This may in fact be a bug - I have not yet checked... but beware, and perhaps using base64 to encode/decode your session data is a good thing to do just to be sure (though you are now left unable to visually inspect serialized session information at the storage level which is a rather big problem for on-the-fly debugging of sessions).
thomas at koudela dot net
18.02.2009 15:08
There are cases when calling session_write_close() from a destructor doesn't work. [E.g. order of destruction works bad for you and dependencies aren't on your side.] In all cases I stumble on...

register_shutdown_function('session_write_close');

...just works fine.
patrick at eternalspace dot com
13.12.2008 4:09
After about 8 hours of banging my skull on abstract art, it turns out that custom Write function use a blackbox encoding process that isn't represented here!  (Perhaps in session_encode it is?)

When a custom write() method gets the $session_hash and the $data of the session, the $data has binary data in it!  Therefore, stripslashes() or mysql_real_escape_string() will NOT properly prepare a collapsed $_SESSION string for database storage!

Instead, when committing write() $data to a database, it turns out that base64_encode() *WILL* catch all non-character information in the collapsed $_SESSION string, making it's result database-friendly!  (And more filling!)

Don't forget to add base64_decode() during the custom read() method!
e dot sand at elisand dot com
26.11.2008 14:07
Here's a few useful tips when working with custom session handlers (and sessions in general - with PHP 5.2.x)...

1) Setting session_set_save_handler() automatically sets the ini setting session.save_handler to 'user' for you - no need to do it yourself.

2) You may in fact not need to set up a __destruct() or use register_shutdown_function() to write your session data if using classes/objects.  My testing with 5.2.x shows that my session data is safely saved without the need of either.  I am unsure if PHP internals have changed to "fix" this and documents just aren't updated with that info, or if perhaps I haven't yet hit a scenario that would trigger this problem.

3) It's stated a few places - but just to re-iterate: you cannot serialize internal PHP objects.  This means if you have a class that extends an internal class (for example, a class that extends PDO for db functions), you will not be able to save an instance of your class in a session variable.  Worst off, you will get a very vague error message about it, saying an unknown exception was thrown and it won't know from where.  The solution is for any class that has to be serialized, don't extend internal classes, instead have them set up an instance of that object internally via __construct() or something, and then use __sleep() and __wakeup() hooks to export/reconfigure your class variables/resources.

4) It appears that since PHP 4.x or somewhere, the seralization of data that takes place for sessions changed.  It used to export human readable text much like serialize() does, however it appears that now the session data is further encoded - quite possibly to hinder session data tampering on the local filesystem.  Just mentioning this if you used to view session data to see what was being saved.
anonymous at anonymous dot org
6.10.2008 21:11
if you simply append the information from session variables every time you'll have many multiples for variables each time they are changed. a simple way to do this is explode the data twice to seperate the variable name from the other relevant information and foreach() check against the stored set. here is a little bit of a mess i wrote to do it.
assuming stored session variables in both database and passed through function:

<?php
$buffer
= array();
$buffer = explode('|',$sessiondata);
$buf1 = array();
$buf2 = array();
$finalbuff = '';
foreach(
$buffer as $i){
   
$i = explode(';',$i);
    foreach(
$i as $b){
       
array_push($buf1,$b);
    }
}
$buffer = explode('|',$result['data']);
foreach(
$buffer as $i){ $i = explode(';',$i); foreach($i as $b){ array_push($buf2,$b);}}
$z = 0;
$x = 0;
while(
$buf2[$z]){
    while(
$buf1[$x]){
        if(
$buf2[$z] == $buf1[$x]){
           
$buf2[($z+1)] = $buf1[($x+1)];
        }
       
$x+=2;
    }
   
$z+=2;
}
foreach(
$buf2 as $i){ $finalbuff .= $i; }
?>

$sessiondata is the variable passed through the function and $result['data'] is the data stored in an sql database.
Anonymous
16.08.2008 6:47
Hey,
I just found i very nice example in german how to store sessions in a MySQL database. Look up at
http://www.mywebsolution.de/workshops/1/page_5/show_Sessions-in-PHP.html#up
james at dunmore dot me dot uk
13.08.2008 15:31
If your using database session handler and your database is in UTF8, but you happen to have a script running in IS0-8859 that tries to put symbols such as £ signs into the database - then the session will corrupt and data will go missing.

You can either - make sure you never put special characters into the session, or - more helpful, do this in your 'write' function

<?php

public static function write( $key, $val )
        {
           
$val = utf8_encode( $val );
           
           
$val = preg_replace( '!s:(\d+):"(.*?)";!se', "'s:'.strlen('$2').':\"$2\";'", $val );

              
//......

?>

probably an overhead on this, but less overhead than loosing data.

You could also use WDDX for storing sessions I suppose
bob at nospam dot com
28.07.2008 14:50
If using mysqli in procedural style you may expect the mysqli handle to be valid in the write callback. It is not the case, the handle in procedural style is a object that will be destroy unlink the plain mysql interface handle. Just register a shutdown function after set save handler like this:

session_set_save_handler('_session_open', '_session_close', '_session_read', '_session_write', '_session_destroy', '_session_gc');
register_shutdown_function('session_write_close');

Since PHP 4.1, it more of a PRE-Shutdown and it fix the issue for me.
tomas at slax dot org
8.07.2008 16:00
Regarding the SAPIs: The warning mentioned in function's description (that the Current working directory is changed with some SAPIs) is very important.

It means that if your callback 'write' function needs to write to a file in current directory, it will not find it. You have to use absolute path and not rely upon the current working directory.

I thought this warning applies only to some strange environments like Windows, but it happens exactly on Linux + Apache 2.2 + PHP 5.
andreas dot beder at gmx dot at
26.06.2008 21:46
in reply to shaun freeman mdb2 session handler:
i just tried your code and want to correct some lines.
at first the database, i wonder why there is a session_id column  when no line uses it ??
the second one: the return value of the read method is a database resource so no session variable will be set.

database structure:

CREATE TABLE IF NOT EXISTS `sessions` (
  `session` varchar(255) character set utf8 collate utf8_bin NOT NULL,
  `session_expires` int(10) unsigned NOT NULL default '0',
  `session_data` mediumtext collate utf8_unicode_ci,
  KEY `session` (`session`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

you could replace the read method with:
<?
   
function read($sessID) {
        global
$php_errormsg;
       
// fetch session-data
       
$query = "
            SELECT session_data FROM sessions
            WHERE session = '
$sessID'
            AND session_expires >
        "
.time();
       
$result = $this->mdb2->query($query);
       
// return data or an empty string at failure
       
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
            return
false;
        }
        list(
$value)=@$result->fetchrow();
        return
$value;
    }
?>

it also took me a while to figure out what parameter i have to give the
session class.

<?
ini_set
("session.save_handler", "user");
require_once
'MDB2.php';
include(
"sessions.php"); // here should be your database class

$dsn = array(
   
'phptype'  => $cfg[db_type],
   
'username' => $cfg[db_user],
   
'password' => $cfg[db_pass],
   
'hostspec' => $cfg[db_host],
   
'database' => $cfg[db],
);

$options = array(
   
'debug'       => 2,
   
'portability' => MDB2_PORTABILITY_ALL,
);

$db =& MDB2::connect($dsn, $options);
if (
PEAR::isError($db)) {
    die(
$db->getMessage());
}

$session=new Session($dsn);
session_start();
echo
$_SESSION["login"]=$_SESSION["login"]+1;
?>
toby at telegraphics dot com dot au
20.06.2008 17:16
1) Re: dmclain, "MySQL will do extra work to allow you to do text-style searching on [a TEXT column]."

Absolutely not so, unless you ask for an index.

2) to anyone implementing garbage collection with SQL DELETE, this is going to be *much* more efficient if you add an index on the 'expire' timestamp column (which allows the comparison to efficiently find rows).
dmclain at expressdynamics dot com
14.05.2008 19:23
I recently implemented MySQL Database Stored Sessions, with a similar solution to those posted here.

I ran into a problem where my Session seemed like it was loosing data!  It turns out I had hit the length limit that a 'text' field could hold, yet the data was still de-serializing properly, and not throwing any error.

Using a 'text' column is fine, but limits you to 2^16 characters (65536-1) in length, and MySQL will do extra work to allow you to do text-style searching on it.

Since you will probably never write a query that selects sessions from your table based on the data that's in them... I suggest using a blob instead of text (blob stores it's data in binary format), and I would suggest going straight to mediumblob; which will allow you to store 2^24 (1.6 Million.. give or take a few) characters before you hit the wall.

Hope this helps!
anddriga at gmail dot com
9.05.2008 19:44
Some PHP code for memcached session handler.

class SessionHadler
{
    private static $lifetime = 0;

    public static function open()
    {
        self::$lifetime = ini_get('session.gc_maxlifetime');
       
        return true;
    }
   
    public static function read($id)
    {
        return memcached::get("sessions/{$id}");
    }
   
    public static function write($id, $data)
    {
        return memcached::set("sessions/{$id}", $data, self::$lifetime);
    }
   
    public static function destroy($id)
    {
        return memcached::delete("sessions/{$id}");
    }
   
    private function __construct(){}
    public static function gc(){ return true; }
    public static function close(){    return true; }
    public function __destruct()
    {
        session_write_close();
    }
}
james at enginecreative dot co dot uk
7.04.2008 0:22
With regards to db session handling:

Remember if you use the REPLACE INTO method to have your session key as the primary key otherwise you will end up with duplicate records in your table.

http://dev.mysql.com/doc/refman/5.0/en/replace.html
james dot ellis at gmail dot com
4.04.2008 13:39
When writing your own session handler, particularly database session handlers, play close attention to garbage cleanup and how it could affect server load.

To pick a round number example:

If you have 1000 requests per minute on session enabled pages, everyone needs a session started but the session garbage cleanup does not need to run every request. Doing so would cause unrequired queries on the database server.

In this example, setting your probability/divisor to 1/1000 would be sufficient to clean up old sessions at a minimum once a minute. If you don't need that kind of granularity, increase the gc divisor.

Finding the tradeoff between clearing up old sessions and server load is the important aspect here.
klose at openriverbed dot de
12.03.2008 21:01
An tested example with static class.
Initiated from maria at junkies dot jp comment.
<?php
/**
 * PHP session handling with MySQL-DB
 *
 * Created on 12.03.2008
 * @license    http://www.opensource.org/licenses/cpl.php Common Public License 1.0
 */

class Session
{
   
/**
     * a database connection resource
     * @var resource
     */
   
private static $_sess_db;

   
/**
     * Open the session
     * @return bool
     */
   
public static function open() {
       
        if (
self::$_sess_db = mysql_connect('localhost',
                                           
'root',
                                           
'')) {
            return
mysql_select_db('my_application', self::$_sess_db);
        }
        return
false;
    }

   
/**
     * Close the session
     * @return bool
     */
   
public static function close() {
        return
mysql_close(self::$_sess_db);
    }

   
/**
     * Read the session
     * @param int session id
     * @return string string of the sessoin
     */
   
public static function read($id) {
       
$id = mysql_real_escape_string($id);
       
$sql = sprintf("SELECT `session_data` FROM `sessions` " .
                      
"WHERE `session` = '%s'", $id);
        if (
$result = mysql_query($sql, self::$_sess_db)) {
            if (
mysql_num_rows($result)) {
               
$record = mysql_fetch_assoc($result);
                return
$record['session_data'];
            }
        }
        return
'';
    }

   
/**
     * Write the session
     * @param int session id
     * @param string data of the session
     */
   
public static function write($id, $data) {
       
$sql = sprintf("REPLACE INTO `sessions` VALUES('%s', '%s', '%s')",
                      
mysql_real_escape_string($id),
                      
mysql_real_escape_string(time()),
                      
mysql_real_escape_string($data)
                       );
        return
mysql_query($sql, self::$_sess_db);
    }

   
/**
     * Destoroy the session
     * @param int session id
     * @return bool
     */
   
public static function destroy($id) {
       
$sql = sprintf("DELETE FROM `sessions` WHERE `session` = '%s'", $id);
        return
mysql_query($sql, self::$_sess_db);
    }

   
/**
     * Garbage Collector
     * @param int life time (sec.)
     * @return bool
     * @see session.gc_divisor      100
     * @see session.gc_maxlifetime 1440
     * @see session.gc_probability    1
     * @usage execution rate 1/100
     *        (session.gc_probability/session.gc_divisor)
     */
   
public static function gc($max) {
       
$sql = sprintf("DELETE FROM `sessions` WHERE `session_expires` < '%s'",
                      
mysql_real_escape_string(time() - $max));
        return
mysql_query($sql, self::$_sess_db);
    }
}

//ini_set('session.gc_probability', 50);
ini_set('session.save_handler', 'user');

session_set_save_handler(array('Session', 'open'),
                         array(
'Session', 'close'),
                         array(
'Session', 'read'),
                         array(
'Session', 'write'),
                         array(
'Session', 'destroy'),
                         array(
'Session', 'gc')
                         );

if (
session_id() == "") session_start();
//session_regenerate_id(false); //also works fine
if (isset($_SESSION['counter'])) {
   
$_SESSION['counter']++;
} else {
   
$_SESSION['counter'] = 1;
}
echo
'<br/>SessionID: '. session_id() .'<br/>Counter: '. $_SESSION['counter'];

?>

And don't miss the table dump. ^^

CREATE TABLE IF NOT EXISTS `sessions` (
  `session` varchar(255) character set utf8 collate utf8_bin NOT NULL,
  `session_expires` int(10) unsigned NOT NULL default '0',
  `session_data` text collate utf8_unicode_ci,
  PRIMARY KEY  (`session`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
maria at junkies dot jp
9.12.2007 15:51
blow example and ta summary of these comments.
and using the simple native functions of mysql.

<?php
class Session
{

   
/**
     * a database connection resource
     * @var resource
     */
   
private $_sess_db;

   
/**
     * Open the session
     * @return bool
     */
   
public function open() {

        if (
$this->_sess_db = mysql_connect(SESSION_DB_HOST,
                                           
SESSION_DB_USER,
                                           
SESSION_DB_PASS)) {
            return
mysql_select_db(SESSION_DB_DATABASE, $this->_sess_db);
        }
        return
false;

    }

   
/**
     * Close the session
     * @return bool
     */
   
public function close() {

        return
mysql_close($this->_sess_db);

    }

   
/**
     * Close the session
     * @return bool
     */
   
public function close() {

        return
mysql_close($this->_sess_db);

    }

   
/**
     * Read the session
     * @param int session id
     * @return string string of the sessoin
     */
   
public function read($id) {

       
$id = mysql_real_escape_string($id);
       
$sql = sprintf("SELECT `data` FROM `sessions` " .
                      
"WHERE id = '%s'", $id);
        if (
$result = mysql_query($sql, $this->_sess_db)) {
            if (
mysql_num_rows($result)) {
               
$record = mysql_fetch_assoc($result);
                return
$record['data'];
            }
        }
        return
'';

    }

   
/**
     * Write the session
     * @param int session id
     * @param string data of the session
     */
   
public function write($id, $data) {

       
$sql = sprintf("REPLACE INTO `sessions` VALUES('%s', '%s', '%s')",
                      
mysql_real_escape_string($id),
                      
mysql_real_escape_string($data),
                      
mysql_real_escape_string(time()));
        return
mysql_query($sql, $this->_sess_db);

    }

   
/**
     * Destoroy the session
     * @param int session id
     * @return bool
     */
   
public function destroy($id) {

       
$sql = sprintf("DELETE FROM `sessions` WHERE `id` = '%s'", $id);
        return
mysql_query($sql, $this->_sess_db);

}

   
/**
     * Garbage Collector
     * @param int life time (sec.)
     * @return bool
     * @see session.gc_divisor      100
     * @see session.gc_maxlifetime 1440
     * @see session.gc_probability    1
     * @usage execution rate 1/100
     *        (session.gc_probability/session.gc_divisor)
     */
   
public function gc($max) {

       
$sql = sprintf("DELETE FROM `sessions` WHERE `timestamp` < '%s'",
                      
mysql_real_escape_string(time() - $max));
        return
mysql_query($sql, $this->_sess_db);

    }

}

//ini_set('session.gc_probability', 50);
ini_set('session.save_handler', 'user');

$session = new Session();
session_set_save_handler(array($session, 'open'),
                         array(
$session, 'close'),
                         array(
$session, 'read'),
                         array(
$session, 'write'),
                         array(
$session, 'destroy'),
                         array(
$session, 'gc'));

// below sample main

session_start();
session_regenerate_id(true);

if (isset(
$_SESSION['counter'])) {
   
$_SESSION['counter']++;
} else {
   
$_SESSION['counter'] = 1;
}

?>
rsumibcay at reddoor dot biz
26.10.2007 19:46
In the below example posted by "shaun at shaunfreeman dot co dot uk". You shouldn't call gc() within the close() method. This would undermine PHP's ability to call gc() based on session.gc_probability and session.gc_divisor. Not to mention an expensive database hit if you are calling gc() on every request. This can become more problematic for load balanced servers talking to a single database server. And even more expensive if the database is setup to replicate your data for failover.
mixailo at mercenaries dot ru
12.10.2007 1:55
It is useful to use MEMORY storage engine in MySQL while handling sessions.
http://dev.mysql.com/doc/refman/5.0/en/memory-storage-engine.html
james at dunmore dot me dot uk
11.10.2007 17:52
I think it is very important here to stress that the WRITE method should use UPDATE+INSERT (or mysql specific REPLACE).

There is example code "out there" that uses just UPDATE for the write method, in which case, when session_regenerate_id is called, session data is lost (as an update would fail, as the key has changed).

I've just wasted a whole day due to this (I know I should have thought it through / RTFM, but it is an easy trap to fall into).
shaun at shaunfreeman dot co dot uk
8.10.2007 18:36
After much messing around to get php to store session in a database and reading all these notes, I come up with this revised code based on 'stalker at ruun dot de' class.
I wanted to use PEAR::MDB2. The only assumption I make is your PEAR::MDB2 object is called $db

SQL:
CREATE TABLE `sessions` (
  `session_id` int(10) unsigned NOT NULL auto_increment,
  `session` varchar(255) character set utf8 collate utf8_bin NOT NULL,
  `session_expires` int(10) unsigned NOT NULL default '0',
  `session_data` mediumtext collate utf8_unicode_ci,
  PRIMARY KEY  (`session_id`),
  KEY `session` (`session`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

<?php
class Session {
   
// session-lifetime
   
public $lifeTime;
    function
__construct ($db) {
       
// get session-lifetime
       
$this->lifeTime = get_cfg_var("session.gc_maxlifetime");
          
// open database-connection
       
$this->mdb2 =& MDB2::factory($db);
        if (
PEAR::isError($this->mdb2)) {
           
$php_errormsg .= $this->mdb2->getMessage();
           
$php_errormsg .= $this->mdb2->getDebugInfo();
        }
       
session_set_save_handler(array(&$this, 'open'),
                                array(&
$this, 'close'),
                                array(&
$this, 'read'),
                                array(&
$this, 'write'),
                                array(&
$this, 'destroy'),
                                array(&
$this, 'gc'));
       
register_shutdown_function('session_write_close');
       
session_start();
           return
true;
    }
    function
open($savePath, $sessName) {
       
// get session-lifetime
       
$this->lifeTime = get_cfg_var("session.gc_maxlifetime");
        return
true;
    }
    function
close() {
       
$this->gc(ini_get('session.gc_maxlifetime'));
       
// close database-connection
       
return $this->mdb2->disconnect();
    }
    function
read($sessID) {
        global
$php_errormsg;
       
// fetch session-data
       
$query = "
            SELECT session_data FROM sessions
            WHERE session = '
$sessID'
            AND session_expires >
        "
.time();
       
$result = $this->mdb2->queryOne($query);
       
// return data or an empty string at failure
       
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
            return
false;
        }
        return
$result;
    }
    function
write($sessID,$sessData) {
        global
$php_errormsg;
       
// new session-expire-time
       
$newExp = time() + $this->lifeTime;
       
// is a session with this id in the database?
       
$query = "
            SELECT * FROM sessions
            WHERE session = '
$sessID'
        "
;
       
$result = $this->mdb2->query($query);
       
// if yes,
         
if($result->numRows()) {
           
// ...update session-data
           
$query = "
                UPDATE sessions
                SET session_expires = '
$newExp',
                 session_data = '
$sessData'
                WHERE session = '
$sessID'
            "
;
          }
       
// if no session-data was found,
         
else {
           
// create a new row
           
$query = "
                INSERT INTO sessions (
                     session,
                      session_expires,
                      session_data)
                VALUES(
                     '
$sessID',
                      '
$newExp',
                      '
$sessData')
            "
;
          }
       
$result = $this->mdb2->exec($query);
       
// if something happened, return true
       
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
            return
false;
        } else {
           
// ...else return true
           
return true;
        }
    }
    function
destroy($sessID) {
        global
$php_errormsg;
       
// delete session-data
       
$query = "
            DELETE FROM sessions
            WHERE session = '
$sessID'
        "
;
       
$result = $this->mdb2->exec($query);
       
// if session was not deleted, return false,
        
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
            return
false;
         } else {
           
// ...else return true
           
return true;
        }
    }
    function
gc($sessMaxLifeTime) {
        global
$php_errormsg;
       
// delete old sessions
       
$query = "
            DELETE FROM sessions
            WHERE session_expires <
        "
.time();
       
$result = $this->mdb2->exec($query);
       
// return affected rows
       
if (MDB2::isError($result)) {
           
$php_errormsg .= $result->getMessage();
           
$php_errormsg .= $result->getDebugInfo ();
        }
        return
$result;
    }
}
?>
maximumpig at yahoo d0t c0m
13.08.2007 23:19
I believe equazcion is not correct--the environment is refreshed with each script so session_set_save_handler() should be called with each script, not once per session.
sroby at colubris dot company
24.05.2007 17:56
The Web app I work on stores sessions in a database using PEAR::DB, so when I migrated it to PHP 5 everything broke because the DB object was unallocated by the time the write/close handlers were called.

However, I've found that adding <?php register_shutdown_function("session_write_close"); ?> works fine as a workaround to the problem.
equazcion
10.03.2007 20:44
I know this might be obvious, but session_set_save_handler() should only be called once per session, or else your saved data will keep getting reset.

If your script doesn't have a predictable start page that will only be called only once per session, place the session_set_save_handler statement in an include file, and call it via require_once().
Colin
8.03.2007 22:25
Aspects of this have been posted in various comments but it's helpful to make it clearer.

The custom session handler seems to perform actions in these orders:

When session_start() is called:

open
read
clean (if cleaning is being done this call)
write
close

When session_destroy() is called after session_start():

open
read
clean (if cleaning is being done this call)
destroy
close

When session_regenerate_id(1) is called after session_start():

open
read
clean (if cleaning is being done this call)
destroy
write
close
Colin
8.03.2007 21:10
When using a custom session handler, if the first callback function (sessOpen in my case) finds no session id, one is set by the time the second argument (sessRead in my case) is called.
Jonathan Zylstra
2.12.2006 2:58
In response to  korvus at kgstudios dot net
11-Jun-2005 05:34:

Actually, using MySQL REPLACE, and getting the number of
affected rows = 2, means that one row was being deleted,
and one row being inserted.
That would happen if your session id was already in the database, and you write to it using 'REPLACE' ... it deletes the old session data, and REPLACE's it with the updated session data, thus resulting in two affected rows.

See:
http://dev.mysql.com/doc/refman/4.1/en/replace.html

I would be careful in assuming that the 'write' callback is called twice because of the behavior of your SQL code.
matt at openflows dot org
20.09.2006 2:02
Note that for security reasons the Debian and Ubuntu distributions of php do not call _gc to remove old sessions, but instead run /etc/cron.d/php*, which check the value of session.gc_maxlifetime in php.ini and delete the session files in /var/lib/php*.  This is all fine, but it means if you write your own session handlers you'll need to explicitly call your _gc function yourself.  A good place to do this is in your _close function, like this:

<?php
function _close() {
   
_gc(get_cfg_var("session.gc_maxlifetime"));
  
// rest of function goes here
}
?>
information at saunderswebsolutions dot com
16.08.2006 20:56
Note that if session.auto_start is set to On in the php.ini, your session_set_save_handler will return false as the session has already been initialized.

If you are finding that your code works OK on one machine but doesn't work on another, check to see if session.auto_start is set to On
sneakyimp AT hotmail DOT com
5.08.2006 6:20
the behavior, return values, and exact time of calling for these functions is pretty poorly documented here.  i thought folks might like to know that:

1) calling session_start() triggers PHP to first call your open function and then call your read function before resuming with the code immediately following your session_start() call.

2) calling session_id('some_value') within your open function WILL NOT SET THE SESSION COOKIE (at least not on my setup - PHP 4.4.1).  Assuming you defined some function to validate a session id called my_func(), you might want to do something like this in your open function:

<?php
 
function _open($save_path, $session_name) {
   
// check for session id
   
$sess_id = session_id();
    if (empty(
$sess_id) || !myfunc($sess_id)) {
     
//session_id is INVALID - generating new
     
$new_id = md5(uniqid("some random seed here"));
     
session_id($new_id);
     
setcookie(session_name(),
                   
$new_id,
                   
0,
                   
"/",
                   
".mydomain.com");
      }

    return
true;
  }
// _open()
?>
djmaze@cpgnuke cms
20.05.2006 19:51
Since register_shutdown_function() and __destruct() will not work on object based sessions, ESPECIALY when they are database based thru a class, then it seems you're screwed.

Issue:
register_shutdown_function('shutdown_function');
new sql();
new session();

on destruct:
sql __destruct()
session __destruct() // can't call sql since already destroyed
shutdown_function() // can't call instances since already destroyed

A trick is to use an "first class instance"
<?php
class my_proper_destructor
{
    protected
$destroyers = array();

    function
add($class_instance)
    {
        if (
is_class($class_instance))
           
$destroyers[] = $class_instance;
    }

    function
__destruct()
    {
        foreach(
$destroyers as $des)
           
$des->on_destroy();
    }
}

$destruct = new my_proper_destructor();

// All your code starts here like:
class session
{
    function
__construct()
    {
        global
$destruct;
       
$destruct->add($this);
    }
}

?>

Since my_proper_destructor is created first it also gets destroyed as the first. That way you can still call important class instances that need to do something on destruct.
mjohnson at pitsco dot com
28.03.2006 21:04
With regards to the read handler, the docs say:

  "Read function must return string value always to make save
  handler work as expected. Return empty string if there is no
  data to read."

I can't emphasize this enough. I just spent half a day trying to figure out why my sessions weren't storing any information. I was blithely returning the results of a query on the database from the read handler. Since there was no match for the new ID, the result was NULL. Since it wasn't a string, sessions were essentially disabled. So, the safe thing might be something like this:

<?php
function sessRead($id)
{
   
// Look up data
   
$results = getStuff($id);
   
   
// Make sure it's a string
   
settype($results, 'string');
    return
$results;
}
?>

Of course, you can do whatever you want with it. But, no matter what, make sure you return a string.

HTH,
Michael
mastabog at hotmail dot com
23.02.2006 16:09
In regard to the note dated 28-Jan-2006 09:15 (there is no email) and the webpage it links to.

That is not the way objects should be saved into session. You should not just assign the entire object to $_SESSION['myObj'] and hope for the best while waiting for the script to end and for the php internal engine to serialize your object, call destructors etc.

You should serialize your object beforehand by specifically calling serialize() on your object and assign the serialized string to a session key.

Yeah, You might be wondering if this isn't defeating the whole purpose. It's not. This way you are sure that your object is serialized correctly and don't have to worry about how session handlers mess with it. You now have a string variable to assign wherever you want and you avoid all the mess of session write()/close() being called before object destructors. You also have more control over the state of the object you want to save into session (the serialized object will not reflect changes done to the object after serialization) and will also end up with a code that is more likely to survive over time in case the guys at PHP decide to make other changes to the session functions.

Marcus has recently introduced the Serializable interface (php 5.1.2) which IMO will improve things *a lot* and is the right way to serialize objects. The problem with it though is that it breaks object references in some cases. I already made a bug report here: http://bugs.php.net/bug.php?id=36424

You can mimic the Serializable interface's methods by adding a public method to your classes that assigns serialize($this) to $_SESSION['myObj'] and another static method that acts as a constructor, returning unserialize($_SESSION['myObj']). This way you have even more control for serializing your objects as you can modify your Foo::serialize()/unserialize() methods to perform other tasks as well.

<?php

class Foo
{
    const
SESSION_KEY = 'someKeyName';

   
// ...
    // other definitions go here
    // ...

   
static function unserialize ($key = self::SESSION_KEY)
    {
        return isset(
$_SESSION[$key]) ? unserialize($_SESSION[$key]) : false;
    }

    public function
serialize ($key = self::SESSION_KEY)
    {
       
$_SESSION[$key] = serialize($this);
    }
}

$Obj = new Foo();

// do somehting on $Obj here

$Obj->serialize();

// continue until the end of script

?>

When getting your object from session you just do:

<?php

// try getting your obj from session
$Obj = Foo::unserialize();

// and to make sure you do a valid Foo instance:
// (you can also do this inside Foo::unserialize() if you want)
$Obj instanceOf Foo or $Obj = new Foo();

?>

If you need to save multiple instances of the same class then just pass a different parameter to Foo::serialize() and Foo::unserialize(). You can also extend your classes over a class like Foo above and overload the Foo::serialize()/unserialize() methods. Of course, you can do all that without having dedicated methods in your classes (yuck).

28.01.2006 18:15
As posted here (http://marc.theaimsgroup.com/?l=php-general&m=113833844422096&w=2) the session module cannot handle objects anymore (tested in 5.1.2).

You have to add

> session_write_close();

at the end of your script to save the object values into the session.

This may indicate general problem. session_write_close() is called in the session module's rshutdown() method, which is much too late: Since 5.1 the zend_object_store is cleaned before the module's rshutdown hook is called.  Furthermore it is not guaranteed that the session module's rshutdown method is called before your module's rshutdown gets called.

In other words: Session shutdown seems to be broken in all PHP versions. It is better to always add session_write_close() to the end of your scripts.  This works with all php versions and it will work with future versions of php whether or not this problem gets fixed.

17.01.2006 22:04
function __construct() {
    session_set_save_handler(array(&$this, 'open'),
                             array(&$this, 'close'),
                             array(&$this, 'read'),
                             array(&$this, 'write'),
                             array(&$this, 'destroy'),
                             array(&$this, 'gc'));
    register_shutdown_function('session_write_close');
    session_start();
}
boswachter at xs4all nl
13.01.2006 12:37
If you're creating a sessionhandler class, and use a database-class and you are experiencing problems because of destroyed objects when write is called, you can fix this relatively easily:

register_shutdown_function("session_write_close");

This way, the session gets written of before your database-class is destroyed.
stalker at ruun dot de
3.01.2006 16:25
object- and mysql-based session-handler, requires the following table:

CREATE TABLE `ws_sessions` (
  `session_id` varchar(255) binary NOT NULL default '',
  `session_expires` int(10) unsigned NOT NULL default '0',
  `session_data` text,
  PRIMARY KEY  (`session_id`)
) TYPE=InnoDB;

<?php
class session {
   
// session-lifetime
   
var $lifeTime;
   
// mysql-handle
   
var $dbHandle;
    function
open($savePath, $sessName) {
      
// get session-lifetime
      
$this->lifeTime = get_cfg_var("session.gc_maxlifetime");
      
// open database-connection
      
$dbHandle = @mysql_connect("server","user","password");
      
$dbSel = @mysql_select_db("database",$dbHandle);
      
// return success
      
if(!$dbHandle || !$dbSel)
           return
false;
      
$this->dbHandle = $dbHandle;
       return
true;
    }
    function
close() {
       
$this->gc(ini_get('session.gc_maxlifetime'));
       
// close database-connection
       
return @mysql_close($this->dbHandle);
    }
    function
read($sessID) {
       
// fetch session-data
       
$res = mysql_query("SELECT session_data AS d FROM ws_sessions
                            WHERE session_id = '
$sessID'
                            AND session_expires > "
.time(),$this->dbHandle);
       
// return data or an empty string at failure
       
if($row = mysql_fetch_assoc($res))
            return
$row['d'];
        return
"";
    }
    function
write($sessID,$sessData) {
       
// new session-expire-time
       
$newExp = time() + $this->lifeTime;
       
// is a session with this id in the database?
       
$res = mysql_query("SELECT * FROM ws_sessions
                            WHERE session_id = '
$sessID'",$this->dbHandle);
       
// if yes,
       
if(mysql_num_rows($res)) {
           
// ...update session-data
           
mysql_query("UPDATE ws_sessions
                         SET session_expires = '
$newExp',
                         session_data = '
$sessData'
                         WHERE session_id = '
$sessID'",$this->dbHandle);
           
// if something happened, return true
           
if(mysql_affected_rows($this->dbHandle))
                return
true;
        }
       
// if no session-data was found,
       
else {
           
// create a new row
           
mysql_query("INSERT INTO ws_sessions (
                         session_id,
                         session_expires,
                         session_data)
                         VALUES(
                         '
$sessID',
                         '
$newExp',
                         '
$sessData')",$this->dbHandle);
           
// if row was created, return true
           
if(mysql_affected_rows($this->dbHandle))
                return
true;
        }
       
// an unknown error occured
       
return false;
    }
    function
destroy($sessID) {
       
// delete session-data
       
mysql_query("DELETE FROM ws_sessions WHERE session_id = '$sessID'",$this->dbHandle);
       
// if session was deleted, return true,
       
if(mysql_affected_rows($this->dbHandle))
            return
true;
       
// ...else return false
       
return false;
    }
    function
gc($sessMaxLifeTime) {
       
// delete old sessions
       
mysql_query("DELETE FROM ws_sessions WHERE session_expires < ".time(),$this->dbHandle);
       
// return affected rows
       
return mysql_affected_rows($this->dbHandle);
    }
}
$session = new session();
session_set_save_handler(array(&$session,"open"),
                         array(&
$session,"close"),
                         array(&
$session,"read"),
                         array(&
$session,"write"),
                         array(&
$session,"destroy"),
                         array(&
$session,"gc"));
session_start();
// etc...
?>
cthree at s2ki dot com
25.12.2005 17:53
In reply to rudy dot metzger at pareto dot nl:

One option to work around this problem is to instantiate your database connection from within session class:

class Session {
  public $db;

  public function open( $path, $name ) {
    $this->db = mysql_connect(...); // or whatever works
    return true;
  }

  ...

  public function close() {
    mysql_close( $this->db ); // or whatever works
    return true;
  }
}

$session = new Session;
session_set_save_handler(
  array(&$session, "open"),
  ...
  array(&$session, "close"),
  ...
  );
session_start();
$db =& $session->db;
...

HTH
bachir
4.12.2005 19:48
php doesn't make any checks about PHPSESSID cookie format, it is then important to verify cookie format before making any sql request.

if your read session request is :
SELECT DataValue FROM sessions WHERE SessionID='$aKey'

this generic cookie could succeed to access others session: PHPSESSID=1' OR SessionID LIKE '%>>

for safety, you can make this verification before sql request:
if (! preg_match('/^([0-9a-f]{32})$/i',$aKey)) return NULL;

hope this can be helpful
frank at ethisoft dot nl
9.11.2005 15:48
REPLY TO >>>>> Keamos at gmail dot com [ 23-Oct-2005 10:34 ]

I would like to mention that there is a major flaw in your implementation:
I'll explain:

look at the 1st line: 
$sessionLifetime = get_cfg_var("session.gc_maxlifetime");

then look at the write function in short:
global $db, $sessionLifetime;
$expiry = time() + $sessionLifetime;
$sql = 'SELECT `expiry` FROM ' . SESSIONS_TABLE . " WHERE `sessionid`=$sessionid";
if(null === $sql)
{
          $sql = 'INSERT INTO ' . SESSIONS_TABLE . " (`sessionid`, `expiry`, `value`) VALUES ($sessionid, $expiry, $sessionData)";
}
else
{
         $sql = 'UPDATE `' . SESSIONS_TABLE . "` SET `expiry`=$expiry, `value`=$sessionData WHERE `sessionid`=$sessionid";
}

then look at the garbage function in short:
$sql = 'DELETE FROM ' . SESSIONS_TABLE . " WHERE expiry < $time";

if you look carefully you will see this in the write function:
$expiry = time() + $sessionLifetime;
and this in the garbage function:
'DELETE FROM ' . SESSIONS_TABLE . " WHERE expiry < $time";

to be short, your session will never be destroyed except when you explicitly call the destroy function!

have a nice 1...
Jaik
2.11.2005 17:01
I have something to add to the note from rudy dot metzger at pareto dot nl.

As objects are being destroyed before sessions are written as of PHP 5.0.5, we can use a __destruct() method to call session_write_close() in a class-based session handler.

It is possible for your session handling class to use other objects. It's just a matter of calling session_write_close() at the end of your script, rather in a __destruct() method.

eg.

<?php

echo "You've been to this page " . $oSession->hits . " times before";

$oSession->hits++;

session_write_close();

?>

It's generally best to make declare a function in your common include file(s) that is called at the end of every script on your site. That way you can do any cleanups you need, and also call session_write_close().
Keamos at gmail dot com
23.10.2005 10:34
Here's a set of session-handling functions that uses PEAR::DB  and a database to store the php sessions. The only assumption I make is your PEAR::DB object is called $db.

mySQL:
CREATE TABLE `sessions` (
  `sessionid` varchar(40) binary NOT NULL default '',
  `expiry` int(10) unsigned NOT NULL default '0',
  `value` text NOT NULL,
  PRIMARY KEY  (`sessionid`)
) TYPE=MyISAM COMMENT='Sessions';

<?
    $sessionLifetime
= get_cfg_var("session.gc_maxlifetime");
   
    function
_openSession($savePath, $id)
    {
       
//We don't need to create any new file handles, et cetera
       
return TRUE;
    }

    function
_closeSession()
    {
       
//We don't need to close any file handles, et cetera
       
return TRUE;
    }

    function
_readSession($sessionid)
    {
        global
$db, $sessionLifetime;

       
$sessionid = $db->quoteSmart($sessionid);
       
$time = time();

       
$sql = 'SELECT `value` FROM ' . SESSIONS_TABLE . " WHERE `sessionid`=$sessionid AND `expiry` > $time";
       
$result = $db->getOne($sql);
       
        if (
PEAR::isError($result))
        {
           
//Must return "" because php.net says so
           
return "";
        }
        else
        {
            return
$result;
        }

       
//Something slipped through the cracks, so throw an error
       
return "";
    }

    function
_writeSession($sessionid, $sessionData)
    {
        global
$db, $sessionLifetime;

       
$expiry = time() + $sessionLifetime;
       
$sessionid = $db->quoteSmart($sessionid);
       
$sessionData = $db->quoteSmart($sessionData);

       
$sql = 'SELECT `expiry` FROM ' . SESSIONS_TABLE . " WHERE `sessionid`=$sessionid";
       
$result = $db->getOne($sql);

        if (
$result === NULL)
        {
           
$sql = 'INSERT INTO ' . SESSIONS_TABLE . " (`sessionid`, `expiry`, `value`) VALUES ($sessionid, $expiry, $sessionData)";
           
$result = $db->query($sql);

            if (
PEAR::isError($result))
            {
               
//error of some sort
               
return FALSE;
            }

            return
TRUE;
        }
        else
        {
           
$sql = 'UPDATE `' . SESSIONS_TABLE . "` SET `expiry`=$expiry, `value`=$sessionData WHERE `sessionid`=$sessionid";
           
$result = $db->query($sql);

           
//Something went wrong
           
if (PEAR::isError($result))
            {
                return
FALSE;
            }

            return
TRUE;
        }

       
//Something slipped through, so throw an error
       
return FALSE;
    }

    function
_destroySession($sessionid)
    {
        global
$db;

       
$sessionid = $db->quoteSmart($sessionid);
       
$sql = 'DELETE FROM ' . SESSIONS_TABLE . " WHERE `sessionid`=$sessionid";
       
$result = $db->query($sql);

        if (!
PEAR::isError($result))
        {
            return
TRUE;
        }

        return
FALSE;
    }

    function
_gcSession($maxlifetime)
    {
        global
$db;

       
$time = $db->quoteSmart(time());
       
$sql = 'DELETE FROM ' . SESSIONS_TABLE . " WHERE expiry < $time";
       
$result = $db->query($sql);

        if (
PEAR::isError($result))
        {
            return
0;
        }

        return
$db->affectedRows();
    }

   
session_set_save_handler("_openSession", "_closeSession", "_readSession", "_writeSession", "_destroySession", "_gcSession");
?>
rudy dot metzger at pareto dot nl
21.09.2005 15:18
NOTE: as from php-5.0.5, objects are destroyed BEFORE the session_write() is called! So if you use an object as custom session handler, your writes will fail, as at the moment of write the object is already destroyed. A workaround for this is to use session_write_close() in the __destruct() of the object. This however only works if you do not need any other objectes, as these already might be gone! I only hope that object are destroyed in "descening" order, as if you would have a DB object in the session handler object which handles the DB connects, you would have to use __destruct() also in this object. otherwise your session object is still alive but your DB is long gone...

I raised a bug for this already but it is claimed that this is no bug.

This only applies to versions 5.0.5 (and possibly higher)
kevin at insuremytrip dot com
25.08.2005 22:34
When creating a session-handling class, I have discovered that it is important to remember that your session object and the $_SESSION global are TWO SEPARATE ENTITIES. You need to create a reference between your Session Object and the built-in $_SESSION global:

session_set_save_handler(
 array(&$this, "_OPEN"),
 array(&$this, "_CLOSE"),
 array(&$this, "_READ"),
 array(&$this, "_WRITE"),
 array(&$this, "_DESTROY"),
 array(&$this, "_GC")
);
           
session_start();
header("Cache-control: private"); // to overcome/fix a bug in IE 6.x
           
$this->session    =& $_SESSION;

Additionally, when you add a $_SESSION variable, it's a good idea to add it as a Session Object variable at the same time, if you want your code to use the Session Object between page changes/reloads:

function setSessionVariable($key,$value) {
    $_SESSION[$key] = $value;
    $this->session[$key] = $value;
    return;
}
ccav at maxbaud dot net
21.08.2005 13:35
Ok, after much hairpulling, I've figured out a successful way to store objects in a session using a postgresql savehandler.

function write ($id, $sess_data)
{
  global $sql;  // my global db connection object
  $sess_data = @pg_escape_bytea(serialize($sess_id)); //works with any type
  $sql->Query("delete from sessions where sessionid = '$id'"); // delete old session record
  $sql->Query("insert into sessions (sessionid,datavalue) values ('$id', '$sess_data')");  //insert into session table
  return true;
}

function read ($id)
{
  global $sql;
  $result=$sql->Query("select * from sessions where sessionid = '$id'");
  if ($sql->rows==1) {
    $row=$sql->Fetch(0);
    $rval = unserialize(@pg_unescape_bytea($sql->data[2]));
    return $rval;
  } else {
    return "";
  }
}

Make the datavalue column (the one used to store the actual session data) in the sessions table type bytea.

The problem apparently lies in the PHP serialize for objects, where there is a CR/LF inserted between the object id and the property array.  The CR/LF isn't escaped, and causes a fit for postgresql.

Any data type in PHP can be serialized, so there's no need to distinguish between types.

The object is fully restored, with methods.
Ysu
1.08.2005 8:33
if you have the problem [markus at fischer dot name]
mentioned earlier with the "Failed to initialize storage module" error (I do), you can simply call session_set_save_handler() again after the  session_write_close() and before another session_start() as a temp. workaround...
Scott P.
18.07.2005 13:55
Reference to the ldap implementation above: I'd strongly suggest against using ldap to store sessions, as an ldap server is designed specifically to serve data that changes rarely (compared to a database). While an ldap server is quick to retreive data, it has a very slow update time. So using this to store constantly changing data will kill a moderately busy website in next to no time!
niklas at removethisandthedot dot bivald dot com
16.07.2005 2:29
Many people are using session_set_save_handler to store the session in a session database, which ofcourse is both valid and smart since it (could) increas security.

What many people forgets, is that session ids can easily be edited by a user as he see fit (by editing a session_cookie for example*)

* If you like to play around to test your site, check Add n edit Cookies extension for firefox.

This might not be a big deal when saving them in a file, since the worst thing that may happen is that the user losts his session and a new one is generated. But when saving to an DB it is*. One should never trust that the server itself add slashes and escapes other vital characters.

* A google search for "SQL Injection" gives 716 000 hits.

Example code, none working:
<?PHP

   
function read ($session_id)
    {
       
$sql        = mysql_query("SELECT * FROM mysessions WHERE session_id=$session_id");
       
$data= mysql_fetch_array($sql);

        }
    }

?>

Is obviously flawed. Since setting our session ID to "; drop mysessions; " would create serious problems.

A more suitable approch would be, something in the lines of:

Example code, none working:
<?PHP

   
function read ($session_id)
    {

  
// ( Code by php.net )
  
if (get_magic_quotes_gpc()) {
      
$session_id = stripslashes($session_id);
   }
  
// Quote if not integer
  
if (!is_numeric($session_id)) {
      
$session_id = mysql_real_escape_string($session_id);
   }

       
$sql        = mysql_query("SELECT * FROM mysessions WHERE session_id=$session_id");
       
$fieldarray = mysql_fetch_array($sql);

        }
    }

?>

I quick checked different sample codes and tutorials and none of them actually escaped session ids.

That's my two cents for too night,
Niklas
korvus at kgstudios dot net
11.06.2005 14:34
It seems when you call 'session_name()', php loads the session id automatically from GET ( if the index exists ) and passes it to the 'read' callback method correctly, but the 'write' callback is invoked twice: first the auto-generated session id, then the custom session id

So be aware of what queries you execute inside the callback .. I got crazy because I used a MySQL 'REPLACE' statement to agilize, and I spent a lot of hours trying to understand why 2 rows instead of 1 were being affected ( the first id was inserting, the second updating )

I hope this helps!
Robert Chapin
6.05.2005 23:09
Session authentication is not meant to be part of the session handlers.

These handlers only read and write the session data itself, and will not allow you to call the vital function session_regenerate_id().

To add extra authentication routines, call them after session_start returns control.  DO NOT put them in your 'open' or 'read' handlers.

Enjoy,
-- Miqro
markus at fischer dot name
16.03.2005 10:07
Note that there's a bug with custom session handlers and when you want to start a session again after you have called session_destroy.

session_destroy disables the custom session_handler and this a call to session_start after it will fail with "Failed to initialize storage module".

See http://bugs.php.net/32330 for more information and a workaround.
oliver at teqneers dot de
3.02.2005 12:44
For some people it might be important to know, that if the standard session handler has been overwritten with session_set_save_handler, no locking is working anymore (between session_read and session_write). The following might happen:

script "A" start         .
read session data        .
.                        script "B" start
.                        read session data
running (30secs)         add session data
.                        write sesion data
.                        script "B" stop
write session data       .
script "A" stop          .

If a script "A" runs for a long time (say 30secs) the same user might start another script "B", which also uses a session. Script "B" will start and read session data, even though script "A" is still running. Because script "B" is much faster it will finish its work and write back its session data before script "A" has ended. Now script "A" ends and overwrites all of script "B"'s session data. If you DON'T use session_set_save_handler, this cannot happend, because in this case, PHP will not start script "B" until script "A" ends.
Balu
20.09.2004 0:26
If a session is closed the save-handlers seem to be resetted (in PHP 4.1.2 they are), so you need to run session_set_save_handler() again after e.g. running session_write_close() and restarting the session with session_start();
Ido
9.07.2004 5:51
Encrypted Sessions:  For those who are either sending their session data in the clear (i.e.: MySQL connections without SSL), or who have sensitive data stored in sessions (such as credit card information!) and wish to protect their users.

I have placed a link to a very crude example of session data encryption for MySQL-based session handling below:

http://www.cs.uchicago.edu/~ido/session_include_php.txt

I apologize if the code is a bit sloppy.  I might release some of my better code for this kind of thing if I have time to delete any site-specific references from that code.

Donate to my college education:)
http://www.cs.uchicago.edu/~ido/donate.php 

Motivation: While most online merchants do not store personal or credit card information in plaintext, most use standard sessions provided by PHP or other database session handlers which store session data in the clear.  Most shopping cart solutions store very sensitive data in session variables.

I hope I can convince more of my fellow web developers to make the minimal effort to protect privacy and confidentiality of user data by encrypting session data prior to storage. All sites developed (even in part) by me feature session data encryption, most of which feature automatic monthly regeneration of random encryption keys (and flawless auto-rollover to the new keys).

In practice, you would probably want to store the symmetric key in a safer location, such as in your Apache config file as a PHP global variable.  Regardless, encryption should be a minimal precaution when sensitive data (session data, for example) is sent across a network -- such as when your database and web servers are on different hosts, or when your database server is on a machine which you cannot ensure is secure.
dolan at unt dot edu
9.06.2004 22:23
This is an LDAP implementation that I wrote which works just fine for me.  This assumes you've got some global variables with your host and other info.  It also uses a custom error function which I have not defined here.  Also, it assumes your LDAP server is set up with correct permissions, objectclass, and all that.  I used an octet string in LDAP to hold the session data.  I had to manually wrap some of this so be careful with cutting and pasting.

$sessconn=null;
//Custom session handling stored in local LDAP
function ldap_sess_open($save_path,$session_name) {
    global $infohost,$infoport,$infodn,$infopw,$sessconn;
    $sessconn=@ldap_connect($infohost,$infoport);
    if(!@ldap_bind($sessconn,$infodn,$infopw)) {
    setError("Failed to open session.");
    return false;
    }
    return true;
}

function ldap_sess_close() {
    global $sessconn;
    @ldap_close($sessconn);
    return true;
}

function ldap_sess_read($id) {
    global $sessconn;
    $sr=@ldap_search($sessconn,'ou=sessions,o=Company',
"(cn=$id)");
    $info=@ldap_get_entries($sessconn,$sr);
    if($info['count']>0)
    return $info[0]['session'][0];
    else
    return "";
}

function ldap_sess_write($id,$sess_data) {
    global $sessconn;
    $update=array();
    $update['objectClass']=array('phpsession','top');
    $update['session']=$sess_data;
    $dn="cn=$id,ou=sessions,o=Company";
    @ldap_delete($sessconn,$dn);
    @ldap_add($sessconn,$dn,$update);
    return true;
}

function ldap_sess_destroy($id) {
    global $sessconn;
    $dn="cn=$id,ou=sessions,o=Company";
    @ldap_delete($sessconn,$dn);
    return true;
}

function ldap_sess_gc($maxlifetime) {
    global $sessconn;
    $sr=@ldap_search($sessconn,'ou=sessions,o=Company',
'(objectClass=phpsession)',array('+','cn'));
    $info=@ldap_get_entries($sessconn,$sr);
    if($info['count']>0) {
    for($i=0;$i<$info['count'];$i++) {
        $id=$info[$i]['cn'][0];
        $dn="cn=$id,ou=sessions,o=Company";
        $modified=stamp2local($info[$i]['modifytimestamp'][0]);
        if((time()-$modified)>=$maxlifetime)
        @ldap_delete($sessconn,$dn);
    }
    }
    return true;
}

//converts my LDAP timestamps over to Unix timestamps
function stamp2local($ldapstamp) {
    $year=substr($ldapstamp,0,4);
    $month=substr($ldapstamp,4,2);
    $day=substr($ldapstamp,6,2);
    $hour=substr($ldapstamp,8,2);
    $minute=substr($ldapstamp,10,2);
    $stamp=gmmktime($hour,$minute,0,$month,$day,$year);
    return $stamp;
}

//Use this so that if your LDAP server goes down people can still
//retain sessions the normal way
$checkds=@ldap_connect($infohost,$infoport);
if(@ldap_bind($checkds,$infodn,$infopw)) {
    session_set_save_handler("ldap_sess_open","ldap_sess_close",
"ldap_sess_read","ldap_sess_write","ldap_sess_destroy",
"ldap_sess_gc");
}
@ldap_close($checkds);
Monty
8.04.2004 0:03
It's really important to apply addslashes() to the serialized values passed to your custom session Write handler if you are using MySQL for storing sessions, even if you have magic_quotes turned on. Otherwise, values stored in $_SESSION will not be saved with the actual session in MySQL, or will be truncated.

<?
function sessWrite($key, $val) {

   
$val = addslashes($val);

   
// Now do your database insert here.

   
return TRUE;
}
?>

Without addslashes(), the above handler would simply not work, and you will get no error back from PHP about it either.

16.03.2004 13:41
@andrew at netrux dot com

never forget: always add escaping when building sql strings.
mysql_escape_string() / pg_escape_string() or whatever is your friend.
tonanbarbarian at hotmail dot com
4.12.2003 7:30
I found the need to have a garbage collection function run with my sessions so I had to write my own session handler.

I took the example code above but found that it had a problem, it does not open the session file exclusively.
This means that if you have a frameset where 2 pages are accessing the session at the same time and both modify the session only the last page to finish processing will have its session data saved. The first page will have all of its session data overwritten.

So here is a modified version of a file session handler in PHP that has file locking. (Not supported on FAT or NFS apparently)

$sess_save_path = $sess_session_name = $fp=null;
function open ($save_path, $session_name) {
  global $sess_save_path, $sess_session_name;
      
  $sess_save_path = $save_path;
  $sess_session_name = $session_name;
  return(true);
}

function close() {
  global $fp;
  flock($fp, LOCK_UN);
  fclose($fp);
  $fp=null;
  return(true);
}

function read ($id) {
  global $sess_save_path, $sess_session_name, $fp;

  $sess_file = "$sess_save_path/sess_$id";
  if ($fp = @fopen($sess_file, "r+")) {
    flock($fp, LOCK_EX);
    $sess_data = fread($fp, filesize($sess_file));
    return($sess_data);
  } else {
    return(""); // Must return "" here.
  }

}

function write ($id, $sess_data) {
  global $sess_save_path, $sess_session_name, $fp;

  $sess_file = "$sess_save_path/sess_$id";
  if (!empty($fp)) {
    fseek($fp,0);
    return(fwrite($fp, $sess_data));
  } elseif ($fp = @fopen($sess_file, "w")) {
    flock($fp, LOCK_EX);
    return(fwrite($fp, $sess_data));
  } else {
    return(false);
  }
}

function destroy ($id) {
  global $sess_save_path, $sess_session_name;
      
  $sess_file = "$sess_save_path/sess_$id";
  return(@unlink($sess_file));
}

function gc ($maxlifetime) {
    global $sess_save_path, $sess_session_name;
      
  $sess_file = "$sess_save_path/sess_$id";
  return true;
}

session_set_save_handler ("open", "close", "read", "write", "destroy", "gc");

enjoy
coco at digitalco2 dot com
24.11.2003 0:59
When using mySQL for your session handling functions, don't forget to call mysql_select_db() to change the database if you are using a separate database for your session data. Call mysql_select_db() INSIDE every handler function that accesses the database, since if you write session data after accessing another database, it will not change the database to your session database, and therefore, not write the session data.
andrew at netrux dot com
22.10.2003 15:15
For those interested in a postgresql version.  Seems to work but dont come crying to me when it eats your pistachio nuts!

<?

Derived from php file version on website
, changes copyright andrew@netrux.com.

/*

create table sessions(
sessionid     char(255) primary key not null,
lastupdated   timestamp not null,
datavalue     text);

*/

function open ($save_path, $session_name) {
 
$sess_conn=pg_connect("host=localhost dbname= user=postgres")
   or die (
"PostgreSQL error: --> " . pg_last_error($sess_conn));
  return(
true);
}

function
close() {
  return(
true);
}

function
read ($id) {
 
$result=pg_query("select * from sessions where sessionid = '$id'")
   or die (
"PostgreSQL error: --> " . pg_last_error($sess_conn));
  if (
pg_num_rows($result)==1) {
   
$row=pg_fetch_array($result);
    return(
$row[2]);
  } else {
    return(
"");
  }
}

function
write ($id, $sess_data) {
   
pg_query("delete from sessions where sessionid = '$id'")
     or die (
"PostgreSQL error: --> " . pg_last_error($sess_conn));
   
pg_query("insert into sessions(sessionid,lastupdated,datavalue) values('$id','now'::timestamp,'$sess_data')")
     or die (
"PostgreSQL error: --> " . pg_last_error($sess_conn));
    return(
true);
}

function
destroy ($id) {
 
pg_query("delete from sessions where sessionid = '$id'")
   or die (
"PostgreSQL error: --> " . pg_last_error($sess_conn));
 
pg_close($sess_conn);
  return(
true);
}

/*********************************************
 * WARNING - You will need to implement some *
 * sort of garbage collection routine here.  *
 *********************************************/
function gc ($maxlifetime) {
  return
true;
}

session_set_save_handler ("open", "close", "read", "write", "destroy", "gc");

?>
rafael dot tz at uol dot com dot br
25.07.2003 20:38
Hi people! I was wondering about a MySQL Sessions control... Here is some of my work, a little mod on some cods that I've found this far:

if(!mysql_table_exists("sessions",$DB))
{
       $query = 'CREATE TABLE sessions
                 (
                  SessionID     char(255)   not null,
                  LastUpdated   datetime    not null,
                  DataValue     text,
                  PRIMARY KEY ( SessionID ),
                  INDEX ( LastUpdated )
                 )';
       mysql_query($query);
}
                            

function sessao_open($aSavaPath, $aSessionName)
{
       global $aTime;

       sessao_gc( $aTime );
       return True;
}

function sessao_close()
{
       return True;
}

function sessao_read( $aKey )
{
       $query = "SELECT DataValue FROM sessions WHERE SessionID='$aKey'";
       $busca = mysql_query($query);
       if(mysql_num_rows($busca) == 1)
       {
             $r = mysql_fetch_array($busca);
             return $r['DataValue'];
       } ELSE {
             $query = "INSERT INTO sessions (SessionID, LastUpdated, DataValue)
                       VALUES ('$aKey', NOW(), '')";
             mysql_query($query);
             return "";
       }
}

function sessao_write( $aKey, $aVal )
{
       $aVal = addslashes( $aVal );
       $query = "UPDATE sessions SET DataValue = '$aVal', LastUpdated = NOW() WHERE SessionID = '$aKey'";
       mysql_query($query);
       return True;
}

function sessao_destroy( $aKey )
{
       $query = "DELETE FROM sessions WHERE SessionID = '$aKey'";
       mysql_query($query);
       return True;
}

function sessao_gc( $aMaxLifeTime )
{
       $query = "DELETE FROM sessions WHERE UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(LastUpdated) > $aMaxLifeTime";
       mysql_query($query);
       return True;
}

session_set_save_handler("sessao_open", "sessao_close", "sessao_read", "sessao_write", "sessao_destroy", "sessao_gc");
Joe Wheeler
31.01.2003 2:04
If you happen to want to build a session manager object you can set the handlers from inside the class. This helps to keep the class encapsulated which helps make it nice and reusable.

class Session_manager {
...
    session_set_save_handler(
        array(& $this, 'session_open_method'),
        array(& $this, 'session_close_method'),
        array(& $this, 'session_read_method'),
        array(& $this, 'session_write_method'),
        array(& $this, 'session_destroy_method'),
        array(& $this, 'session_gc_method')
    );
...
}

Each handler is identified by an array containing a self-reference to the instance (note the &) and a string identifying a method in the session manager class.

Remember that the new operator passes back a copy of the object. This is important if you want to call session_set_save_handler() from inside the class constructor. You must remember to use another reference when you create the instance...

$my_session_manager = & new Session_manager;

Otherwise the session handlers will be assigned to an instance which is created by the new operator and NOT to the copy which is stored in the variable $my_session_manager.
ivo at magstudio dot net
25.11.2002 16:08
Just a few words to explain some troubles while using session_set_save_handler(). It appears that internally PHP calls session management functions in this order: open(), read(), write(), close(). Close() function is called even if you does not make a call to sesison_start(), perhaps for some reasons like cleaning.
   If you try to redefine these functions and call sessions_set_save_handler() but something doesn't work, (in my case the ssion data hasn't been written) it's a good idea to debug them in the order they are called. They doesn't produce error output to browser but you can use print or echo.
   Shortly, if your write() function doesn't work as expected take a look for errors in previous functions - open() and read().
   I hope that this will help to save someone few hours debugging.
steve at saturn5 dot com
10.06.2002 17:39
if you want to call session_start again after session_destroy, you have to call session_set_save_handler a second time. otherwise the session module doesn't know about all the nice functions you defined earlier because it has been destroyed.

example:

# session handlers functions are defined
# somewhere else

session_set_save_handler ("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc");

session_start();

# do some stuff ...

session_destroy();

# call this again, because session_destroy
# destroys more than just the session.

session_set_save_handler ("sess_open", "sess_close", "sess_read", "sess_write", "sess_destroy", "sess_gc");

session_start();

...

we need to call session_start() twice in our login script to prevent multiple logins. i tore my hair out over this one. hope this helps someone else. :)
spam at skurrilo dot de
8.05.2002 13:16
You can't use the session autostart feature with

session.save_handler = user

set in your php.ini. Use instead the auto_prepend_file directive in the php.ini and point it to your save_handler with an session_start() at the end.
jpm at phpbrasil dot com
17.04.2002 19:51
You may also use static class methods as the 'function names' for session_set_save_handler(), as in:

<?php
session_set_save_handler
(
  array(
"SessionHandler", "open"),
  array(
"SessionHandler", "close"),
  array(
"SessionHandler", "read"),
  array(
"SessionHandler", "write"),
  array(
"SessionHandler", "destroy"),
  array(
"SessionHandler", "gc")
);
?>

Anyway, just a little note that this is also possible.



PHP Powered Diese Seite bei php.net
The PHP manual text and comments are covered by the Creative Commons Attribution 3.0 License © the PHP Documentation Group - Impressum - mail("TO:Reinhard Neidl",...)