Crie Web sites rapidamente com CakePHP, Parte 4: Use os componentes de Sessão e de Manipulador de Solicitação do CakePHP

Agilize os aplicativos PHP

O CakePHP é uma ajuda estável, pronta para produção, de desenvolvimento rápido para construir Web sites em PHP. Esta série "Crie Web sites rapidamente com CakePHP" mostra como construir um catálogo de produtos on-line usando o CakePHP.

Sean Kelly, Web Application Developer, ID Society

Sean Kelly se formou em matemática pelo Reed College. Atualmente, ele é desenvolvedor de aplicativos Web para a ID Society, uma agência completa de marketing de Internet de Nova York. Ele apoia sistemas de gerenciamento de conteúdo de software livre, e contribui para o Joomla! e para o projeto Wikipedia.



02/Jun/2009 (Primeira publicação 02/Jan/2007)

Antes de iniciar

Nota do editor: Esta série foi publicada originalmente em 2006 e atualizada em 2007 e 2008. Desde sua última publicação, os desenvolvedores do CakePHP fizerem mudanças nele, resultando em múltiplas revisões nesta série. Esta revisão foi escrita para CakePHP V1.2.2.8120.

Esta série "Crie Web sites rapidamente com CakePHP" foi projetada para desenvolvedores de aplicativos PHP que queiram começar a usar o CakePHP para facilitar a própria vida. No fim, você terá aprendido a instalar e configurar o CakePHP, as noções básicas de design do Model-View-Controller (MVC), a validar dados de usuário no CakePHP, a usar os auxiliares CakePHP e a ter um aplicativo pronto e em execução rapidamente usando o CakePHP. Pode parecer muita coisa para aprender, mas não se preocupe — O CakePHP faz a maior parte do trabalho para você.

Sobre esta série

  • A Parte 1 se concentra em ter o CakePHP pronto e em execução, e nas noções básicas para preparar um aplicativo simples que permita aos usuários registrar uma conta e fazer login no aplicativo.
  • A Parte 2 demonstra como usar scaffolding e Bake para começar rapidamente seu aplicativo, e a usar as listas de controle de acesso (ACLs) do CakePHP.
  • A Parte 3 mostra como usar Sanitize, uma classe CakePHP útil, que ajuda a tornar um aplicativo seguro ao limpar os dados enviados pelo usuário. Ela também abrange o componente de segurança CakePHP, manipulação de solicitações inválidas e outras autenticações de solicitação avançadas.
  • A Parte 4 se concentra primariamente no componente de Sessão do CakePHP, demonstrando três modos de salvar os dados da sessão, além do componente Manipulador de Solicitação para ajudá-lo a gerenciar múltiplos tipos de solicitações (navegadores móveis, solicitações que contêm XML ou HTML, etc).
  • A Parte 5 trata de cache, em especial a visualização e o layout dele, o que pode ajudar a poupar consumo de recursos de servidor e agilizar seu aplicativo.

Sobre este tutorial

Há múltiplas maneiras de salvar dados de sessão usando o componente de Sessão do CakePHP, e cada método tem suas vantagens. Neste tutorial, você aprenderá a usar o componente de Sessão incorporando todos os três métodos no seu aplicativo, para que possa escolher o que funciona melhor para você. Além disso, aprenderá a usar o componente do Manipulador de Solicitação para ajudá-lo a manusear várias solicitações HTTP, incluindo as de navegadores móveis ou as que contenham conteúdo XML ou HTML.

Este tutorial é dividido em dois tópicos principais:

  • Os diferentes tipos de manuseio de sessão abrangidos pelo CakePHP — Você aprenderá as vantagens e desvantagens de cada um, e como implementá-los.
  • Como usar o Manipulador de Solicitação nos seus controladores— Usaremos ele para dois fins: acrescentar um feed de RSS aos seus produtos e implementar a funcionalidade Ajax.

Pré-requisitos

Este tutorial presume que você já concluiu a Parte 1, Parte 2 e Parte 3, e que ainda tem o ambiente de trabalho que preparou para esses tutoriais. Se você não tiver o CakePHP instalado, estude as Partes 1 e 2 antes de continuar.

Presume-se que você esteja familiarizado com o PHP, tenha uma idéia fundamental do design de bancos de dados e não se importe de sujar as mãos.

No caso da seção sobre o Ajax, presume-se também que você tem um conhecimento básico sobre ele. Veja em Recursos os links que o ajudarão na sua introdução ao Ajax.

Requisitos do sistema

Antes de começar, você precisa de um ambiente no qual trabalhar. O CakePHP tem requisitos de servidor razoavelmente mínimos:

  1. Um servidor HTTP que suporte sessões (e, de preferência, mod_rewrite). Este tutorial foi escrito usando o Apache V2.2.4 com mod_rewrite ativado.
  2. PHP V4.3.2 ou posterior (incluindo PHP V5). Este tutorial foi escrito usando PHP V2.3.
  3. Um mecanismo de banco de dados suportado. Este tutorial foi escrito usando MySQL V5.0.4.

Você também precisará de um banco de dados pronto para ser usado pelo seu aplicativo. O tutorial fornecerá a sintaxe para criar as tabelas necessárias em MySQL.

O modo mais simples de fazer o download do CakePHP é acessar CakeForge.org e baixar a versão estável mais recente. Este tutorial foi escrito usando V1.2.812. Também estão disponíveis compilações e cópias rápidas direto de Subversão. Os detalhes estão no Manual de CakePHP (veja os Recursos).


