O ponto é que não precisamos gravar os dados de sessão em um arquivo em disco, podemos gravá-lo em qualquer lugar que desejarmos.
Dados transientes (como sessões web, bloqueios, ou estatísticas de curto prazo) devem ser mantidos em um armazenamento de dados transitórios como Memcache.
De fato, por se tratar de memória, Memcache pode ser uma saída bastante adequada para nossos dados de sessão, porém, algumas vezes precisamos colher dados de navegação de usuários para testes de usabilidade, por exemplo:
Imagine que não basta utilizar o Google Analytics para saber quantas vezes uma determinada seção do seu site foi acessada; Imagine que você precisa conhecer o caminho que o usuário fez para chegar até lá e o que ele fez em seguida, quais dados do site foram consumidos, enfim, você precisa saber se a seção está facilmente acessível e se os dados estão dispostos corretamente.
Dados estatísticos, frequentemente escritos mas raramente lidos (por exemplo, um contador de hits na web), devem usar um modelo chave/valor como o Redis, ou um modelo de documento como o MongoDB.
O fato é que, em momentos da aplicação poderemos ter situações de uso de um manipulador de sessão que utiliza Memcache, em outros momentos MongoDB.
Se modelarmos um gerenciador de sessão baseado em uma implementação Memcache ficaremos totalmente dependentes dessa implementação, precisamos desacoplar a forma que os dados de sessão serão manipulador para que, em diversos pontos de uma aplicação, possamos variá-lo conforme a situação contudo, se fizermos um gerenciador de sessão tentando implementar as diversas variações do sistema de armazenamento, teremos um acoplamento desnecessário e perderemos em reusabilidade, sem contar que nosso manipulador de sessão terá código demais.
Precisamos desacoplar e abstrair as implementações de armazenamento para que possamos variar o manipulador conforme necessário; Precisamos encapsular o método de armazenamento em uma classe específica para esse fim e delegar a responsabilidade de armazenamento à elas:

O próximo passo é instalar o Memcache e o MongoDB:
[root@localhost neto]# yum install memcached ...
Depois de instalá-lo, basta utilizar a PECL para instalar o driver para PHP,
[root@localhost neto]# pecl install memcache
Bom, com Memcache e MongoDB instalados, vamos definir a interface do nosso manipulador de sessão:
SessionHandler.php
<?php
/**
* Interface para definição de um manipulador de sessão
*/
interface SessionHandler {
/**
* Fecha o manipulador de sessão
* @param $session Session
* @return boolean
*/
public function close( Session $session );
/**
* Destroi a sessão e seus dados
* @param $session Session
* @return boolean
*/
public function destroy( Session $session );
/**
* Coletor de lixo de sessão
* @param $session Session
* @return boolean
*/
public function garbageCollection( Session $session );
/**
* Abre o manipulador de sessão
* @param $session Session
* @return boolean
*/
public function open( Session $session );
/**
* Lê os dados de sessão
* @param $session Session
* @return array
*/
public function read( Session $session );
/**
* Grava os dados de sessão
* @param $session Session
* @return boolean
*/
public function write( Session $session );
}
Com isso, podemos definir nossos manipuladores Memcache e MongoDB:
MemcacheSessionHandler.php
<?php
require_once 'SessionHandler.php';
/**
* Manipulador de sessão que utiliza Memcache para armazenar os dados
*/
class MemcacheSessionHandler implements SessionHandler {
/**
* Instância de Memcache
* @var Memcache
*/
private $memcache;
/**
* Constroi o manipulador de sessão utilizando Memcache
*/
public function __construct(){
$this->memcache = new Memcache();
}
/**
* Fecha o manipulador de sessão
* @param $session Session
* @return boolean
*/
public function close( Session $session ){
return $this->memcache->close();
}
/**
* Destroi a sessão e seus dados
* @param $session Session
* @return boolean
*/
public function destroy( Session $session ){
return $this->memcache->delete( $session->getSessionId() );
}
/**
* Coletor de lixo de sessão
* @param $session Session
* @return boolean
*/
public function garbageCollection( Session $session ){
return $this->memcache->flush();
}
/**
* Abre o manipulador de sessão
* @param $session Session
* @return boolean
*/
public function open( Session $session ){
$host = explode( ':' , $session->getSavePath() );
$this->memcache->connect( $host[ 0 ] , isset( $host[ 1 ] ) ? (int) $host[ 1 ] : 11211 );
if ( $this->memcache->getServerStatus( $host[ 0 ] , isset( $host[ 1 ] ) ? (int) $host[ 1 ] : 11211 ) == 0 ){
throw new RuntimeException( 'Falha ao conectar ao servidor' );
}
return true;
}
/**
* Lê os dados de sessão
* @param $session Session
* @return string
*/
public function read( Session $session ){
$_SESSION = $this->memcache->get( $session->getSessionId() );
return true;
}
/**
* Grava os dados de sessão
* @param $session Session
* @return boolean
*/
public function write( Session $session ){
return $this->memcache->set( $session->getSessionId() , $_SESSION , MEMCACHE_COMPRESSED , $session->getSessionExpires() );
}
}
MongoSessionHandler
<?php
require_once 'SessionHandler.php';
/**
* Manipulador de sessão que utiliza MongoDB para armazenar os dados
*/
class MongoSessionHandler implements SessionHandler {
/**
* Instância do MongoDB
* @var Mongo
*/
private $mongo;
/**
* Coleção que será utilizada para armazenar os dados;
* A coleção possui o mesmo nome do cookie da sessão
* @var MongoCollection
*/
private $session;
/**
* Fecha o manipulador de sessão
* @param $session Session
* @return boolean
*/
public function close( Session $session ){
$this->mongo->close();
$this->session = null;
}
/**
* Destroi a sessão e seus dados
* @param $session Session
* @return boolean
*/
public function destroy( Session $session ){
$this->session->remove( array( '_id' => $session->getSessionId() ) );
}
/**
* Coletor de lixo de sessão
* @param $session Session
* @return boolean
*/
public function garbageCollection( Session $session ){
$this->session->remove( array( 'expires' => array( '$lte' => new MongoDate( time() - $session->getLifeTime() ) ) ) );
}
/**
* Abre o manipulador de sessão
* @param $session Session
* @return boolean
*/
public function open( Session $session ){
$this->mongo = new Mongo( $session->getSavePath() );
$this->session = $this->mongo->selectCollection( 'session' , $session->getSessionName() );
return true;
}
/**
* Lê os dados de sessão
* @param $session Session
* @return string
*/
public function read( Session $session ){
$data = $this->session->findOne( array( '_id' => $session->getSessionId() ) , array( 'serialized' ) );
if ( isset( $data[ 'serialized' ] ) && ( $data[ 'serialized' ] instanceof MongoBinData ) ){
return gzuncompress( $data[ 'serialized' ]->bin );
} else {
return null;
}
}
/**
* Grava os dados de sessão
* @param $session Session
* @return boolean
*/
public function write( Session $session ){
if ( !empty( $_SESSION ) ){
$this->session->save(
array(
'_id' => $session->getSessionId(),
'data' => eval(
sprintf( 'return %s;',
preg_replace( array( '/\w+::__set_state\(/' , '/\)\)/' ) , array( null , ')' ),
var_export( $_SESSION , true ) )
)
),
'serialized' => new MongoBinData( gzcompress( $session->getSessionData() ) ),
'expires' => new MongoDate( time() + $session->getSessionExpires() )
)
);
}
return true;
}
}
Bom, temos nossos manipuladores, e agora ?
Se dermos uma olhada no manual do PHP, vamos encontrar uma função bastante interessante: session_set_save_handler().
Vejamos:
Session.php
<?php
require_once 'MemcacheSessionHandler.php';
require_once 'MongoSessionHandler.php';
/**
* Gerenciador do manipulador de sessão
*/
class Session {
/**
* Tempo de expiração da sessão
* @var integer
*/
private $expires;
/**
* Lista de manipuladores de sessão
* @var array
*/
private $handlers;
/**
* Manipulador de sessão
* @var SessionHandler
*/
private $handler;
/**
* ID da sessão
* @var string
*/
private $id;
/**
* Tempo de vida dos dados de sessão
* @var integer
*/
private $lifeTime;
/**
* Nome da sessão
* @var string
*/
private $name;
/**
* Destino dos dados de sessão
* @var string
*/
private $savePath;
/**
* Instância única do gerenciador de sessão
* @var Session
*/
private static $session;
/**
* Constroi o gerenciador de sessão utilizando um manipulador de sessão
* @param $key string Chave identificadora do manipulador
* @param $handler SessionHandler O manipulador de sessão que será utilizado para
* @param $savePath string Caminho onde os dados serão salvos
*/
protected function __construct( $key , SessionHandler $handler , $savePath ){
$this->handlers = array();
$this->addSessionHandler( $key , $handler , $savePath );
}
/**
* Adiciona um novo manipulador de sessão
* @param $key string Chave identificadora do manipulador
* @param $handler SessionHandler Manipulador de sessão
* @param $savePath string Caminho onde os dados serão salvos
* @return Session
*/
public function addSessionHandler( $key , SessionHandler $handler , $savePath ){
$this->handlers[ $key ] = array( 'handler' => $handler , 'savePath' => $savePath );
if ( is_null( $this->handler ) ){
self::savePath( $savePath );
$this->handler = $handler;
}
return $this;
}
/**
* Modifica o manipulador de sessão atual
* @param string $key Chave identificadora do manipulador
* @return Session
* @throws InvalidArgumentException
*/
public function changeHandler( $key ){
if ( isset( $this->handlers[ $key ] ) ){
self::commit();
self::savePath( $this->handlers[ $key ][ 'savePath' ] );
$this->handler = $this->handlers[ $key ][ 'handler' ];
self::realStart( $this->getSessionName() , $this->getSessionId() );
} else {
throw new InvalidArgumentException( 'Manipulador não encontrado' );
}
return $this;
}
/**
* Fecha o manipulador de sessão
* @return boolean
*/
public function close(){
return $this->handler->close( $this );
}
/**
* Grava e fecha a sessão
* @see Session::write(), Session::close()
*/
public static function commit(){
session_write_close();
}
/**
* Destroi a sessão e seus dados
* @param $id string O ID da sessão
* @return boolean
*/
public function destroy( $id ){
$this->id = $id;
return $this->handler->destroy( $this );
}
/**
* Coletor de lixo de sessão
* @param $lifeTime integer Tempo de vida do lixo
* @return boolean
*/
public function garbageCollection( $lifeTime ){
$this->lifeTime = $lifeTime;
return $this->handler->garbageCollection( $this );
}
/**
* Recupera o tempo de expiração em minutos dos dados de sessão
* @return integer
* @see Session::garbageCollection()
*/
public function getSessionExpires(){
return session_cache_expire();
}
/**
* Recupera a instância do gerenciador de sessão
* @return Session
* @throws BadMethodCallException
*/
public static function getInstance(){
if ( !self::$session instanceof Session ){
throw new BadMethodCallException( 'A sessão não foi inicializada' );
}
return self::$session;
}
/**
* Caso o manipulador possua alguma operação específica que precisa
* ser utilizada diretamente, esse método por ser utilizado para recuperá-lo.
* @return SessionHandler
*/
public function getHandler(){
return $this->handler;
}
/**
* Recupera o tempo de vida dos dados de sessão para ser utilizado
* com o garbase collector
* @return integer
* @see Session::garbageCollection()
*/
public function getLifeTime(){
return $this->lifeTime;
}
/**
* Recupera o nome do cookie que será enviado ao user agent do usuário
* @return string
*/
public function getSessionName(){
return $this->name;
}
/**
* Recupera o caminho onde os dados de sessão deverão ser salvos
* @return string
*/
public function getSavePath(){
return $this->savePath;
}
/**
* Recupera os dados de sessão codificados para serem gravados
* @return string
*/
public function getSessionData(){
return session_encode();
}
/**
* Recupera o ID da sessão para ser utilizado com resgate ou gravação dos dados
* @return string
*/
public function getSessionId(){
return $this->id;
}
/**
* Abre o manipulador de sessão
* @param $savePath string Caminho onde os dados de sessão serão salvos
* @param $name string Nome da sessão
*/
public function open( $savePath , $name ){
$this->savePath = $savePath;
$this->name = $name;
return $this->handler->open( $this );
}
/**
* Lê os dados de sessão
* @param $id string O ID da sessão
* @return string
*/
public function read( $id ){
$this->id = $id;
return $this->handler->read( $this );
}
/**
* Inicia a sessão
* @param $name string Nome do cookie de sessão
* @param $id string ID da sessão
*/
private static function realStart( $name , $id ){
if ( !is_null( $name ) ){
session_name( $name );
}
session_start();
if ( !is_null( $id ) ){
session_id( $id );
}
}
/**
* Inicializa a sessão
* @param $key string Chave identificadora do manipulador
* @param $handler SessionHandler Manipulador de sessão que será utilizado
* @param $savePath string Caminho onde os dados serão salvos
* @param $id string ID de sessão
* @param $name string Nome do cookie de sessão
* @return Session
* @throws BadMethodCallException Se a sessão já tiver sido inicializada utilizando session_start()
*/
public static function start( $key , SessionHandler $handler = null , $savePath , $id = null , $name = null ){
if ( !self::$session ){
$currentId = session_id();
if ( empty( $currentId ) ){
self::$session = new Session( $key , $handler , $savePath );
session_set_save_handler(
array( self::$session , 'open' ),
array( self::$session , 'close' ),
array( self::$session , 'read' ),
array( self::$session , 'write' ),
array( self::$session , 'destroy' ),
array( self::$session , 'garbageCollection' )
);
self::realStart( $name , $id );
} else {
throw new BadMethodCallException( 'A sessão já foi iniciada utilizando session_start()' );
}
}
return self::$session;
}
/**
* Define o destino dos dados da sessão
* @param $path string
* @return string
*/
public static function savePath( $path ){
return session_save_path( $path );
}
/**
* Grava os dados de sessão
* @param $id string O ID da sessão
* @param $data string Dados da sessão
* @return boolean
*/
public function write( $id , $data ){
return $this->handler->write( $this );
}
}
Ok, vamos usar isso ai:
<?php
require 'Session.php';
$session = Session::start( 'memcache' , new MemcacheSessionHandler() , 'localhost:11211' );
$session->addSessionHandler( 'mongo' , new MongoSessionHandler() , 'mongodb://meuUsuario:minhaSenha@localhost:27017' );
var_dump( $_SESSION );
$_SESSION[ 'teste' ] = new stdClass();
$_SESSION[ 'teste' ]->nome = 'João Batista Neto';
$session->changeHandler( 'mongo' );
var_dump( $_SESSION );
$_SESSION[ 'info' ] = new stdClass();
$_SESSION[ 'info' ]->cliente = 'Fulano';
$_SESSION[ 'info' ]->secao = 'Uma seção do site';
Com isso, conseguimos gravar os dados transientes em memória e dados estatísticos no nosso MongoDB, utilizando nossa super-global $_SESSION sem ter que modificar aplicações que já utilizam essa variável.
Editado por Mário Monteiro, 28 abril 2012 - 17:06 .




