Utilizando Sessões em PHP de forma inteligente

Artigo que explica os conceitos de sessões em sistemas Web, como utilizá-las de forma correta, como configurá-las no PHP e como usar a variável $_SESSION.

macaco pensando
Introdução

Armazenamento de dados em sessão é um mecanismo oferecido pelo PHP que permite que a aplicação armazene/recupere dados específicos por usuário e compartilhados entre diferentes scripts. Este recurso é comumente utilizado para garantir a autenticação do usuário para que ele só precise informar a sua credencial uma única vez (log-in). Porém, este recurso também é importantíssimo para criar aplicações com nível de dinamicidade alto, e que requer o armazenamento constante de dados temporários, como uma cesta de compras, por exemplo. Neste artigo, veremos como utilizar a sessão de forma inteligente e otimizada, além de aprofundar em seu funcionamento.


O funcionamento das sessões

A primeira questão a ser explorada está relacionada aos recursos usados para o funcionamento das sessões. Antes de apresentar estes recursos, é preciso entender como as sessões funcionam.

O funcionamento básico segue estas etapas:

  1. O cliente (navegador) acessa uma página (um script no servidor) que prevê a abertura de uma sessão.
  2. O script requer a abertura de uma sessão, mas como não recebe nenhum ID de sessão informado pelo cliente, ele gera um ID aleatório, cria uma sessão associada a este ID, e solicita ao cliente o armazenamento deste ID, para que ele o informe para as demais páginas que acessar (seguindo algumas restrições, como prazo, domínio, etc).
  3. O cliente recebe o pedido e, de alguma forma, armazena esse ID no navegador.
  4. O cliente acessa outra página que também prevê a abertura de sessão e, neste ato, também informa a esta página (de algum modo) qual é o ID de sessão que ele recebeu do servidor.
  5. O script requer a abertura de uma sessão e, como recebe um ID do cliente, verifica se existe uma sessão com aquele ID e, caso exista, recupera os dados da sessão, podendo utilizá-la no script.
  6. ...

Este é o funcionamento. Não citei o recurso que o servidor usa para guardar a sessão, nem que recurso o cliente (navegador) usa para guardar o ID informado pelo servidor. Isso porque podem existir diferentes formas.

Armazenamento de dados da sessão

O servidor pode guardar sessões em arquivos, em Banco de Dados ou qualquer outro mecanismo de persistência de dados baseado em chave/valor (que seja suportado por PHP). Por padrão, o mecanismo usado é o armazenamento em arquivos no servidor. Estes arquivos ficam em um diretório reservado e seguro no servidor, onde o ID da sessão está presente no próprio nome do arquivo e o conteúdo do arquivo é uma string serializada dos dados da sessão (se você não sabe o que é uma string serializada, leia sobre as funções serialize e unserialize).

Armazenar muitos arquivos num único diretório pode ter uma baixa performance, dependendo do sistema de arquivos utilizado, do tamanho médio dos arquivos, etc. Talvez, seja interessante avaliar uma solução de armazenamento em banco de dados ou em memória. Você pode optar por um SGBD mais completo, mas pode ser um mais simples, como o SQLite ou o Firebird. Para configurar o PHP para usar um mecanismo de armazenamento diferente do padrão, é preciso implementar alguns métodos reservados, depois registrá-los com a função session_set_save_handler, antes de usar as funções de sessão. Esta função recebe 6 parâmetros do tipo callback, que representam funções que fazem as seguintes operações:

  • open - abre uma sessão.
  • close - fecha uma sessão.
  • read - obtém o valor da sessão a partir da sua chave (nome da variável).
  • write - armazena um valor na sessão associado a uma chave.
  • destroy - apaga os dados da sessão a partir do ID de sessão.
  • gc - limpa as sessões consideradas lixo (garbage collector).

A partir do PHP 5.4, a função session_set_save_handler passou a poder receber também um objeto que implementa a interface SessionHandlerInterface. Neste caso, não é necessário passar vários callbacks, apenas um objeto.

Para obter mais detalhes sobre como implementar seu próprio mecanismo de armazenamento, veja a documentação da função session_set_save_handler, que apresenta, inclusive, um exemplo de implementação.

Note que você continua usando as funções de sessão definidas pelo PHP, ou seja, session_start e session_destroy, além de acessar $_SESSION para armazenar e recuperar valores da sessão. A diferença é que, ao invés de usar a implementação padrão, o PHP irá chamar as funções que você registrou, para os respectivos propósitos. Tudo feito de forma transparente para quem usa a sessão. Se você implementou algumas das funções de maneira indevida, a utilização da sessão será impactada também.