Tor, até o momento

Até agora, você usou o CakePHP para criar um aplicativo simples para gerenciar produtos e vendedores. Na Parte 1, você aprendeu sobre o paradigma MVC. Na Parte 2, usou o poderoso componente de scaffolding para desenvolver facilmente a estrutura do seu aplicativo. Concluiu a Parte 3 com vários projetos para aprimorar o Tor. O primeiro era higienizar seus dados.

O que higienizar

Quando você higienizou seus dados, provavelmente notou que a maior parte da entrada de usuário até o momento é bastante simples. A maior parte da entrada de dados pode ser filtrada usando o método paranoid, visto que não deve deixar nada complexo demais do usuário. A ação login do controlador de usuários é mostrada abaixo.

Listagem 1. Higienizando a entrada de nome de usuário
function login()
{
   if ($this->data)
      {
        $results = $this->User->findByUsername(Sanitize::paranoid($this->data
		                          ['User']['username']));
        if ($results && $results['User']['password'] == md5($this->data
		                          ['User']['password']))
        {
        $this->Session->write('user', $this->data['User']['username']);
        $this->redirect(array('action' => 'index'), null, true);
        } else {
        $this->set('error', true);
        }
    }
 }

De modo similar para registro, seria de esperar que o nome do usuário contivesse apenas letras, espaços, hifens e apóstrofos. Contudo, apóstrofes e hifens podem ser más notícias para um banco de dados SQL. A maior parte da limpeza normal de SQL, do tipo injeção, é gerenciada pela camada DBO, mas você pode suar o método de limpeza para executar higienização pesada em dados de outras entradas, se quiser.

