in Профессиональное

Symfony 2009 advent calendar: день 19 – Разработка для Facebook (часть 2)

Symfony 2009 advent calendar – это 24 урока продвинутого уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить.

Перевод статьи Symfony 2009 Advent Calendar: Developing for Facebook (part 2).

Facebook Connect

О том как Facebook Connect работает и о различных стратегиях интеграции

Facebook Connect, прежде всего, предоставляет свою сессию для совместного доступа вашему вебсайту. Работа этой функции становится возможной благодаря копированию аутентификационных кукисов с Facebook через iframe на сайте, который указывает на страницу Facebook, которая в свою очередь открывает iframe на сайт. Для этого, Facebook Connect должен иметь доступ к вебсайту, что делает невозможным использовать или тестировать Facebook Connect на локальном сервере или в интранет-сети. Входной точкой является xd_receiver.htm, который поставляется с sfFacebookConnectPlugin‘ом. Не забудьте выполнить таск plugin:publish-assets для того чтобы этот файл стал доступен.

Когда подготовительные процедуры выполнены, официальная библиотека Facebook получает возможность использовать сессию Facebook. В дополнение к этому, sfFacebookConnectPlugin создает пользователя sfGuard, связанного с сессией Facebook и который прочно связан с существующим сайтом на symfony. Вот почему плагин по умолчанию редиректит пользователя на экшен sfFacebookConnectAuth/signIn после того как тот нажмет кнопку Facebook Connect и сессия пройдет валидацию. Первым делом плагин ищет существующего пользователя с тем же UID’ом или с тем же хэшем Email (см. также раздел “Сопоставляем существующих пользователей с их аккаунтами на Facebook” в конце этой статьи). Если пользователь не найден, создается новый пользователь.

Другая стратегия регистрации заключается в том, чтобы не создавать пользователей напрямую, а сначала перенаправлять их на особую форму регистрации. На этой форме, с использованием сессии Facebook, например, можно сразу заполнить базовую информацию. Для того чтобы это сделать, можно добавить следующий код в форму регистрации:

public function setDefaultsFromFacebookSession()
{
  if ($fb_uid = sfFacebook::getAnyFacebookUid())
  {
    $ret = sfFacebook::getFacebookApi()->users_getInfo(
      array(
        $fb_uid
      ),
      array(
        'first_name',
        'last_name',
      )
    );

    if ($ret && count($ret)>0)
    {
      if (array_key_exists('first_name', $ret[0]))
      {
        $this->setDefault('first_name',$ret[0]['first_name']);
      }
      if (array_key_exists('last_name', $ret[0]))
      {
        $this->setDefault('last_name',$ret[0]['last_name']);
      }
    }
  }

Для использования второй стратегии, просто разрешите в app.yml редирект после Facebook Connect и маршрут, который необходимо будет использовать.

# default values
all:
  facebook:
    redirect_after_connect: true
    redirect_after_connect_url: '@register_with_facebook'

Фильтр Facebook Connect

Другая важная особенность Facebook Connect заключается в том, что пользователи Facebook, при просмотре интернет-страниц, зачастую уже залогинены в Facebook. Вот тут во всю раскрывается сила sfFacebookConnectRememberMeFilter. Если пользователь, зашедший на ваш вебсайт, уже залогинен в Facebook, sfFacebookConnectRememberMeFilter автоматически залогинит его на вашем сайте, примерно так, как это делает фильтр “Remember me”.

$sfGuardUser = sfFacebook::getSfGuardUserByFacebookSession();
if ($sfGuardUser)
{
  $this->getContext()->getUser()->signIn($sfGuardUser, true);
}

Тем не менее, эта плюшка имеет один серьёзный недостаток: пользователи не могут разлогиниться, пока они залогинены на Facebook (т.к. они каждый раз будут логиниться автоматически). Используйте эту фичу осторожно (ну или на свой страх и риск).

Как избежать фатального JavaScript бага в IE

Один из самых ужасных багов, который вы можете получить на вебсайте, является ошибка “Operation aborted” в IE, которая запросто рушит рендеринг вебсайта на стороне клиента. Это следствие кривого движка рендеринга в IE6/7, который может обрушиться, если вы добавляете DOM-элементы к элементу BODY из скрипта, который не является прямым потомком элемента BODY. К несчастью, это как правило случается, если вы загружаете JavaScript для Facebook Connect и не озаботились загрузить его напрямую из body в конце документа. Тем не менее, этого легко избежать при помощи slot’ов. Используйте slot для того чтобы включить скрипт Facebook Connect’а когда это необходимо, в ваш шаблон лэйаута в конце документа (перед тагом </body>):

// in a template that uses a XFBML tag or a Facebook Connect button
slot('fb_connect');
include_facebook_connect_script();
end_slot();

// just before </body> in the layout to avoid problems in IE
if (has_slot('fb_connect'))
{
  include_slot('fb_connect');
}

Лучшие практики для Facebook-приложений

Благодаря sfFacebookConnectPlugin, интеграция с sfGuardPlugin выполняется гладко и выбор, будет ли приложение FBML, IFrame или Facebook Connect, можно отложить на последок. Для того чтобы мы могли двигаться дальше и создавать навороченные приложения для Facebook, ниже приведено несколько важных советов.

Используйте environments в symfony для того чтобы поднять несколько тестовых серверов для Facebook Connect

Очень важный аспект философии symfony заключается в быстрой отладке и качественном тестировании приложения. Интеграция с Facebook может серьезно осложнить эту задачу, так как почти все время будет требоваться соединение с Internet для коммуникаций с сервером Facebook и открытый 80й порт для обмена аутентификационными кукисами. Кроме того, есть еще ограничение: приложение, использующее Facebook Connect может быть подключено только к одному хосту. Это становится настоящей проблемой, когда приложение разрабатывается на одной машине, тестируется на другой, помещается в пре-продакшн на третий сервер и, наконец, используется в продуктовом окружении на четвертом.

В этой ситуации наиболее простым решением будет создать приложение для каждого сервера и создать в symfony environment для каждого из них. В симфони это делается легким движением руки ))) делаем копию frontend_dev.php (или его аналога), в frontend_preprod.php и редактируем строку во вновь созданном файле для изменения dev environment’а на новый, который мы назовем preprod:

// заменяем энвайронмент на новый:
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'preprod', true);

Далее, вносим поправки в app.yml для того чтобы сконфигурировать новое Facebook-приложение для каждого нового окружения:

prod:
  facebook:
    api_key: xxx
    api_secret: xxx
    api_id: xxx

dev:
  facebook:
    api_key: xxx
    api_secret: xxx
    api_id: xxx

preprod:
  facebook:
    api_key: xxx
    api_secret: xxx
    api_id: xxx

Теперь приложение тестируемо на каждом отдельном сервере с использованием соответствующей среде frontend_xxx.php точки входа.

Используйте систему логгирования symfony для отладки FBML

Решение с переключением лэйаутов позволяет разрабатывать и тестировать большинство FBML приложений вне сайта Facebook. Тем не менее, финальный тест внутри Facebook может выдавать какие-то невразумительные ошибки. Действительно, основная проблема рендеринга FBML напрямую в Facebook заключается в том, что ошибка 500 обрабатывается и заменяется на не очень информативную стандартную страницу с ошибкой. Кроме того, web debug toolbar, не отображается в Facebook-фрейме. К счастью, на помощь к нам приходит замечательная система логгирования symfony. sfFacebookConnectPlugin автоматически логгирует многие важные действия, вы также с легкостью можете добавить строки в файл лога в любой точке приложения:

if (sfConfig::get('sf_logging_enabled'))
{
  sfContext::getInstance()->getLogger()->info($message);
}

Используйте Proxy во избежание ошибочных редиректов Facebook