Para utilização de sessões ainda mais rápidas, você pode optar por deixá-la em memória RAM. Uma solução conhecida e viável é usar o memcached, que pode até ficar em máquina(s) dedicada(s). Usar sessões em memória tem altíssima performance, porém, é preciso estar atento às configurações do tempo de vida da sessão e à capacidade de memória do servidor, para evitar estouro de memória.

Para utilizar o memcached para sessões, é necessário configurar um servidor com o serviço memcached, depois instalar o módulo pecl memcache ou o memcached no servidor web, e configurar as diretivas do php.ini: session.save_handler para "memcache" ou "memcached" e session.save_path apontando para o(s) servidore(s) configurados com memcache.

Para saber as diferenças entre estas extensões, recomendo este artigo: PHP Memcache vs. Memcached. E, para saber como instalar extensões pecl, leia o artigo: Instalação de módulos PEAR e PECL.

Armazenamento de ID de sessão pelo cliente

O armazenamento do ID de sessão pelo cliente (navegador) pode se dar de duas maneiras: (i) em um cookie de sessão ou (ii) usando trans_sid.

Usar cookies é o mecanismo mais recomendado e seguro para o propósito. O navegador guarda o ID em um cookie com um nome que pode ser configurado pelo script (através da função session_name) e, para cada acesso a uma página (do escopo especificado pela sessão), informa o valor do seu ID de sessão.

Uma dica é utilizar um nome diferente para cada usuário que acessa a página. Desta forma, dificulta o "roubo de sessão", ou seja, um usuário descobre o ID de sessão de outro, cria um cookie com o nome reservado e valor do ID de sessão e acessa a página que requer autenticação. Desta forma, este usuário malicioso irá se passar por outro que já efetuou a autenticação. Uma forma de se gerar um nome diferente por usuário é utilizar o IP e a string informada pelo User-Agent. Por exemplo:

session_name(md5('sal'.$_SERVER['REMOTE_ADDR'].'sal'.$_SERVER['HTTP_USER_AGENT'].'sal'));

Observação: no lugar de cada 'sal', você pode colocar um texto secreto, mas não aleatório.

Outra dica é configurar adequadamente as características do cookie de sessão através do método session_set_cookie_params, que recebe o tempo de duração do cookie (passe zero para a sessão durar até o navegador fechar), o path da URL onde o cookie é válido, o domínio onde o cookie é válido, se ele só será passado em conexão segura (HTTPS), e se ele terá a flag httpolny (para evitar que o cliente acesse o cookie via JavaScript, por exemplo).

Já o mecanismo trans_sid basicamente armazena o ID de sessão internamente pelo navegador e, para cada página acessada, o navegador informa o valor do ID de sessão através da query string da URL, ou seja, no próprio endereço da página. Isso pode ser um perigo, especialmente se o usuário esquecer a sessão aberta (não acessar a ferramenta "sair", que destrói a sessão dele), e o ID da sessão ficar exposto no histórico de seu navegador.

Para usar apenas cookies, basta utilizar as seguintes linhas antes da chamada de session_start:

ini_set('session.use_cookies', '1');
ini_set('session.use_only_cookies', '1');
ini_set('session.use_trans_sid', '0');

Note que são diretivas de configuração do PHP, que podem ser especificadas no arquivo php.ini. Para mais detalhes, leia o artigo "Configurações do PHP".


Utilização da sessão

Considerando que uma sessão possui um conjunto de valores definidos através de chaves/valores, que são serializados/desserializados, é notável que esta conversão tenha um custo. Guardar sessões com dados gigantescos, portanto, pode ser um problema, pois, a cada chamada de session_start este pacotão de dados deverá ser convertido de string para um array associativo. Uma dica útil é armazenar em sessão apenas o essencial. Por exemplo, você não precisa guardar todos dados do usuário logado em sessão. Você pode guardar apenas o ID (campo de identificação única do usuário) e guardar os dados do usuário logado em algum mecanismo de cache próprio.

Nesta mesma linha, também podemos citar o acumulo de muitos dados que já não são mais necessários. Por exemplo, dados que foram guardados em sessão para algum propósito, mas já não são necessários. Nestes casos, é útil que se limpe tudo que não é mais utilizado, para evitar o problema das sessões gigantes.

Mas se mesmo assim você ainda precisa utilizar a sessão para guardar dados em cache, ou seja, apenas para agilizar algo que já foi feito antes, então reserve uma posição de $_SESSION só pra isso e limite a quantidade de dados desta posição. Por exemplo, se reservamos a posição $_SESSION['cache'], então ao inserir um novo valor neste array, certifique-se que ele não passou de N posições e, caso tenha passado, remova a posição mais antiga com array_shift. Veja o exemplo que limita a posição $_SESSION['cache'] a 10 elementos apenas:

if (count($_SESSION['cache']) > 10) { 
    array_shift($_SESSION['cache']);
}

