Desenvolvendo iframe apps no Facebook com PHP – 5 dicas importantes

Em cada aplicativo que desenvolvemos aqui na Polvo aprendo uma porção de coisas novas, as quais gostaria de compartilhar agora. A documentação do Facebook não é excepcional, então tive que ir garimpando muitas coisas na internet.

Como na internet há muita informação boa, mas também muita coisa ruim, em alguns momentos utilizei métodos que acreditava estarem corretos quando, na verdade, tinham uma funcionalidade bastante limitada. Bastava terem alguns votos no fórum do StackOverflow e já via isso como correto.

Na segunda app que fui fazer, alguns códigos que estava utilizando na primeira não funcionavam mais, então fui filtrando um pouco mais o que achava na internet, refiz várias coisas e cheguei a algumas conclusões importantes, que aqui posto como dicas, e são elas:

1) Tudo pode ser feito utilizando a SDK do Facebook

Após baixar a SDK do Facebook (que está nesse link aqui), estava me complicando no momento de fazer a autenticação do usuário. Na segunda app é que fui descobrir como fazer isso de maneira correta. Na internet achei um método chamado oauth(), que eu fazia de maneira separada à SDK do Facebook, e que aparentemente funcionava, mas depois descobri que utilizar este recurso não é saudável para o código da app.

Depois de instanciar a classe Facebook, você pode pegar o endereço de login através de um método chamado getLoginUrl(), através do qual é possível passar uma url de retorno e o scope, que são as autorizações necessárias para o funcionamento da app (fotos, e-mail, etc).

Então, ao acessar a app, fiz o seguinte método:

private function getFacebook(){

   $facebook = $this->constructFacebook();

   if (!$facebook->getAccessToken()){
      $this->retornaHome($facebook);
   }

   try {
      $user_profile = $facebook->api('/me');
   } catch(FacebookApiException $e) {
      $this->retornaHome($facebook);
   }
   return $facebook;
}

Este é um método privado que chamo em todos os métodos da app. Primeiramente, ele chama o método constructFacebook(), que é o método que instancia a classe da SDK do Facebook:

private function constructFacebook(){
   $config = array();
   $config['appId'] = self::APP_ID;
   $config['secret'] = self::SECRET;
   $config['cookie'] = true;

   return new Facebook($config);
}

Onde self::APP_ID e self::SECRET são constantes que criei na classe da minha app com os valores que peguei lá das configurações da app no Facebook.

Tendo o objeto $facebook instanciado, agora eu posso verificar a existência de um access token. Para isso eu utilizo o método $facebook->getAccessToken(). Caso ele não retorne um valor, será chamado o método retornaHome(), que mostrarei abaixo. Depois eu faço uma pequena verificação utilizando a api graph, que tenta pegar alguns dados do usuário atual. Caso não conseguir, ele também chama o retornaHome().

Esse método retornaHome() é responsável por levar o usuário para a tela de login do Facebook, onde ele irá checar as autorizações necessárias para o funcionamento do aplicativo. Se o usuário já estiver logado e tiver autorizado o scope previamente, ele será levado automaticamente à página inicial da app.

O método retornaHome() consiste em:

private function retornaHome($facebook){

   die("<script> top.location.href='" . $facebook->getLoginUrl(array("canvas"=>1, "redirect_uri"=>self::APP_URL_SEM_BARRA,"scope"=>array("publish_stream","user_photos","email"))) . "'</script>");

}

Ou seja, aí ele faz um direcionamento, utilizando javascript, para a página de login do Facebook. Para saber qual é o endereço dessa página, eu chamo o método getLoginUrl(), no meu objeto $facebook já existente. Para este método estou passando três parâmetros:

canvas – Informa se a minha app está dentro de um canvas ou não (1 para verdadeiro e 0 para falso)

redirect_uri - Endereço da minha app sem a última barra. Para parametrizar, preferi salvá-la em uma constante dentro da classe. Ela possui valor de “https://apps.facebook.com/nomedaminhaapp”.

scope – São as autorizações que necessito, passadas por array, sendo elas “publish_stream”, que é a autorização para eu publicar na linha do tempo do usuário, “user_photos”, que é para eu acessar os álbuns dele e “email”, para eu ter essa informação e, por segurança, salvar em minha base.

Feito isso, já tenho como autenticar o usuário em todas as minhas páginas: basta chamar o método getFacebook().

2) Links dentro do iframe

A primeira página está em perfeito funcionamento, consigo pegar informações do usuário, etc. Então eu tinha o seguinte link:

<a href="pagina2.php">Link para a página 2</a>

Aí, quando eu tentava pegar alguma informação do usuário na página 2, não funcionava. Em algumas ocasiões, funcionava no Chrome, mas no Internet Explorer o problema persistia. Então descobri que o signed_request, que a classe Facebook vai ler e pegar as informações do usuário, vem através do cabeçalho do Facebook, e não da app, ou seja, na primeira vez que abria, o cabeçalho do Facebook era carregado, então eu tinha o signed request. Como eu tinha um link dentro do iframe, na segunda página ele não era carregado, então eu perdia as informações.

Isso foi solucionado apontando os links para o próprio facebook, da seguinte maneira:

<a href="https://apps.facebook.com/nomedaminhaapp/pagina2.php?var1=1&var2=2" target="_top">Link para a página 2</a>