Имеется странный баг Facebook, который заключается в том, что когда Facebook Connect сконфигурирован в приложении, Facebook Connect рассматривается как домашняя директория (home) приложения. Не смотря на то, что home может быть сконфигурирован, он должен быть расположен на том же домене, что и Facebook Connect хост. Таким образом, не существует другого решения кроме как сдаться и сконфигурировать home как простой экшен, осуществляющий перенаправление куда потребуется. Следующий код осуществляет перенаправление на Facebook-приложение:

public function executeRedirect(sfWebRequest $request)
{
  return $this->redirect('http://apps.facebook.com'.sfConfig::get('app_facebook_app_url'));
}

Используйте fb_url_for() хелпер

Для создания универсального приложения, которое может быть использовано как FBML внути Facebook или XFBML в iframe в любую (даже последнюю минуту), необходимо уделить внимание проблеме маршрутизации:

  • Для FBML-приложения, ссылки внутри приложения должны указывать на /app-name/symfony-route;
  • Для iframe приложения важно передавать информацию о сессии facebook при переходе между страницами.

Плагин sfFacebookConnectPlugin предоставляет особый хелпер, который решает указанные проблемы, это fb_url_for() хелпер.

Редиректы внутри FBML приложения

Symfony разработчики быстро привыкают выполнять редирект после успешного POST, и это в общем-то хорошая практика для избежания дублирования данных в БД. Перенаправление в FBML апликации же не работает как ожидается. Вместо этого необходимо применять специализированный таг <fb:redirect> для того чтобы Facebook выполнил редирект. Для сохранения независимости от контекста (FBML таг или обычный редирект) существует специальный статический метод в классе sfFacebook, который может быть использован, например, в экшене сохранения формы:

if ($form->isValid())
{
  $form->save();
  return sfFacebook::redirect($url);
}

Сопоставляем существующих пользователей с их аккаунтами на Facebook

Одна из целей Facebook Connect – упростить регистрацию для новых пользователей. Кроме того, интерес также может представлять сопоставление существующих пользователей с их аккаунтами на Facebook как для получения более полной информации о них, так и для взаимодействия с их фидом. Эта вспомогательная цель может быть достигнута двумя способами:

  • Заставить существующих пользователей кликать на кнопку Connect with Facebook. Экшен sfFacebookConnectAuth/signIn не будет создавать нового пользователя, если он уже залогинен, он лишь будет сохранять данные Faceook пользователя для текущего sfGuard-пользователя. Это просто.
  • Использовать Facebook-систему распознавания email’ов. Когда пользователь использует Facebook Connect на сайте, Facebook может предоставить специальный хэш его email-адреса, который можно сравнить с хэшами в существующей базе данных для того чтобы определить аккаунт, принадлежащий пользователю. Тем не менее, по соображениям безопасности, Facebook предоставляет указанные хэши только если пользователь был зарегистрирован ранее при помощи его API! Таким образом, необходимо регистрировать email’ы всех новых пользователей, чтобы можно было опознать их впоследствии. Этим и занимается таск registerUsers. Этот таск должен запускаться как минимум каждую ночь для того чтобы регистрировать вновь созданных пользователей. В принципе, вы также можете использовать метод registerUsers из класса sfFacebookConnect: sfFacebookConnect::registerUsers(array($sfGuardUser));

Что дальше?

Надеюсь эта статья выполнит свое назначение: поможет вам начать разработку приложения для Facebook c использованием symfony и пояснит основные возможности разработки для Facebook. Тем не менее sfFacebookConnectPlugin не заменяет собой Facebook API, поэтому чтение оригинальной документации никто не отменял, тем более что там вы можете получить самую полную информацию о разработке для facebook.

В заключение, хочу выразить благодарность сообществу symfony за качество и отзывчивость, в особенности тем, кто уже внес вклад в развитие sfFacebookConnectPlugin своими комментариями и патчами: Damien Alexandre, Thomas Parisot, Maxime Picaud, Alban Creton (и да простят меня все кого я тут не упомянул ))) И, конечно же, если вам есть что сказать или дополнить, не колеблясь вносите свой вклад!

Write a Comment

Comment

*