Lembre-se que um valor armazenável em cache é aquele que você consegue obter (ou calcular) novamente caso o valor não esteja em cache.


Concorrência entre Nomes de Chaves

A concorrência de nomes de chaves usadas na sessão se dá na medida em que qualquer script pode acessar qualquer possição de $_SESSION e, portanto, criar posições neste array com nomes dos mais variados. Se um script utiliza a posição $_SESSION['cod_usuario'] para um propósito e um outro script utiliza a mesma posição para outro propósito, o valor será sobrescrito em algum momento.

Para evitar este tipo de inconveniente, basta definir algumas regras de uso da variável $_SESSION. Por exemplo, estipular que o primeiro nível desse array será indexado pelo nome completo do arquivo. Então, se dois arquivos armazenarem valores em $_SESSION[__FILE__]['cod_usuario'], cada um estará armazenando um valor em uma posição diferente da sessão, afinal, a constante mágica __FILE__ guarda sempre o nome do arquivo corrente, que é diferente entre os dois scripts.

Usar o nome do arquivo pode ser útil quando o dado da sessão só é usado pelo próprio script, mas é problemático quando este dado é compartilhado entre vários scripts. Nestes casos, é recomendado reservar as chaves do primeiro nível de indexação do array $_SESSION com palavras intuitivas. Por exemplo, para manipular a sessão em uma classe, utilizar sempre a posição $_SESSION['classe'][__CLASS__]. Neste caso, se uma classe precisar de um dado de sessão de outra, basta ele informar o nome da outra classe. De forma semelhante, isso pode ser aplicado aos módulos da sua aplicação. Neste caso, cada módulo deve ter um cuidado especial para saber exatamente quais posições ele manipula, para não ocorrer concorrência de chaves entre o próprio módulo.


Conclusão

Este artigo veio para destrinchar um assunto muito importante, mas que nem todos programadores PHP tem domínio completo.

Espero que tenham gostado e deixem suas criticas e sugestões.

42 comentários

Paulo Henrique disse...

Ainda tenho que estudar um bocado de php. Me explica depois como configurar o Feedburner para enviar os conteúdos pelo e-mail com aqueles links de leia-mais. É por lá ?

Abç !!

betolima disse...

Rubens, não consegui compreender bem a questão de array_shift no sessao cache.
Na verdade não entendi o conceito e propósito.
Você já me deu algumas dicas de sessão la nas listas php e ja uso elas.
function iniciaSessao(){
if(!isset($_SESSION)){
ini_set('session.use_cookies', '1');
ini_set('session.use_only_cookies', '1');
ini_set('session.use_trans_sid', '0');
@session_start();
}
}
Mas no restante da minha aplicação apenas recupero os valores das sessões criadas.
Ex: $_SESSION['usuario'] = 'login do cara vindo do banco'.
Pergunto, esqueci de fazer algo pra deixar 100% seguro?

Rubens Takiguti Ribeiro (autor do blog) disse...

Então, Beto: sessões não são utilizadas apenas para guardar o usuário logado. Serve também para guardar dados em cache para deixar algumas ferramentas mais rápidas. Você calcula uma vez e guarda em cache, depois, antes de calcular novamente, você verifica se a informação já está em cache e, caso esteja, apenas recupera este valor sem precisar recalcular.

Quando você usa a sessão como cache, você pode especificar uma posição da $_SESSION para guardar dados através de um array. A dica aqui foi limitar o tamanho deste vetor para ele não ficar sobrecarregado (o array_shift remove o elemento inserido inicialmente, ou seja, as informações mais antigas são descartadas). Isso é feito porque o $_SESSION tem um custo para guardar e recuperar valores. Se você deixa ele muito grande, todas páginas começam a ser afetadas quanto a performance.

Sobre a segurança em guardar o usuário logado, ela está muito mais relacionada com o que você faz antes de guardar qualquer valor em sessão. Por exemplo, se o sistema tem falha de SQL Injection, vai consultar indevidamente um usuário sem as credenciais corretas e, consequentemente, será guardado em sessão algo inválido. Ou Seja, deve-se reforçar a segurança antes do código do usuário ser efetivamente armazenado.

Cela disse...

Li, utilizei e adorei...
meu carrinho de comrpas ficou muito mais fácil de entender e dar manutenção...

Parabéns

Codigo Secreto disse...

Olá Rubens, estava procurando sobre sessões e achei seu blog. Umas dúvidas, tem como ver quanto tempo falto para acabar a sessão de um $_SESSION? Toda vez que mudo de pagina .php tenho que chamar o session_start('nome_sessao') para recuperar os dados da sessão para aquele documento, mas isso acho que reinicia o tempo da sessão, e não mantem o tempo antigo, estou certo?

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Código Secreto

