Ir para o conteúdo

Publicidade

 Estatísticas do Fórum

  • 0 Usuários ativos

    0 membro(s), 0 visitante(s) e 0 membros anônimo(s)

Foto:

[Resolvido] Sessões PHP com NoSQL

  • Por favor, faça o login para responder
26 respostas neste tópico

#1 João Batista Neto

João Batista Neto

    Verschränkung

  • Administradores
  • 4.547 posts

Postado 29 agosto 2010 - 10:21

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

:seta: http://br.php.net/ma...nstallation.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.

;)

Editado por Mário Monteiro, 28 abril 2012 - 17:06 .

  • 0

#2 André D. Molin

André D. Molin
  • Membros
  • 2.174 posts

Postado 29 agosto 2010 - 12:21

Show de bola João!
  • 0

#3 Douglas

Douglas

    iMasters

  • Administradores
  • 6.579 posts

Postado 29 agosto 2010 - 16:03

Excelente demais!

Post completissimo.
  • 0

#4 Will Fernando

Will Fernando

    Twilight Vanquisher

  • Membros
  • 1.259 posts

Postado 29 agosto 2010 - 16:03

muito bom como sempre! parabens neto !

valwww :P

Editado por Mário Monteiro, 28 abril 2012 - 17:06 .

  • 0

#5 Suissa

Suissa

    Don't look to the sky your place is down

  • Moderadores
  • 2.684 posts

Postado 29 agosto 2010 - 17:33

Shoooooooooow de bola! Mais para frente implementarei aqui.
  • 0

#6 Rogério Y.

Rogério Y.
  • Membros
  • 91 posts

Postado 30 agosto 2010 - 09:37

Meus parabéns.
  • 0

#7 lucaswxp

lucaswxp
  • Membros
  • 1.239 posts

Postado 01 setembro 2010 - 18:02

Caraca...
Parabéns João =D
  • 0

#8 Flávio Douglas Nunes

Flávio Douglas Nunes
  • Membros
  • 50 posts

Postado 02 setembro 2010 - 16:30

Muito Bom mesmo !
  • 0

#9 André D. Molin

André D. Molin
  • Membros
  • 2.174 posts

Postado 08 setembro 2010 - 19:36

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

http://www.nosqlbr.c...-com-nosql.html

#choremos
  • 0

#10 Prog

Prog

    Enterprise Search Specialist

  • Moderadores
  • 5.161 posts

Postado 08 setembro 2010 - 20:44

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

http://www.nosqlbr.c...-com-nosql.html

#choremos


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

(...think...)
  • 0

#11 André D. Molin

André D. Molin
  • Membros
  • 2.174 posts

Postado 08 setembro 2010 - 20:47

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

#12 Prog

Prog

    Enterprise Search Specialist

  • Moderadores
  • 5.161 posts

Postado 08 setembro 2010 - 20:52

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


Hmmmm... :joia:
Mas quem tu acha que copiou de quem?

(...think...)

Editado por Mário Monteiro, 28 abril 2012 - 17:06 .

  • 0

#13 André D. Molin

André D. Molin
  • Membros
  • 2.174 posts

Postado 08 setembro 2010 - 22:06

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:

Editado por Douglas, 04 maio 2012 - 19:41 .
Imagens não existem.

  • 0

#14 Prog

Prog

    Enterprise Search Specialist

  • Moderadores
  • 5.161 posts

Postado 08 setembro 2010 - 22:12

Bicho... Tu é muito inteligente! Te juro que não tinha pensado nisto!
Tu é muito Jóia! :joia:

(...think...)

Editado por Mário Monteiro, 08 maio 2012 - 18:38 .

  • 0

#15 VascoDaGama

VascoDaGama
  • Membros
  • 98 posts

Postado 08 setembro 2010 - 22:39

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...
  • 0

#16 João Batista Neto

João Batista Neto

    Verschränkung

  • Administradores
  • 4.547 posts

Postado 09 setembro 2010 - 10:29

Opz, pessoal !!!!

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

;)

Editado por Mário Monteiro, 28 abril 2012 - 17:05 .

  • 0

#17 Mário Monteiro

Mário Monteiro

    Amo Você Fabíola. Ao seu lado tudo é perfeito.

  • Administradores
  • 33.239 posts

Postado 09 setembro 2010 - 18:04

Polemica resolvida

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

#18 Suissa

Suissa

    Don't look to the sky your place is down

  • Moderadores
  • 2.684 posts

Postado 14 setembro 2010 - 11:49

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% :clap:

Editado por Mário Monteiro, 28 abril 2012 - 17:03 .

  • 0

#19 Bruno Augusto

Bruno Augusto

    Sou Bobo Também ¬¬

  • Moderadores
  • 6.330 posts

Postado 04 outubro 2010 - 10:52

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()?
  • 0

#20 Marcio Mauricio

Marcio Mauricio
  • Membros
  • 23 posts

Postado 16 setembro 2012 - 12:46

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!
  • 0