Ir para conteúdo

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

João Batista Neto

[Resolvido] Sessões PHP com NoSQL

Recommended Posts

Muita gente não sabe mas os dados de sessões PHP são gravados em disco e, como todo processo de gravação e escrita em disco, tende a ser lento.

 

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:

 

Imagem Postada

 

 

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

http://forum.imasters.com.br/public/style_emoticons/default/seta.gif http://br.php.net/manual/en/memcache.installation.php

 

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.

 

;)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Shoooooooooow de bola! Mais para frente implementarei aqui.

Compartilhar este post


Link para o post
Compartilhar em outros sites

Inevitável as cópias descaradas e sem crédito....

 

http://www.nosqlbr.com.br/sessoes-php-com-nosql.html

 

#choremos

 

Será que lhe ocorreu que a mesma pessoa postou aqui e lá no blog?

 

(...think...)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Tenho 99,99% de certeza que não.

 

Hmmmm... http://forum.imasters.com.br/public/style_emoticons/default/joia.gif

Mas quem tu acha que copiou de quem?

 

(...think...)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Desculpe, mas quem ta precisando "...think..." aqui é você. Primeiro de tudo que eu conheço muito bem o João e sei que ele não faria, em NENHUMA hipotese, uma cópia de código e propriedade intelectual sem a devida permissão e colocação dos devidos créditos.

 

E eu queria te ensinar uma coisa que me parece que ainda não sabe, que é se atentar a pequenos detalhes. Data de postagem, itens alterados no texto, itens NÃO alterados no texto e etc.

 

São alguns detalhes, como esses:

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bicho... Tu é muito inteligente! Te juro que não tinha pensado nisto!

Tu é muito Jóia! http://forum.imasters.com.br/public/style_emoticons/default/joia.gif

 

(...think...)

Compartilhar este post


Link para o post
Compartilhar em outros sites

lá no final do suposto artigo plagiado:

Posted by joao in memcached, mongodb, nosql

 

lá em baixo nos comentários:

Douglas · 1 week ago

excelente!

suponho que o joao seja o mesmo João Batista desse forum.

 

e esse douglas deve ser esse mesmo cara da placa verde...

Compartilhar este post


Link para o post
Compartilhar em outros sites

Opz, pessoal !!!!

 

O "joão" que postou no NoSQL Brasil é exatamente o mesmo João Batista Neto que postou aqui no iMasters.

 

;)

Compartilhar este post


Link para o post
Compartilhar em outros sites

Polemica resolvida

 

Não houve plagio, houve apenas multiplicação do artigo, belíssimo por sinal

Compartilhar este post


Link para o post
Compartilhar em outros sites

Opz, pessoal !!!!

 

O "joão" que postou no NoSQL Brasil é exatamente o mesmo João Batista Neto que postou aqui no iMasters.

 

;)

 

HUAhuHUAhuHauhuAhuhAHUhuAHUaHUHUahuHUahua

 

Calma ae André o João é meu broder tb e ele tb posta no nosqlbr, tente se informar antes de sair acusando por ae beleza?

 

Todos os posts externos são devidamente linkados aos originais.

(...think...)

 

Tenho 99,99% de certeza que não.

 

Ainda bem q não eram 100% hein, você errou por 0,01% http://forum.imasters.com.br/public/style_emoticons/default/clap.gif

Compartilhar este post


Link para o post
Compartilhar em outros sites

Bom, não é um tópico assim tão velho já né?

 

Enfim...

 

Testei hoje essa implementação, para comparar com a que eu tinha feito quando precisei (antes desse tópico existir) e ver se o meu código suportava múltiplos handlers.

 

E não havia problema algum, adaptei o MongoHandler aos meus namespaces, adicionei o session_set_save_handler() e voilà.

 

Mas, testando a fundo percebi uma coisa estranha, mas não sei dizer se é efeito colateral da minha implementação ou do MongoHandler.

 

Se eu seto alguma variável em $_SESSION sem usar o MongoHandler, ela é gravada tranquilamente.

 

Eu ativo o MongoHandler e esse valor setado vai pra ele certinho.

 

Daí eu adiciono um novo valor com o MongoHandler ativo e testo para efetivar a gravação. Em seguida, comento a linha que adicionei com ele ativo e, ao testar de novo, ela some do registro, mantendo apenas a anteriormente criada (antes dele ser ativado)

 

Pergunta: Mas esse valor não foi gravado nesse banco "sem banco" anteriormente? Ele não deveria ter sido apagado apenas por comentar a linha, deveria continuar lá, da mesma forma que o "handler nativo" (que só se esvazia se forçarmos um array vazio à ele)

 

Outra coisa, meio offpost, em eu estando com o handler nativo em uso, mudar para o Mongo, mas decidir, de repente, retornar ao nativo. O que deveria ser informado em session_set_save_handler()?

Compartilhar este post


Link para o post
Compartilhar em outros sites

Em primeiro lugar gostaria de parabeniza-lo João pelo post maravilhoso.

Não sei se a galera observou mais creio que partindo desse principio se torna muito facil de monitorar os usuários que estão logados no sistema gerenciando o login de modo a permitir somente um login por usuário e outras necessidades de comunicações instantâneas.

 

Um forte abraço!

 

Sucesso!

Compartilhar este post


Link para o post
Compartilhar em outros sites

×

Informação importante

Ao usar o fórum, você concorda com nossos Termos e condições.