Acredito que não exista algo nativo do PHP para saber quanto tempo a sessão ainda tem. Porém, você pode reservar uma variável na sessão para indicar o instante em que a ela foi criada e, com isso, você consegue determinar quanto tempo ela ainda tem.

Isso é bem simples:
if (!isset($_SESSION['start_time'])) {
$_SESSION['start_time'] = time();
}

Ao chamar o session_start, o tempo não é reiniciado (aliás, não precisa passar nenhum parâmetro para esta função). O que define o "tempo" da sessão é a diretiva "session.gc_maxlifetime", que você pode definir assim:

ini_set('session.gc_maxlifetime', $duracao);

(note que a duração precisa ser em segundos)

Gustavo Araujo disse...

Olá Rubens blz, gostei do artigo.
Tenho um dúvida, por exemplo, quero que uma sessão termine após 10 segundos, tenho as configurações abaixo:
session.gc_probability: 1
session.gc_divisor: 1
session.gc_maxlifetime: 10
Após 10s era para a sessão encerrar correto? Já que gc_probability/session.gc_divisor = 100% que o garbage colector irá atuar, mas para mim não funciona, a sessão ainda continua ativa, acho que não sei bem como o garbage colector funciona...

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá Gustavo,

Parece que você entendeu as diretivas de probability e divisor, e configurou para executar o garbage collection em todas chamadas do session_start.

Já a diretiva session.gc_maxlifetime define o tempo para que uma sessão seja considerada lixo. Só que este tempo é baseado no mtime (data de modificação) do arquivo onde está a sessão.

Ou seja, se você define 10 segundos, então se a sessão não for modificada por 10 segundos, ao chamar o session_start a sessão será carregada no $_SESSION e, em seguida, apagada. Acredito que o mais conveniente seria primeiro apagar, depois criar uma nova.

Outra coisa que pode gerar confusão é que o ID da sessão pode ser o mesmo que aquele que acabou de ser apagado. Vou te mandar um arquivo por e-mail para você testar aí.

Ricardo disse...

Boa tarde,
Estou estudando sobre sessoes em php e achei o seu blog. Primeiramente parabens pelo post, é excelente.
Só pintou uma duvida :
Se inicio a sessão assim :
session_start();
$_SESSION['xyz'] = $xyz;

Ele por default gerará um cookie ? E se várias pessoas abrirem minha pagina, uma irá utilizar a seção da outra ? Ou a seção fica no usuário ?
Teria algum livro que você me recomendaria para este fim ?

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Ricardo.
Os dados da sessão ficam no servidor e são identificador por um ID único. No cliente (navegador) é gerado um cookie que guarda apenas esse ID e, desta forma, o servidor sabe quais dados de sessão cada usuário pode acessar.

Não é obrigatório o navegador guardar o cookie. Existe um outro mecanismo que o navegador guarda o ID e, toda vez que acessa uma nova página, envia esse ID para o servidor. Porém, essa forma é pouco segura (prefira os cookies mesmo).

De fato, se alguém cria um cookie no seu navegador com o ID de outra pessoa, ela pode conseguir acessar o site como se fosse a outra pessoa. Isso se chama roubo de cookie de sessão. Há maneiras de previnir isso com algumas estratégias usadas no PHP (por exemplo, checar o IP, User-Agent, ou outra informação relevante).

Não sei se existe livro específico sobre sessões. Normalmente há um capítulo que trata do assunto.

Ricardo disse...

Primeiramente obrigado pelas respostas.
Vejamos se entendi da forma correta.
Se eu setar um valor na seção $_SESSION['xyz'] para um usuário X que fez o login no site.
E ao entrar no meu site eu verifico e mostro este valor em tela, ele será comum a todos que acessem ? Ou cada usuário em que eu printar a $_SESSION['xyz'] vai obter seu valor unico ?
Ou o ideal seria ramdomizar o id informado no session? Ex.: $_SESSION[$ID_RAMDOM]

Rubens Takiguti Ribeiro (autor do blog) disse...

Ricardo,

Acho que você está confundindo o "ID de sessão" com a "chave do vetor $_SESSION". São coisas distintas. O ID de sessão é gerado aleatoriamente para cada usuário.

A variável $_SESSION é carregada por usuário, de acordo com o ID de sessão. Dois usuários diferentes tem ID de sessão diferente, logo, a variável $_SESSION terá valores distintos.

Geovanek disse...

Muito interessante seu tópico.

Uma dúvida, estou desenvolvendo um site para coleta de dados de uma pesquisa de mestrado.

Mas antes dos participantes fazerem o cadastro, eles devem aceitar o termo de consentimento e este eu salvo em uma sessão (com data e hora que foi aceito) e já crio um cadastro no banco de dados para essa pessoas. Depois que ela completar o cadastro, os dados serão salvos no BD no cadastro que tem como Valor_x a sessão iniciada anteriormente.