Listagem 2. Você deve higienizar entradas diferentes com base nos seus valores esperados
function register()
{
   if (!empty($this->data))
      {
        $this->data['User']['username'] = Sanitize::paranoid($this->data
		       ['User']['username']);
        $this->data['User']['email'] = Sanitize::paranoid($this->data
		       ['User']['email'], array('@', '.', '-', '+'));
        $this->data['User']['first_name'] = Sanitize::sql($this->data
		       ['User']['first_name']);
        $this->data['User']['last_name'] = Sanitize::sql($this->data
		       ['User']['last_name']);
        $this->data['User']['password'] = md5($this->data
		       ['User']['password']);

Aqui vão alguns exemplos de como higienizar seus dados.

Tornando o aplicativo seguro.

Sua próxima tarefa era tornar o Tor seguro usando o componente de Segurança. Se analisar que formulários precisam de mais segurança, um palpite natural seria aqueles que alteram o banco de dados. Essa é uma boa regra prática: Se for feita uma mudança no banco de dados, o formulário deverá ser enviado pelo método POST.

Uma ação que se ajusta a essa descrição é a ação delete de produto. Visto que excluir um produto remove uma linha do banco de dados, deveria ser uma solicitação apenas de POST. O código para exigir isso é mostrado abaixo.

Listagem 3. ProductsController exige POST para ação de exclusão
function beforeFilter()
{
  $this->Security->requireAuth('delete');
  $this->Security->requirePost('delete');
}

Se você tentar excluir um produto agora, notará que receberá um erro 400. Isso é bom porque significa que qualquer exclusão precisa ocorrer em resultado de uma solicitação formatada específica. Contudo, precisamos recuperar a funcionalidade de exclusão, de modo que você precisará fazer a alteração apropriada nas suas visualizações que apontam para a ação delete. Veja na Listagem 4 como a nova seção showDelete deve se parecer.

Listagem 4. As chamadas da ação delete devem ocorrer dentro de formulários
<?php if ($showDelete) { ?>

        <?php echo $form->create('Product', array('action' => 'delete/' . 
               $product['Product']['id']));?>

        <li><?php echo $form->end('Delete product');?></li>

<?php } ?>

Fornecendo feedback para solicitações inválidas

Sua tarefa final era usar blackHoleCallback para fornecer ao usuário uma resposta apropriada em resultado de uma solicitação inválida. Seu objetivo era fornecer feedback útil e amigável, em vez de um desagradável erro de servidor.

Uma possível implementação desse código é encontrada abaixo.

Listagem 5. Informações mais amigáveis para uma função de solicitação ruim beforeFilter()
{
  $this->Security->requireAuth('delete');
  $this->Security->requirePost('delete');
  $this->Security->blackHoleCallback='invalid';
}

function invalid() {
  header('HTTP/x 400 Bad Request');
  echo('<h1>Tor</h1>');
  echo('<p>We\'re sorry - there has been a problem processing your request.  
Please try submitting the form again.</p>');
  die;
}

Alterando o layout padrão

Você vai acrescentar o recurso de produtos favoritos ao Tor. Os visitantes do seu site devem poder salvar produtos para uso futuro e usar um feed de RSS para acompanhar os novos produtos que ficarem disponíveis. Para acrescentar essa funcionalidade, você vai lidar com a função de manuseio de sessão e o componente do Manipulador de Solicitação. Contudo, antes de começar, você precisa expandir um pouco o Tor para que haja lugar para esses recursos. Para esse fim, você vai alterar o layout padrão a fim de customizar a aparência do Tor.

Quando você criou o controlador de produtos para o Tor, usou o script do Bake para criar a estrutura do aplicativo. O resultado foi uma página genérica com uma única tabela que se parecia com a Figura 1.

Figura 1. Scaffolding puro
Plain scaffolding

Embora esse scaffolding seja bom para estabilizar o design do seu aplicativo, você está agora em um ponto em que os usuários vão precisar de mais funcionalidade do que simplesmente ver produtos. Visto que o layout padrão forneceu um bom ponto de partida, tudo o que você precisa fazer é atualizar um pouco as visualizações existentes para organizar o aplicativo.

Layouts

Layouts são um tipo de visualização que não corresponde diretamente a um controlador ou modelo. Explicado de forma simples, um layout é um template. Mudar o layout padrão afeta a área ao redor de todas as outras visualizações e geralmente é usado para coisas como cabeçalhos, rodapés e barras de menus.

Estivemos utilizando o layout padrão do CakePHP, que se encontra em cake/libs/view/layouts/default.ctp (não mude isso!). Para substituir o layout por seu template customizado, copie o template padrão para app/views/layouts/default.ctp e modifique-o até que ele se pareça com a Listagem 6 (nada após as mudanças de menu div do template padrão).

Listagem 6. Substituindo o layout por seu template customizado
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
        <title>
                Tor : <?php echo $title_for_layout;?>
        </title>

        <?php echo $html->charset();?>
        <link rel="icon" href="<?php echo $this->webroot;?>favicon.ico" 
		             type="image/x-icon" />
        <link rel="shortcut icon" href="<?php echo $this->webroot;?>
		             favicon.ico" type="image/x-icon" />
        <?php echo $html->css('cake.generic');?>
        <?php echo $scripts_for_layout;?>
</head>

<body>
        <div id="container">
                <div id="header">
                     <h1><?php echo $html->link('Tor', '/'); ?>
                     : Welcome <?php echo $session->read('user') ?></h1>
                </div>
                <div id="content">
                <div id="menu">
                <?php echo $html->link('Products', 
                    array('controller' => 'products')); ?> | <?php echo 
                    $html->link('Favorites', array('controller' => 'users', 
                    'action' => 'favorites')); ?> |

                <?php echo $html->link('Login', array('controller' => 
				'users', 'action' => 'login')); ?> |
                <?php echo $html->link('Logout', array('controller' => 
				 'users', 'action' => 'logout')); ?>
                </div>
...
(message flash, content for layour, cake link, debug, etc
...
</body>
</html>

Esse código se baseia no layout padrão do CakePHP e simplesmente acrescenta uma barra de menus e a marca Tor. Visto que ele ainda usa o CSS padrão, o site não vai mudar muito visualmente.

Note que o layout é o XHTML Transitional. Embora todas as nossas visualizações sejam em XHTML, é totalmente possível elas serem conectadas a um layout em outro formato, como Atom, WML ou XHTML-Mobile. Vamos voltar à ideia de layouts quando acrescentarmos essa funcionalidade ao site.


Manuseio de sessão

Quando é feita uma solicitação ao Web site, ela é totalmente independente de toda outra solicitação antes dela. Para implementar a funcionalidade de usuário, determinado cliente Web precisa de acesso repetido a uma informação única, possivelmente secreta. Isso é chamado manuseio de sessão em aplicativos Web, e as sutilezas de manusear sessões de forma adequada são uma dor de cabeça para muitos desenvolvedores.

Noções básicas de manuseio de sessão

A ideia básica é a mesma em qualquer linguagem de programação. Após visitar o Web site pela primeira vez, o cliente recebe uma identificação única, em geral armazenada como cookie. No lado do servidor, é armazenado uma matriz de variáveis, de algum modo, que corresponde àquela identificação única. Em qualquer solicitação posterior do mesmo cliente, a identificação única é enviada junto com a solicitação, e o servidor carrega o array na memória. Essas variáveis são chamadas de variáveis de sessão para o cliente. Em PHP, elas são armazenadas em uma $_SESSION variável global.

Você poderia simplesmente usar a variável global $_SESSION para acessar informações da sessão ao usar o CakePHP. Contudo, o CakePHP tem seu próprio objeto de manuseio de sessão, que, como veremos depois, tem vários benefícios.

Manuseio de sessão dentro do Tor

Já implementamos sessões sem muito esforço. Quando o usuário faz o login, o objeto UsersController armazena o nome de usuário e o associa automaticamente ao cliente durante solicitações posteriores.

Cada objeto Controller (qualquer coisa herdada de AppController) tem acesso automaticamente ao componente de Sessão. Lembre-se de que Component é uma classe associada ao controlador que permite funcionalidade extra, de forma bem parecida a um auxiliar que fornece funcionalidade extra a uma visualização. Você usou o componente Acl na Parte 2 quando acrescentou ACLs. Lembre-se de que você precisa acrescentar a linha a seguir no início das classes ProductsController e UsersController: var $components = array('Acl');.

Embora o manuseio de sessão seja um componente do CakePHP, é um componente padrão incluído com cada controlador, de modo que não é necessário acrescentá-lo a essa lista.

Para usar o componente de Sessão, simplesmente acesse a variável de instância de sessão do controlador. As duas funções mais importantes do manipulador de sessão são a configuração e obtenção de variáveis, que são obtidos pela chamada dos métodos read e write. No caso de login de usuário, você usou o código da Listagem 7 para gravar o nome de usuário do cliente atual na sessão. Esse código se localiza na classe UsersController.

Listagem 7. Usando o método write para armazenar o nome de usuário
function login()
{
  ...

  $this->Session->write('user', $this->data['User']['username']);

  ...
{

O método write usa dois parâmetros. O primeiro é a chave atribuída e o segundo, o valor para atribuir a ele. Neste caso, a chave é user e o valor é o nome de usuário inserido (veja a Listagem 8). O nome de usuário é acessado depois para testar se o produto pode ser visto ou editado com base na ACL.

Listagem 8. Usando o método read para acessar o nome de usuário
function view($id) {
  ...

  if ($this->Acl->check($this->Session->read('user'), 
$id.'-'.$product['Product']['title'], 'read'))
  {
    ...
  }
}

O método read usa um único parâmetro — a chave que você deseja acessar — e retorna o valor que foi armazenado.

Armazenando sessões

Nada disso deve ser novo para desenvolvedores Web, mas o manuseio de sessão em geral é um assunto simples para aplicativos pequenos. Se um aplicativo exige que informações sensíveis do usuário sejam armazenadas na sessão, como senhas ou informações de cartão de crédito, onde e como os dados são armazenados é de importância vital.

O armazenamento de sessões é controlado por uma única linha do CakePHP: Configure::write('Session.save', 'php');. Essa linha, localizada no arquivo /app/config/core.php, diz ao Cake como as sessões devem ser armazenadas. Há três valores válidos: php (use aquele que o arquivo php.ini indicar), cake (salve os arquivos de sessão no diretório tmp do Cake) e database (use as sessões de banco de dados do Cake).

Armazenando sessões: cake

Quando esse valor é determinado, as sessões são armazenadas como arquivos na pasta do seu aplicativo. O CakePHP fornece uma pasta em /app/tmp/session com arquivos que parecem com algo como sess_50bfa744a2ab2c98df808f70c893704c. Dentro desses arquivos, variáveis de sessão individuais são armazenadas como texto simples sem criptografia.

O servidor Web Apache tem uma configuração chamada modo seguro que restringe as pastas às quais o Apache tem acesso. Em geral, o Apache só pode ler o que estiver no docroot do site, com algumas exceções para arquivos de biblioteca. Nesta série, você instalou o CakePHP puxando o diretório inteiro no diretório DocumentRoot do Apache (neste caso, o diretório /webroot). Sem dúvida, esse é um modo simples de instalar a estrutura, mas não mais seguro, e a razão é a seguinte: Ao configurar o manuseio de sessão para cake, você está armazenando variáveis de sessões em um diretório que pode ser acessado diretamente pelo navegador. Se seu site for comprometido de tal forma que o invasor faça o Apache retornar arquivos arbitrários, os dados de sessão de cada usuário ficarão vulneráveis (embora essa talvez seja a menor das suas preocupações). O Cake faz um ótimo trabalho ao proteger esses arquivos mesmo que tudo esteja no DocumentRoot, mas o ideal é instalar de forma mais segura tornando o diretório app/webroot seu diretório DocumentRoot. Isso deve manter off-line os arquivos de sessão do Cake.

é importante notar que você não pode controlar as permissões nesses arquivos de sessão. Eles terão exatamente o mesmo usuário e permissões de grupo que PHP concedeu. Em alguns sistemas, essa também pode ser uma vulnerabilidade de segurança.

Vantagens:

  • As variáveis de sessão são armazenadas dentro do Cake e, assim, todo o aplicativo continua em um só lugar.
  • Os arquivos de sessão podem ser lidos por um navegador de texto, possivelmente para depuração (é pouco provável que tenha utilidade).
  • Iniciar e acessar uma sessão não exige conexão com o banco de dados (é pouco provável que seja útil, visto que acessar modelos no CakePHP inicia uma sessão PHP).

Desvantagens:

  • Qualquer arquivo armazenado em DocumentRoot pode ser comprometido se o servidor Web for comprometido.
  • Os arquivos de sessão têm as mesmas permissões concedidas ao PHP.
  • Servidores Web com equilíbrio de carga que não compartilham um sistema de arquivos não podem compartilhar o acesso a arquivos de sessão, fazendo com que estas sejam desativadas misteriosamente a menos que se usem sessões persistentes.

Armazenando sessões: database

Se as informações da sessão que você está armazenando precisarem de um nível maior de segurança ou maior controle de permissões, as sessões de banco de dados serão melhores. Configurando Session_save para database, você está dizendo ao CakePHP para armazenar toda informação de variável serializada em uma tabela no banco de dados com o restante do seu aplicativo.

Antes de poder usar sessões de banco de dados com o CakePHP, você deve criar a tabela. Fique à vontade para experimentá-la no Tor. Por padrão, o nome da tabela é cake_sessions, embora isso possa ser alterado no arquivo app/config/core.php. Será preciso tirar os comentários das linhas Configure::write na Session.table e no Session.database a fim de usar o armazenamento da sessão de banco de dados. O esquema dessa tabela é armazenado em app/config/sql/sessions.sql. O esquema incluído no CakePHP V1.2.0.x é mostrado abaixo.

Listagem 9. Criar a tabela cake_sessions
CREATE TABLE cake_sessions (
  id varchar(255) NOT NULL default '',
  data text,
  expires int(11) default NULL,
  PRIMARY KEY  (id)
);

Após ter criado a tabela e configurado Session.save para database, tente fazer o login no Tor em /users/login. Após fazer o login, verifique o banco de dados para ver se sua sessão apareceu. Deve haver uma única linha com a seguinte aparência:

id                                 data                                expires
50bfa744a2ab2c98df808f70c893704c   Config|a:3:{s:4:"rand";i:94427...   1164661678

Armazenar as sessões no banco de dados trata diretamente de todas as três desvantagens alistadas no método acima. Primeiro, o Apache em geral não tem acesso direto ao banco de dados, de modo que comprometer o servidor Web não expõe os arquivos de sessão. Segundo, as permissões do banco de dados podem ser configuradas explicitamente para o seu aplicativo, e você pode até restringir o acesso a determinado host. Terceiro, os servidores com equilíbrio de carga têm acesso compartilhado com a tabela de sessão sem trabalho extra da sua parte.

Para a maioria dos grandes aplicativos, as sessões de bancos de dados são essenciais porque permitem o máximo de segurança com o mínimo de esforço.

Vantagens:

  • Simples de configurar — exige apenas uma tabela extra no seu banco de dados.
  • Uma perda de segurança no servidor Web provavelmente não resultará em comprometimento das sessões.
  • As sessões podem ser compartilhadas mais facilmente entre servidores com equilíbrio de carga.

Desvantagens:

  • Usar o banco de dados para armazenar sessões acrescenta-lhe um pouco de overhead. Isso pode se acumular.
  • As sessões ainda são armazenadas em texto simples no banco de dados; os backups do banco de dados podem fazer com que dados sensíveis sejam armazenados por períodos prolongados.
  • Dependendo de como seu banco de dados está configurado, as comunicações entre seu aplicativo e o banco de dados podem não ser seguras. Se o seu banco de dados não estiver em localhost, ou não está em um canal seguro, como VPN, é possível que as comunicações fiquem comprometidas.

Armazenando sessões: php

O método final de armazenar sessões é usar qualquer manuseio de sessão que o PHP estiver configurado para usar. Por padrão, o PHP vai gravar suas sessões como arquivos similares à configuração cake para Session.save. Uma das principais diferenças é que em vez de salvar variáveis de sessão dentro do aplicativo do Cake, elas são geralmente armazenadas em um diretório temporário em outra parte do sistema de arquivos. Isso pode ser ou não ser preferível a ter sessões armazenadas no mesmo diretório do seu site.

Ao configurar as sessões para serem armazenadas via PHP, você dá mais controle a sessões por PHP, em vez de por Cake. O PHP lhe permite cancelar várias funções de manipulador de sessão, essencialmente cancelando o modo como as sessões são armazenadas em um método à sua escolha. Pode ser armazenamento de sessões em um banco de dados separado, em um canal seguro customizado, ou qualquer outro método doido que você inventar.

Se você precisar mudar o modo como o manuseio de sessão é feito no PHP, há uma única função, session_set_save_handler, que controla a função de retorno de chamada de sessão. Como se passa a essa função o nome das funções que abrem, fecham, leem e gravam na sessão é demonstrado abaixo. Uma análise detalhada da função session_set_save_handler está fora do escopo deste artigo (veja os Recursos).

Listagem 10. Redefinindo como o PHP armazena sessões
function open($save_path, $session_name)
{
  global $sess_save_path;
 
  $sess_save_path = $save_path;
  return(true);
}

function read($id)
{
  global $sess_save_path;
 
  $sess_file = "$sess_save_path/sess_$id";
  return (string) @file_get_contents($sess_file);
}

...

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

As funções de manuseio de sessão são redefinidas pelas funções do PHP: session_start(). Usar esse método tem algumas vantagens.

Vantagens:

  • é flexível. Qualquer método de armazenamento suportado pelo PHP.
  • Se você não cancelar as funções de manuseio de sessão, as sessões serão armazenadas do mesmo modo que todos os outros aplicativos no seu servidor.

Desvantagem:

  • Visto que o PHP é configurado para armazenar sessões no diretório tmp por padrão, você pode encontrar algumas das desvantagens descritas acima em "Armazenando sessões: 'cake'."

Decidir que método usar para manusear o armazenamento de sessões nem sempre é simples. Na maior parte do tempo, deixar o PHP manusear suas sessões não causa problemas. Mas no fim das contas, você terá de tomar essa decisão com base nas peculiaridades do seu aplicativo.

Na próxima seção, analisaremos o componente do Manipulador de Solicitação e como você pode usá-lo para acrescentar a funcionalidade do Ajax ao seu aplicativo.


Utilizando o Manipulador de Solicitação

O CakePHP vem com um Manipulador de Solicitação que lhe permite apresentar seu aplicativo de formas diferentes, dependendo do tipo de solicitação feita. Nas seções anteriores, você viu como os layouts controlam o wrapper do seu aplicativo, que, por padrão, no CakePHP é o XHTML Transitional. Aqui, examinaremos como você pode usar o Manipulador de Solicitação para retornar dados do seu aplicativo em uma forma diferente do XHTML, dependendo do que está solicitando a página.

Anatomia de uma solicitação HTTP

Cada elemento de uma página Web é obtido via solicitação HTTP, um cabeçalho de texto que contém o recurso sendo solicitado, e informações sobre o agente que solicita. Abaixo, uma amostra de solicitação HTTP.

Listagem 11. Amostra de solicitação HTTP
GET / HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.1) 
Gecko/20061010 Firefox/2.0
Accept: text/xml,application/xhtml+xml,text/html
Accept-Language: en-us,en
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8
Keep-Alive: 300
Connection: keep-alive
Cache-Control: max-age=0

Em Web sites antigos, não era incomum tentar analisar a cadeia de caractere User-Agent, fornecendo conteúdo completamente diferente a navegadores Web diferentes. Por exemplo, uma empresa que projetasse seu site para Internet Explorer® podia descobrir que ele não aparecia corretamente em outros navegadores e teria de sugerir que seus visitantes usassem o IE.

Felizmente, esses dias estão chegando ao fim, e boas maneiras na Web significam que todo navegador Web deveria obter exatamente o mesmo conteúdo para exatamente os mesmos recursos. Note que o mesmo conteúdo não significa necessariamente que tenha de ser apresentado da mesma maneira.

Em vez de usar o User-Agent para determinar como formatar o conteúdo, um método melhor é usar o cabeçalho Accept. No exemplo acima, o Firefox aceita três tipos de conteúdo identificado por seus tipos de MIME: XML, XHTML+XML e HTML.

Criando um feed de RSS

Agora você criará um feed de RSS para o Tor, que é uma lista atualizada de todos os novos produtos. Isso mudou drasticamente desde o CakePHP V1.1.8: Agora, é mais fácil ainda. Você precisa incluir o componente do Manipulador de Solicitação no manipulador de Produtos, criar uma visualização que use o Auxiliar de RSS e dizer roteador para analisar as extensões RSS.

Dar esse comportamento à lista de produtos é fácil quando se usa o componente do Manipulador de Solicitação. Visto que o seu feed se localizará em /products, abra o arquivo que contém a classe ProductsController. Para começar, inclua o componente do Manipulador de Solicitação.

Listagem 12. Incluindo o componente do Manipulador de Solicitação
<?php
class ProductsController extends AppController
{

  var $name = 'Products';
  var $helpers = array('Html', 'Form' );
  var $components = array('Acl', 'Security', 'RequestHandler'); 
  ...
  
}

Há muito mais coisas sobre o Manipulador de Solicitação do que aquilo que vamos abranger aqui. Você pode usá-lo para determinar que tipos de solicitações são aceitos, ou preferidos, e muito mais. Mas por agora, simplesmente incluí-lo será suficiente.

Depois, você precisar criar uma visualização apenas para o feed de RSS. Ela será renderizada automaticamente quando solicitado e usará o Auxiliar de RSS para realizar o trabalho necessário. Crie o arquivo app/views/products/rss/index.ctp (pode ser necessário criar esse diretório RSS). A aparência da visualização deve ser semelhante ao que se vê abaixo.

Listagem 13. Visualização RSS de índice de produtos
<?php
     echo $rss->items($products, 'transformRSS');
     function transformRSS($products) {
           return array(
                'title'  => $products['Product']['title'],
                 'link'    => array('url'=>'/products/view/'
				       .$products['Product']['id']),
                        'description'   => $products['Product']['description'],
                );
        }
?>

Por fim, acrescente a seguinte linha ao arquivo app/config/routes.php: Router::parseExtensions('rss');.

é isso! Você pode ver o novo feed de RSS em http://localhost/products/index.rss. Talvez seja necessário ver a fonte para ler o XML. E se o seu nível de depuração for configurado para 2 ou 3, a informação de depuração sem dúvida invalidará o formato XML. Mas isso deve encaminhá-lo muito bem para brincar com RSS no Cake.

Acrescentando o Ajax

O Asynchronous JavaScript and XML (Ajax) é um método popular para criar aplicativos Web interativos sem sacrificar a compatibilidade do navegador. Nesta seção, descrevemos como usar o auxiliar do Ajax do CakePHP e os componentes do Manipulador de Solicitação para agilizar a atualização dos produtos. Presume-se que você tem uma familiaridade geral com os conceitos do Ajax. Se precisar de uma introdução, experimente ler "Dominando o Ajax" e "Avaliando o Ajax" (veja os Recursos).

Lembre-se de que o Ajax é uma série de solicitações assíncronas do cliente para o servidor, em geral iniciadas por uma interação do usuário. Visto que o CakePHP separa os plug-ins back-end e front-end em componentes e auxiliares, respectivamente, isso significa que todas as suas funções JavaScript serão implementadas pelo auxiliar do Ajax, enquanto toda a funcionalidade back-end será suportada pelo componente do Manipulador de Solicitação.

O componente do Manipulador de Solicitação permite de forma eficaz que você reutilize o código não-Ajax que criou. Ele tem um método isajax, que retorna verdadeiro se a solicitação tiver sido feita por uma chamada XMLHttpRequest. Dentro do seu controlador, você pode usar esse método para verificar a natureza da solicitação e ajustar um pouco a saída, dependendo de se o Ajax está envolvido.

script.aculo.us

O auxiliar do Ajax do CakePHP exige Protótipo para o Ajax e usa o script.aculo.us para os efeitos. Ambos são liberados por licença do MIT. (A licença do MIT é a mesma do CakePHP e basicamente diz que você pode usar o software por qualquer meio, com poucas restrições). A biblioteca script.aculo.us inclui uma cópia do Protótipo, então, não é necessário fazer o download de ambos. Comece fazendo o download da cópia mais recente do script.aculo.us (veja os Recursos). Na verdade, você não precisa conhecer muito o script.aculo.us, exceto para incluir seus arquivos de JavaScript no seu site.

Depois de fazer o download e extrair o script.aculo.us, você deve ver uma pasta chamada src com vários arquivos de JavaScript nela. Você não vai usar todos eles, mas pode copiar todos para a pasta app/webroot/js do seu site. Copie também o arquivo lib/prototype.js para app/webroot/js.

Agora que você tem o script.aculo.us, o JavaScript precisa ser incluído em cada página. Abra o arquivo app/views/layouts/default.ctp e acrescente as linhas relacionadas ao JavaScript mostradas abaixo.

Listagem 14. Alterando o cabeçalho
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?php
if ( isset($javascript) ) {
  echo $javascript->link('prototype.js');
  echo $javascript->link('scriptaculous.js?load=effects');
  echo $javascript->link('controls.js');
}
?>
...

Isso é basicamente tudo que você vai ter que fazer com a biblioteca script.aculo.us. Daqui por diante, tudo envolve apenas objetos no CakePHP.

Acrescentando uma lista de favoritos

Lembre-se da Parte 2 que você conseguiu fazer um link com dois modelos com os relacionamentos hasMany e belongsTo. Em especial, você criou um link entre Vendedores e Produto. Há outro relacionamento, hasAndBelongsToMany, que é especialmente útil ao fazer links com muitos modelos.

Para ilustrar, imagine que você criou os itens favoritos do usuário utilizando o relacionamento hasMany. Então, cada produto deve "pertencerA" determinado usuário, o que significa que nenhum outro usuário pode marcá-lo como favorito. Isso não seria bom. A estrutura de banco de dados resultante é mostrada na Figura 2.

Figura 2. Esquema de banco de dados para o relacionamento hasMany e belongsTo
Database schema for hasMany and belongsTo relationship

Note que um produto pode pertencer a apenas um usuário. Em vez disso, o relacionamento hasAndBelongsToMany usa uma "tabela de junção", que é um modo de assegurar que os produtos hasMany de um usuário, sem que os produtos precisem "pertencerA" um único usuário. A tabela de junção simplesmente rastreia uma lista de pares — um usuário e um produto — para indicar que os dois estão ligados. A estrutura de banco de dados resultante é mostrada abaixo.

Figura 3. Esquema de banco de dados para o relacionamento hasAndBelongsToMany
Database schema for hasAndBelongsToMany relationship

Qualquer número de conexões pode ser feito entre as duas tabelas. Se você configurou uma tabela de junção adequadamente, o CakePHP vai criar automaticamente os links apropriados para você. Para assegurar que o CakePHP possa encontrar sua tabela de junção, há diversas convenções que você deve seguir:

  • O nome da tabela deve incluir os nomes dos dois modelos, no plural e em ordem alfabética. Por exemplo, se você ligar um objeto de usuário e de grupo, o nome da tabela seria groups_users.
  • A tabela deveria ter duas teclas estrangeiras, cada qual com o mesmo nome do relacionamento belongsTo. Por exemplo, uma tabela de junção users/groups teria os campos user_id e group_id.
  • Para otimização, as duas chaves formariam uma chave primária na tabela de junção.

No nosso caso, estamos ligando produtos a usuários, de modo que a consulta do SQL vai criar uma tabela que atenda a essas normas.

Listagem 15. Crie uma tabela para ligar os produtos aos usuários
CREATE TABLE products_users (
  product_id int(5) NOT NULL,
  user_id int(5) NOT NULL,
  PRIMARY KEY (product_id,user_id)
) ENGINE=MyISAM;

Para ligar os dois modelos, simplesmente acrescentamos o relacionamento hasAndBelongsToMany ao produto e aos modelos de usuários.

Listagem 16. Acrescente o relacionamento hasAndBelongsToMany ao produto e modelos de usuário
//In app/models/user.php:
<?php
  class User extends AppModel
  {
    ...
    
    var $hasAndBelongsToMany = array( 'Product' =>
        array(
            'className' => 'Product'
        )
    );
}
?>

//In app/models/product.php:
<?php
  class Product extends AppModel
  {
    ...
    
    var $hasAndBelongsToMany = array( 'User' =>
        array(
            'className' => 'User'
        )
    );
}
?>

Agora, sempre que um produto ou usuário for lido no banco de dados, ele vai verificar a tabela products_users para ver se há algum link que deve ser feito. Se encontrar linhas aplicáveis, vai carregá-las para os objetos correspondentes. Crie uma nova ação em users_controller.php chamada favoritos.

Listagem 17. Recuperando uma lista de favoritos
   function favorites () {
       $username = $this->Session->read('user');
       $favorites = array();
       
       if ($username)
       {
         $this->User->recursive = 2;
         $results = $this->User->findByUsername($username);
         
         foreach($results['Product'] as $product) {
           $favorites[] = array('Product' => $product, 'Dealer' => 
$product['Dealer']);
         }
         
         $this->set('products', $favorites);

      } else {

         $this->redirect(array('controller' => 'users', 'action' => 'login'));
       }
   }

A ação addToFavorites

Do ponto de vista da interface, queremos que o usuário acrescente um produto aos seus favoritos. Portanto, precisamos de uma ação addToFavorites em ProductsController.

Abra o arquivo app/controllers/products_controller.php e acrescente a ação addToFavorites, mostrada abaixo.

Listagem 18. Acrescentando ação addToFavorites
 function addToFavorites($id) {
    $product = $this->Product->read(null, $id);
    $username = $this->Session->read('user');
    $success = false;
         if ($this->Acl->check($username, $id.'-'.$product['Product']
		                            ['title'], 'read')) {
              $result = $this->Product->User->findByUsername($username);
               $product['User'] = array( 'User' =>
                   array($result['User']['id'])
               );
               $this->Product->save($product);
               $success = true;
         }
         if ( $this->RequestHandler->isAjax() ) {
               $this->layout = 'ajax';
               if ( $success ) {
                     echo 'Added product to favorites';
               } else {
                     echo 'Access denied';
                      }
exit;

         } else {
                if ( $success ) {
                     $this->Session->setFlash('Added product to favorites');
                     $this->redirect(array('controller' => 'users', 'action' 
					               => 'favorites'), null, true);
                 } else {
                     $this->Session->setFlash('Access denied');
                     $this->redirect(array('action' => 'index'), null, true);
                 }
         }
   }

Vejamos o que faz essa ação. Primeiro, ela faz uma verificação rápida na ACL para ver se o usuário realmente pode ler o produto. Depois, carrega o objeto de usuário com base no valor armazenado na variável de sessão user. Daí, formata os dados para salvar e salva o item na lista de favoritos.

Por fim, o Manipulador de Solicitação é chamado para ver se a ação foi uma solicitação do Ajax. Se foi, é retornada uma mensagem informativa. Se não, o usuário é redirecionado para a página apropriada.

Agora você pode ir para /products para testar a ação, dando o resultado mostrado na Figura 4. Veja a lista de produtos e escolha um por identificação, como o produto 8. Depois, vá para http://localhost/products/addToFavorites/8 e veja o resultado. Embora não haja outra evidência dele a não ser na mensagem de saída, o link foi feito. Conecte ao seu banco de dados e olhe a tabela products_users, e deverá ver uma única linha ligando seu user_id com o product_id selecionado.

Figura 4. Sucesso ao acrescentar produto
The completed favorites page

Ligando à ação addToFavorites

Por fim, você deve fornecer um link para a ação addToFavorites na página de visualização de produtos. Você usará o método de link do auxiliar do Ajax para criar o link. O método, $ajax->link, age como o método $html->link. Ele usa os seguintes parâmetros:

  • $title— Uma cadeia de caractere que será usada para o título do link — no caso, "Excluir"
  • $href— A ação que o link executará
  • $options— Um array de opções, a mais importante das quais é a atualização, a identificação do elemento para o qual o texto de resposta deve ser encaminhado — no caso, estamos atualizando toda a tabela
  • $confirm— Um alerta de JavaScript que vai abrir em pop-up, confirmando a ação

Esse método envia todo o JavaScript necessário para fazer uma solicitação Ajax completa. No fim, só precisaremos atualizar o controlador de produtos para atender a um novo tipo de solicitação. Utilizando o componente do Manipulador de Solicitação, mudamos o comportamento se a ação faz parte de uma chamada Ajax, como mostrado na Figura 18. Você também precisará acrescentar um teste à visualização de produtos para ver se o Ajax está habilitado e, se estiver, mostrar um link do Ajax. Esse link tentará atualizar um elemento DOM chamado favMessage. Você deve criá-lo em algum lugar da visualização, como no fim do produto div.

Listagem 19. Acrescentando a links favoritos na visualização de produtos
<?php
if ( isset($ajax) ) {
      echo $ajax->link('Add to Favorites', array('action' => 'addToFavorites/' . 
         $product['Product']['id']), array('update' => 'favMessage'));
} else {
      echo $html->link('Add to Favorites', array('action' => 'addToFavorites/' . 
         $product['Product']['id']));
}
?>

...

<span id='favMessage'> </span>
...

Para que isso funcione, você precisa acrescentar os auxiliares de JavaScript e Ajax ao controlador de Produtos, assim: var $helpers = array('Html', 'Form', 'Javascript', 'Ajax' );.

Usando o auxiliar do Ajax do CakePHP, acrescentamos a funcionalidade do Ajax sem ter de escrever uma única linha de JavaScript.

Preenchendo as falhas

Em vez de lhe dar o código para exibir a tabela de favoritos, vamos deixar você tentar isso sozinho. Você precisa acrescentar o método descrito antes favorites ao controlador de usuários e uma visualização de users/favorites.

Quando terminar com isso, tente acrescentar um link "Remover dos favoritos" à tabela de produtos. Configure-o de modo que o usuário seja mostrado no link "Acrescentar/Remover" com base em se o produto já está nos favoritos ou não.


Resumo

Vez por outra, todo aplicativo Web encontrará o problema de como armazenar sessões. Com o CakePHP, você tem grande flexibilidade para tratar desse problema. As sessões de banco de dados podem ser facilmente configuradas para mudar um parâmetro e criar uma tabela, o que poupa muito tempo.

Você também sobre como o Manipulador de Solicitação é uma ferramenta multiuso para mudar o modo como seu aplicativo é apresentado. Viu como pode ser usado para manipular solicitações do Ajax e enviar feeds de RSS. Quando estiver brincando com o CakePHP, experimente outros tipos de solicitações, como detecção de dispositivos móveis.


Download

DescriçãoNomeTamanho
Part 4 source codeos-php-cake4.source.zip13KB

Recursos

Aprender

Obter produtos e tecnologias

Discutir

Comentários

developerWorks: Conecte-se

Los campos obligatorios están marcados con un asterisco (*).


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


A primeira vez que você entrar no developerWorks, um perfil é criado para você. Informações no seu perfil (seu nome, país / região, e nome da empresa) é apresentado ao público e vai acompanhar qualquer conteúdo que você postar, a menos que você opte por esconder o nome da empresa. Você pode atualizar sua conta IBM a qualquer momento.

Todas as informações enviadas são seguras.

Elija su nombre para mostrar



Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Los campos obligatorios están marcados con un asterisco (*).

(Escolha um nome de exibição de 3 - 31 caracteres.)

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

 


Todas as informações enviadas são seguras.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre
ArticleID=395942
ArticleTitle=Crie Web sites rapidamente com CakePHP, Parte 4: Use os componentes de Sessão e de Manipulador de Solicitação do CakePHP
publish-date=06022009