Ou seja, ele irá carregar a página que eu queria, só que dando reload na página inteira. Ao dar esse reload, o signed_request é carregado novamente e eu consigo ter informações do usuário. O target=”_top” é importante pois estou direcionando um link de dentro do iframe para fora. Esse endereço irá carregar a página www.enderecodocanvas.com.br/pagina2.php e enviar as variáveis do método get normalmente.

3) Formulários

Depois de ter arrumado os links, percebi que meus formulários não estavam funcionando. O action deles tem que ser alterado da mesma maneira que os links, e também deve ser inserido um target=”_top”. O method deve ser setado para GET.

<form action="https://apps.facebook.com/nomedaminhaapp/targetdoform.php" method="GET" target="_top"></form>

Você também pode fazer o action como o endereço do seu canvas, processar as informações lá e depois voltar para o facebook com um redirect. Isso pode ser útil quando for necessário um upload de arquivo.

<form action="https://www.meucanvas.com.br/targetdoform.php" method="POST" target="_top" enctype="multipart/form-data">
   <input type="file" name="foto">
</form>

4) FQL Multiquery pode economizar muito tempo de processamento na sua app

Na primeira app que fiz, eu trabalhei da seguinte maneira: fiz uma query para pegar a lista de álbuns da pessoa. Depois de ter esse array já no php, dei um foreach() nele e, dentro de cada um, fiz uma nova query para pegar as fotos. Caso a pessoa tivesse 15 álbuns, eu fazia 16 queries: uma para a lista de álbuns e 15 para pegar as fotos de cada um deles. Cada uma levava em torno de 1 segundo para ser executada, gerando um tempo de processamento de 16 segundos. Parece pouco, mas para uma app ou para um piloto de Fórmula 1 é muita coisa.

Então descobri que podia fazer todas essas queries em uma só, ou seja, ao invés de usar:

$albuns = $facebook->api(array(
"method" => "fql.query",
"query" => "SELECT aid FROM album WHERE owner=me()"
));

E depois fazer a mesma coisa para as fotos, passei a usar:

$fql = '{
"data_album" : "SELECT aid FROM album WHERE owner=me()",
"photos" : "SELECT object_id FROM photo WHERE aid IN (SELECT aid FROM #data_album)",
}';

$params = array(
'method' => 'fql.multiquery',
'queries' => $fql
);

$albuns_fotos = $facebook->api($params);

Dessa maneira, consegui colocar todos os álbuns e fotos dentro da variável $albuns_fotos. Isso reduziu o tempo de execução de 16 segundos para 1, uma redução de 93,75%, o que é um valor bastante considerável. Para mais informações de como trabalhar com as FQL’s, clique aqui.

5) Salve informações do usuário em sua base utilizando o método getUser() e a api graph

Caso você quiser pegar o id do usuário que está utilizando sua app, basta utilizar o método $facebook->getUser();

Caso queira pegar mais informações, como nome, sexo, localização ou e-mail (caso você tiver colocado email no seu scope), você poderá fazer a seguinte consulta:

$user_profile = $facebook->api('/me','GET');

A variável $user_profile conterá um array com diversas informações sobre o usuário. Aí basta salvá-las. Vale lembrar que, quanto mais informações você colocar no scope, na hora de fazer o getLoginUrl(), maior é a chance de o usuário desistir de usar sua app. Portanto, use esta ferramenta com moderação. Você também pode salvar a quantidade de usuários que chegam ao método getLoginUrl() mas que acabam não utilizando a sua app, o que pode te dar uma ideia da taxa de conversão. Se estiver muito baixa, verifique se não está pedindo dados demais.

É isso aí, valeu e até a próxima! =)

PREENCHIDA – Vaga para programador web jr

POLVO contrata:

Estamos procurando um programador web júnior que se enquadre no perfil descrito abaixo. Mas que se enquadre MESMO. Ficamos com muita vergonha alheia de quem vem aqui, mente e acaba rebolando pra resolver os exercícios que são aplicados durante a entrevista.

Além dos conhecimentos técnicos listados abaixo, seria ótimo que o candidato fosse minimamente humano, no sentido de reconhecer erros, limites, responsabilidades e a tênue linha que separa a camaradagem da burrice.

Se você se interessa ou conhece alguém que possa se interessar, o e-mail para contato está logo ali embaixo.

***

Bom conhecimento em desenvolvimento web
Imprescindível: PHP, MySQL, XHTML, CSS, Webstandards, Javascript, Orientação a Objetos, MVC
Desejável: Java, Python, Linux

Interessados devem enviar e-mail para rh@polvo.com.br com currículo e pretensão salarial.

***

Morte ao Internet Explorer 6

Acho que todos os desenvolvedores web já sofreram, ou ainda sofrem (meu caso), com esse maldito browser, não é mesmo? Dependendo do caso, precisamos acrescer algumas horinhas (consideráveis) a mais no cronograma do cliente para resolver os bugs desse pseudobrowser.

Morte ao IE6

Devido a isso, problemas de segurança e à idade exagerada desse vovô dos navegadores, o portal iMasters lançou uma campanha de incentivo à atualização dos navegadores antigos. Eu achei uma providência divina: os clientes vão sair ganhando e a Polvo já está participando.

Vamos colaborar com a campanha: coloque o script em seu site também, incentive seus amigos, clientes e conhecidos que ainda usam essa bicheira a evoluir!

IE6: ¤ 2001 † 2009 (se Deus existe).