A minha dúvida é, se várias pessoas estiverem ao mesmo tempo no site fazendo isso, pode haver perda de sessão? Pois está havendo alguns problemas, pessoas que tentam fazer o cadastro e ocorre erro. E já testei milhões de vezes e nunca deu problema.

Rubens Takiguti Ribeiro (autor do blog) disse...

Kung Lao, nunca ouvi dizer sobre perda de sessão pelo volume de acessos. Algumas possibilidades que penso que poderiam causar problemas seriam:
* o problema não ser exatamente na sessão, mas no BD, que está sendo sobrecarregado.
* o problema não ser exatamente na sessão, mas no servidor http que está sendo sobrecarregado (alguns processos podem ficar na fila e estourar o limite de tempo de execução do php - diretiva max_execution_time).
* o tempo dos cookies de sessão está baixo, então as pessoas perdem a sessão antes de terminar o cadastro (diretiva session.cookie_lifetime);
* o tempo de vida da sessão está baixo, então o garbage collector limpa a sessão antes do usuário terminar o cadastro (diretiva session.gc_maxlifetime);

Enfim, experimente gerar alguns logs para determinar mais precisamente onde está o problema. Não sei como você determinou que as pessoas estão perdendo a sessão, mas talvez o problema seja até outro.

Rubens Takiguti Ribeiro (autor do blog) disse...

Anônimo,

Para armazenar informações em sessão, você chama a função session_start() e então armazena os valores em $_SESSION, que é um array. A forma como você a controla é livre.

Porém, para fazer uma agenda, não me parece útil utilizar a sessão para armazenar os dados. Afinal, quando o usuário fecha o navegador, ele perde o cookie de sessão e, consequentemente, vai perder os dados da sessão.

O ideal é que você armazene este tipo de informação em um banco de dados.

Unknown disse...

Como eu faço para impedir que duas pessoas façam logins simultâneos com o mesmo login e senha?
A ideia é impedir que um usuário repasse seus dados e que outras pessoas possam utilizar um sistema.

Rubens Takiguti Ribeiro (autor do blog) disse...

Luciano, neste caso, uma ideia é você registrar em BD um hash baseado no ID de sessão do usuário. Por exemplo: md5(session_id())

Assim que ele loga, você atualiza o hash que está no BD.

Em cada página, você gera o hash baseado no ID de sessão atual e compara com o hash que está em BD. Se for igual não faz nada, mas se for diferente, você desloga o usuário.

Ou seja, o segundo login "derruba" o primeiro.

Se você quer o contrário, é mais complicado, pois precisaria garantir que o usuário fez o logout, mas a ideia é similar.

MegaBrasil Host disse...

E aí, post antigo que ajuda bastante gente! Me diz uma coisa, quero fazer um sistema de login, seguro é claro, e tenho uma dúvida sobre sessões. Quando salvo um usuário no banco de dados, eu salvo um hash da senha, e o email do usuário. É correto e seguro eu salvar na sessão o ip do usuário, navegador, esse hash e o e-mail do usuário em sessão? Quais os dados que você acha que é seguro deixar em sessão para manter o usuário logado?

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, MegaBrasil Host
Na minha opinião, o hash da senha do usuário não é útil para nada, a não ser a lógica para autenticar na aplicação (e, opcionalmente, para acessar alguma ferramenta de troca de senha ou alguma operação crítica que exija a autenticidade mesmo após o login). Logo, acho que não vale a pena colocá-la na sessão.

Creio que o ideal seja armazenar em sessão apenas o ID de identificação única de usuários (pode ser a chave primária do usuário, ou o login, ou algo que o identifique sem ambiguidade) e, por segurança, algumas informações relacionadas ao navegador (User-agent) e/ou IP, que normalmente não mudam de acesso para acesso de um mesmo usuário.

Assim, a cada acesso podem ser confrontados os dados do navegador + IP com o que há em sessão para garantir que não houve roubo de ID de sessão. Aliás, para maior segurança ainda, você pode armazenar os dados do navegador + IP do usuário que logou de forma criptografada (por algum algoritmo de criptografia de via única). Exemplo:

$hash = md5($sal . $_SERVER['HTTP_USER_AGENT'] . $_SERVER['REMOTE_ADDR']);

Desta forma, mesmo se alguém conseguir acesso aos dados da sessão, terá grande dificuldade de passar os parâmetros necessários para produzir o hash requerido.

Ainda no escopo de segurança, existe a diretiva de configuração "session.referer-check":
http://www.php.net/manual/en/session.configuration.php#ini.session.referer-check

Ela obriga que toda requisição tenha uma determinada substring para a sessão ser considerada válida. Isso pode ser usado em conjunto com a verificação de dados do navegador + IP.

Os outros dados do usuário como "nome", "e-mail" (ou login), ou outros dados que são usados com frequência na aplicação, podem ser armazenados em um cache separado (como APC, Memcache, ou na própria sessão), pois caso sejam apagados do cache, ainda poderão ser recuperados do BD, utilizando o ID guardado em sessão. A segurança quanto ao meio de armazenamento varia de um para o outro, mas, por via de regra, prefira não armazenar dados muito sigilosos como cartões de crédito ou algo do gênero.

Carlos Telles disse...

Olá Rubens, ótimo artigo. Mas estou com uma dúvida e um problema.
No caso de gravar em banco de dados quando o usuário logar para manter registrado os usuários logados, como vou marcá-lo como deslogado caso ele saia do sistema sem realizar o logoff, simplesmente saindo do navegador ou desligando a máquina? Eu não teria aí uma ação para fazer o update em banco de dados. Queria saber se existe uma forma de ver todas as sessões existentes no servidor PHP e ver os IDs das mesmas, pois dessa forma poderia comparar com o banco de dados e ver que sessão corresponde a cada usuário.

Tenho um exemplo para o mesmo: suponhamos que eu meu sistema eu tenha uma regra de login por licenças de usuário, onde o cliente contratou cinco licenças para logar simultaneamente, porém ele pode ter mais que cinco usuários. Neste caso preciso impedir um sexto usuário de realizar o login. Tem alguma sugestão para isso?

Desde já, muito obrigado!

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Carlos

Ótima pergunta. Quando você usa a sessão em banco, deve registrar as funções de callback que realizarão cada oparação necessária (session_set_save_handler). Se ver a documentação desta função, vai notar que um dos callbacks necessários é o gc (garbage collector). O próprio PHP se encarrega de chamar esse callback de tempos em tempos e esse callback é que deve eliminar sessões consideradas "abandonadas".

Veja mais detalhes em:
http://php.net/manual/en/function.session-set-save-handler.php

Sobre configurar o "tempo" em que o garbage collector será chamado, leia sobre estas configurações do PHP:
session.gc_divisor
session.gc_maxlifetime
session.gc_probability

http://www.php.net/manual/en/ini.list.php

Hayttle Soljnivisk disse...

Primeiramente, parabéns pelo artigo Rubens.
Estou trabalhando num projeto em que a questão envolve performance e segurança nos acesso às informações e gravação no BD, pois necessito que vários usuários simultaneamente façam login na página, efetuem algumas operações de tempo em tempo e seja gravado no banco de dados essas operações.
A questão é: Como fazer tais operações autenticando os usuários sem comprometer a performance do servidor e garantir a segurança?
Qual a melhor forma, utilizar sessões armazenando as operações dos usuários em BD, memcached, cookies, etc....????

Obrigado

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Hayttle Soljnivisk
Normalmente a autenticação de usuários não compromete a performance da aplicação. Trata-se de uma operação relativamente simples. A principal preocupação quanto a ela é que o login seja único por usuário e possua um índice no banco de dados, para agilizar a consulta. Você faz a consulta dos dados do usuário pelo login e então confere a senha. A senha, por questões de segurança, deve ficar codificada. Há várias opções de codificação em PHP:
http://rubsphp.blogspot.com.br/2010/11/autenticacao-e-criptografia-de-senhas.html

Quanto ao armazenamento de dados resultantes das operações, normalmente não faz sentido guardá-los em cookie (pois os cookies ficam no navegador do cliente), e nem em memcache (que fica apenas em memória). Sem detalhes sobre o que precisa, eu diria que o ideal é guardar em BD. Porém, pode ser que faça sentido utilizar outro meio, dependendo do cenário. As vezes um banco NoSQL faça sentido e tenha melhor performance.

Hayttle Soljnivisk disse...

O cenário basicamente é o seguinte: São provas online que o candidato fará. E ele tem 2 horas para realizar a mesma.
A questão: salvar de tempo em tempo essas questões que vão sendo respondidas, pois caso ocorra algum problema na conexão ou travamento, é reaberto o browser (a prova) e continua a responder, sem perder as questões já respondidas, que buscarão no BD.
Então em relação a performance, como garantir um bom desempenho nesses envios das resposta ao servidor, de que forma armazenar, já que pode ocorrer envios simultâneos pelos candidatos e que não sobrecarregue o servidor? É gravar as sessões em BD?

Rubens Takiguti Ribeiro (autor do blog) disse...

Neste caso, creio que não precise de nada muito sofisticado: basta gravar em banco de dados mesmo. Se colocar em sessão, o usuário pode fechar o navegador e perder o cookie com a chave da sessão. Sobre guardar as respostas periodicamente, é uma opção, mas é mais simples guardar sob demanda utilizando ajax. Conforme o usuário responde alguma, já manda aquela resposta via ajax para ser gravada no banco. A não ser que você tenha milhares de usuários simultâneos, não há com que se preocupar.

Guilherme disse...

Boa Tarde Rubens, parabéns pelo artigo.
Tenho uma dúvida em relação a performance, acredito eu, tenho uma aplicação (formulário) que é divido em 3(três) partes, dados pessoais do candidato, endereço e contato e por último habilitações técnicas do mesmo. O candidato ao se inscrever tem a possibilidade de avançar e voltar para poder alterar dados que ele tenha preenchido equivocadamente. Eu estou utilizando session para gravar todos os dados do usuário e ao final do cadastramento faço o salvamento dos dados em um banco de dados (mysql). A minha pergunta é em relação a performance, esta seria a melhor maneira? Por ser uma página que recebe muitos cadastros eu gostaria de saber se eu não estou de alguma forma prejudicando a performance da aplicação, ou sobrecarregando o servidor, existe uma outra alternativa para o usuário conseguir ir e voltar nas páginas de cadastro sem perder os dados já preenchidos e claro sem que sobrecarregue o servidor? Muito Obrigado Rubens!

Rubens Takiguti Ribeiro (autor do blog) disse...

Olá, Guilherme
Tudo vai depender de que tipo de sessão está usando. Se está usando sessões com memcache, sabemos que a memória é muito rápida, mas também é limitada. Você precisa fazer uma estimativa do tamanho que cada usuário vai gastar em média e a quantidade de usuários simultâneos que fazem cadastro. Para estimar o tamanho de cada sessão, basta montar um array com os dados de um usuário e fazer um serialize.
Porém se usa sessões em arquivo, normalmente não será problema. Naturalmente que o espaço em disco também é limitado, mas geralmente tem maior capacidade. Por outro lado, a performance é inferior.
Além disso, precisa lembrar que nesta abordagem, se o usuário abandona o cadastro, os dados acabam sendo perdidos. Já guardando em banco, você tem a persistência dos dados, mas o acesso a base de dados aumenta.
Enfim, tudo é uma questão de por da balança e avaliar seus recursos e requisitos.

Guilherme disse...

Entendi Rubens, é que sempre utilizei SESSION para carregar os dados do cadastro dos usuários entre as páginas (indo e voltando), mas pesquisando um pouco comecei a reparar que usa-se mais session para autenticação de usuários e não para guardar dados inseridos, por isso questionei se estava fazendo da maneira correta ou se existiria uma forma melhor para fazer isso. Já vi alguns comentários em que desenvolvedores utilizam o hidden para transitar os valores entre páginas não sei se esta seria uma melhor alternativa em relação a se utilizar session já que não estaríamos guardando nada no servidor (/tmp) apenas fazendo requisições. Na verdade quero me adequar ao que seria melhor para utilizar sem perder performance na aplicação e no servidor. Acredito que utilizo as seções em arquivo, só faço o start da session e salvo os valores dos post no mesmo. Talvez seja o modo como montei a aplicação ao invés de avançar e retroceder mudando de página, poderia fazer este avanço e retrocesso via jquery simulando ao usuário que está havendo a troca de página, porém a aplicação estaria na mesma página e assim quando fosse gravar os dados na página de salvar levaria os dados via post, não sei se fui claro. Muito obrigado pelas informações Rubens. Abç

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi, Guilherme
Na verdade utilizar sessões para guardar o usuário autenticado é uma das utilizações mais comuns das sessões, porém, não se restringe a isso. O que você fez não é errado. Creio que o ideal é que faça uma reflexão sobre o que são sessões e qual é o seu propósito.

No meu entendimento, sessões servem para guardar dados relacionados a uma visita de um usuário, de modo que ajude o programador rastrear algumas coisas que o usuário fez ao longo desta visita.

No caso da autenticação, guardamos a informação de qual foi o usuário que se autenticou com sucesso durante a visita. No seu caso, são os dados que o usuário preencheu na etapa 1, 2 ou 3 de cadastro, enquanto fazia sua visita. Ou seja, está certíssimo no meu entendimento.

A grande questão é saber que:

* os dados da sessão serão perdidos se você não persistí-los em algum lugar como um banco de dados, por exemplo.

* você pode escolher diferentes modos de guardar/recuperar os dados da sessão através da diretiva de configuração "session.save_handler" (http://au2.php.net/manual/en/session.configuration.php#ini.session.save-handler), e que cada alternativa possui peculiaridades quanto a performance ou capacidade de armazenamento.

Passar dados via hidden também é uma alternativa, porém, é preciso se atentar ao fato de que, mesmo passando um dado via hidden, você precisa validá-lo no lado do PHP, pois os usuários podem alterá-lo com relativa facilidade.

Enfim, espero que a discussão ajude em algo.

Anônimo disse...

Tenho o seguinte problema, tenho dois sistemas Web no mesmo servidor, exemplo tenho uma pasta no meu Servidos, chamada "sistemas", dentro dela tenho os meus sistemas, exemplo tenho dentro dessa pasta mais duas pastas, uma "sistema1" e "sistema2", quando estou logado no "sistema1" esta tudo certo, mas quando logo no mesmo navegador com o "sistema2", ele me desloga do 1, e caso eu feche o "sistema2" rapidamente, o "sistema1" pega a sessão do outro, descobri que isso tem a ver com o session_start();
Mas eu preciso dele pois uso alguma das variáveis de sessao como o ID e nome de quem logou, isso preciso em ambos os sistema.
Teria alguma forma de eu conseguir ter essa sessão em ambos sem deslogar um ao outro e no mesmo navegador?

João Luis disse...

Adorei o post e tenho uma dúvida que é exatamente igual a ultima, que ainda não teve resposta.
Estou tentando pegar o RETORNO do pagseguro, no ato de uma compra, para salvar na tabela de pedido, mas como ao entrar no pagseguro eu perco a SESSAO do meu sistema, não sei como fazer. Se alguem puder me ajudar, vou agradecer muito.

Unknown disse...

pessoal, achei interessante o tutorial, mas minha duvida nao foi sanada aqui... eu desejo fazer o seguinte: pegar um endereço pré montado (Url) e concatenar com o nome de um arquivo que vira atraves de um bando de dados sql VEJA EXEMPLO: ("http://localhost/video aulas/videos/") a variavel me traria o nome do arquivo e seu formato, ou seja, ficaria assim: http://localhost/video aulas/videos/arquivo.mp4... o problema e que o player não acha o video... da erro... acho que nao to conseguindo fazer a sessao jogar o nome do video onde deveria... =/

Unknown disse...

pessoal, achei interessante o tutorial, mas minha duvida nao foi sanada aqui... eu desejo fazer o seguinte: pegar um endereço pré montado (Url) e concatenar com o nome de um arquivo que vira atraves de um bando de dados sql VEJA EXEMPLO: ("http://localhost/video aulas/videos/") a variavel me traria o nome do arquivo e seu formato, ou seja, ficaria assim: http://localhost/video aulas/videos/arquivo.mp4... o problema e que o player não acha o video... da erro... acho que nao to conseguindo fazer a sessao jogar o nome do video onde deveria... =/

Rubens Takiguti Ribeiro (autor do blog) disse...

Celio, não entendi direito seu problema. Em todo caso, me parece que seu problema está em manipulação de nomes de arquivos. Segue um artigo sobre o assunto:
http://rubsphp.blogspot.com.br/2011/02/caminhos-de-arquivos-e-diretorios.html

Unknown disse...

Rubens, parabéns pelo artigo. Como posso gerar esse cache e depois consultar ele. Ex:
Tenho um form em html com 5 elementos do tipo option e a cada envio do form ele salva esses dados numa session que recupero pelo $_GET['name']; e se eu voltar no form e enviar novamente, os valores vão ser diferente, queria manter esses valores que já foram enviados no cache e depois mostrar todos.

Rubens Takiguti Ribeiro (autor do blog) disse...

Renato, para guardar todos os valores que usuário submeteu, é só você guardá-los em um array. A sessão já é um array, mas você pode reservar uma posição dele para guardar o que veio do option:
$_SESSION['nomes'][] = $_GET['name'];

Depois você percorre o array para obter cada valor:
foreach ($_SESSION['nomes'] as $nome) {
echo $nome . PHP_EOL;
}

Adriano Oliveira disse...

Olá Rubens, pode me ajudar onde está o erro ? o valor '5455br' eu trago em $_SESSION['controle'] ao fazer o login para comparar. O $_SESSION['controle'] traz o valor compara e funciona mas derepente ao passar de uma página para outra ele perde a sessão a volta para o login. Em todas as página que preciso dessa sessão eu coloquei um obrigado.

Rubens Takiguti Ribeiro (autor do blog) disse...

Oi, Adriano
Se você colocou o session_start na página, recomendo que dê uma olhada na request HTTP que requisita a página que está perdendo a sessão. Veja se a request está enviando o cookie de sessão no cabeçalho. Se estiver, coloque um log na página pra ver o valor que está chegando no cookie.

Além disso, também pode acompanhar o conteúdo na sessão conforme você navega. Por exemplo, se guarda a sessão em arquivos, pode ver o conteúdo do arquivo (o nome do arquivo é igual ao id de sessão que fica no cookie de sessão).