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

Symfony 2 & Facebook: часть 1, первый проект

Уже совсем скоро (хочется надеяться) будет выпущен первый релиз Symfony 2. А по сему пробуем применить его на практике.

Этим постом я планирую начать небольшую серию публикаций про работу с Symfony 2 и Facebook на php 5.3. Что из этого выйдет – время покажет, а пока делаем первые шаги:

Для начала качаем sandbox Symfony 2: http://symfony-reloaded.org/downloads/sandbox_2_0_PR2.zip

Я экспериментирую на локальной машине с denwer‘ом. У вас може быть XAMPP, MAMP или любая другая работоспособная комбинация из вебсервера и PHP 5.3.2+ (у меня установлен PHP 5.3.3).

Создаем директорию z:hometest-project.com и распаковываем содержимое сандбокса в нее. Получится примерно следующее:

z:hometest-project.com
    hello
    src
    web

Web не мудрствуя лукаво переименуем в www чтобы не возиться с ручным конфигом и hosts (в этом случае денвер сделает все настройки самостоятельно. Иначе – вам придется сделать их самим, что займет лишние 2 минуты). Не забываем рестартануть денвера.

Далее качаем с github тем или иным способом facebook php sdk: http://github.com/facebook/php-sdk/. Нас оттуда интересует файл src/facebook.php. Сразу предупрежу – я поступил немного грязно и разделил этот класс на два (их там и есть два, собственно Facebook и FacebookApiExcetion), а также разместил в namespace Facebook. Выглядит это примерно так:

// src/vendor/facebook/Facebook/Facebook.php
<?php

namespace Facebook;

if (!function_exists('curl_init')) {
  throw new Exception('Facebook needs the CURL PHP extension.');
}
if (!function_exists('json_decode')) {
  throw new Exception('Facebook needs the JSON PHP extension.');
}

/**
 * Provides access to the Facebook Platform.
 *
 * @author Naitik Shah <naitik@facebook.com>
 */
class Facebook
{

И вот так:

// src/vendor/facebook/Facebook/FacebookApiException.php
<?php

namespace Facebook;

/**
 * FacebookApiException
 *
 * @author     dmitry.bykadorov@gmail.com
 * @version    SVN: $Id:  $
 */

/**
 * Thrown when an API call returns an exception.
 *
 * @author Naitik Shah <naitik@facebook.com>
 */
class FacebookApiException extends Exception
{

Обратите внимание на строку class FacebookApiException extends Exception. Мы используем стандартный класс Exception, который не принадлежит никакому пространству имен. Ну или принадлежит корневому (стандартному) пространству имен. Честно говоря я пока не очень продвинут в работе с неймспейсами, поэтому в терминологии могу ошибаться ).

Как вы можете видеть, файлы классов расположены в директории src/vendor/facebook/Facebook/. Разработчики Symfony 2 рекомендуют не включать сторонние библиотеки в бандлы или приложения – думаю стоит к этому прислушаться. Поэтому после размещения библиотеки в нашем приложении нам нужно зарегистрировать его в автолоадере. Для того открываем файл src/autoload.php и добавляем туда наш неймспейс в функцию registerNamespaces:

// src/autoload.php
//...

$loader = new UniversalClassLoader();

$loader->registerNamespaces(array(
  'Symfony'                    => __DIR__.'/vendor/symfony/src',
  'Application'                => __DIR__,
  'Bundle'                     => __DIR__,
  'Doctrine\Common'           => __DIR__.'/vendor/doctrine/lib/vendor/doctrine-common/lib',
  'Doctrine\DBAL\Migrations' => __DIR__.'/vendor/doctrine-migrations/lib',
  'Doctrine\ODM\MongoDB'     => __DIR__.'/vendor/doctrine-mongodb/lib',
  'Doctrine\DBAL'             => __DIR__.'/vendor/doctrine/lib/vendor/doctrine-dbal/lib',
  'Doctrine'                   => __DIR__.'/vendor/doctrine/lib',
  'Zend'                       => __DIR__.'/vendor/zend/library',
  'Facebook'                   => __DIR__.'/vendor/facebook/lib',
));
//...

Для реализации тестового примера нам потребуется одна страница, поэтому структуру сандбокса с application bundle hello мы менять не будем (кроме того я пока не придумал зачем нам нужен будет бандл Facebook, поэтому это будет тема следующей статьи).

Создаем iframe приложение Facebook в разделе разработчиков (на этом отдельно не останавливаюсь, статью напишу если попросите). Оттуда нам нужны параметры приложения – app_id, api_key, app_secret. Второй параметр для oAuth не используется, но может будет полезен впоследствии. Наши настройки мы внесем в файл настроек hello/config/config.yml:

# hello/config/config.yml
parameters:
    kernel.include_core_classes: false

    # facebook
    facebook.credentials.app_id: <ваш_app_id>
    facebook.credentials.api_key: <ваш_api_key>
    facebook.credentials.app_secret: <ваш_app_secret>
    facebook.url.canvas:  # будет нужен потом
    facebook.url.site:  # будет нужен потом

Модифицируем HelloController:

// src/Application/HelloBundle/Controller/HelloController.php
<?php

  namespace ApplicationHelloBundleController;

  use Facebook;
  use SymfonyFrameworkFoundationBundleController;

  class HelloController extends Controller
  {
    public function indexAction($name)
    {
      $app_id = $this->container->getParameter('facebook.credentials.app_id');
      $api_key = $this->container->getParameter('facebook.credentials.api_key');
      $app_secret = $this->container->getParameter('facebook.credentials.app_secret');

      // у меня локально без отключения проверки сертификатов не заработало
      FacebookFacebook::$CURL_OPTS[CURLOPT_SSL_VERIFYHOST] = 0;
      FacebookFacebook::$CURL_OPTS[CURLOPT_SSL_VERIFYPEER] = 0;

      $facebook = new FacebookFacebook(
      array(
        'appId' => $app_id,
        'secret' => $app_secret,
        'cookie' => true,
      ));
      $session = $facebook->getSession();

      $me = null;
      $uid = null;

      // Обращение к API на основе сессии
      if ($session)
      {
        try
        {
          $uid = $facebook->getUser();
          $me = $facebook->api('/me');
        }
        catch ( FacebookFacebookApiException $e )
        {
          // пока ничего не делаем
        }
      }

      // login или logout url будут нужны в зависимости от статуса авторизации пользователя
      $logoutUrl = '';
      $loginUrl = '';
      if ($me)
      {
        $logoutUrl = $facebook->getLogoutUrl();
      }
      else
      {
        $loginUrl = $facebook->getLoginUrl();
      }

      // Обращение к API без сессии
      $user = $facebook->api('/dmitry.bykadorov');

      return $this->render(
        'HelloBundle:Hello:index',
        array(
          'facebook' => $facebook,
          'session' => $session,
          'me' => $me,
          'logoutUrl' => $logoutUrl,
          'loginUrl' => $loginUrl,
          'user' => $user,
          'uid' => $uid,
        )
      );
    }
  }

Теперь нужно модифицировать шаблон: добавить в него декоратор (layout – он теперь не цепляется по умолчанию) и сформируем шаблон:

<?php
<!-- src/Application/HelloBundle/Resources/views/Hello/index.php -->
/**
 *
 */

?>

<?php $view->extend('HelloBundle::layout') ?>

<div id="fb-root"></div>

<style>
 body {
 font-family: 'Lucida Grande', Verdana, Arial, sans-serif;
 }
 h1 a {
 text-decoration: none;
 color: #3b5998;
 }
 h1 a:hover {
 text-decoration: underline;
 }
</style>

<script>
 window.fbAsyncInit = function()
 {
 FB.init({
 appId   : '<?php echo $facebook->getAppId(); ?>',
 session : <?php echo json_encode($session); ?>, // не фетчим сессию если она уже есть в PHP
 status  : true, // разрешаем проверку логина
 cookie  : true, // разрешаем куки для проверки сессии
 xfbml   : true // разрешаем парсинг XFBML
 });

 // если юзер вошел надо обновить страницу
 FB.Event.subscribe('auth.login', function()
 {
 window.location.reload();
 });
 };

 (function() {
 var e = document.createElement('script');
 e.src = document.location.protocol + '//connect.facebook.net/en_US/all.js';
 e.async = true;
 document.getElementById('fb-root').appendChild(e);
 }());
</script>

<h1><a href="example.php">php-sdk</a></h1>

<?php if ($me): ?>
<a href="<?php echo $logoutUrl; ?>">
 <img src="http://static.ak.fbcdn.net/rsrc.php/z2Y31/hash/cxrz4k7j.gif">
</a>
<?php else: ?>
<div>
 С использованием JavaScript &amp; XFBML:
 <fb:login-button></fb:login-button>
</div>
<div>
 Без JavaScript &amp; XFBML:
 <a href="<?php echo $loginUrl; ?>">
 <img src="http://static.ak.fbcdn.net/rsrc.php/zB6N8/hash/4li2k73z.gif">
 </a>
</div>
<?php endif ?>

<h3>Сессия</h3>
<?php if ($me): ?>
<pre><?php print_r($session); ?></pre>

<h3>Вы</h3>
<img src="https://graph.facebook.com/<?php echo $uid; ?>/picture">
<?php echo $me['name']; ?>

<h3>Ваш юзерский объект</h3>
<pre><?php print_r($me); ?></pre>
<?php else: ?>
<strong><em>Вы не подключены.</em></strong>
<?php endif ?>

<h3>Юзер</h3>
<img src="https://graph.facebook.com/dmitry.bykadorov/picture">
<?php echo $user['name']; ?>

Ну и немного подправим декорирующий шаблон:

<!-- src/Application/HelloBundle/Resources/views/layout.php -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns:fb="http://www.facebook.com/2008/fbml">
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>

 <table style="width:700px;margin-left:auto;margin-right:auto;" border="1">
 <tr>
 <td>
 <?php $view->slots->output('_content') ?>
 </td>
 </tr>
 </table>

</body>
</html>

В результате мы поимеем примерно следующее:

So… рубикон перейден, надеюсь мы с вами тут же на бережку не разляжемся а двинемся дальше ))

Have fun! ))

Write a Comment

Comment

*

16 Comments

    • Честно говоря за рассылками следить не успеваю ) Вышла – значит посмотрим )

  1. Спасибо, отлично написано!
    Видимо уже не буду изучать версию 1.х а сразу начну работу с 2-ой версии, с задатком на будущее, что многие хостеры начнут переход на 5.3 PHP

    • Symfony 1.x тоже мощный инструмент, не надо его списывать со счетов – года два он нас будет радовать 😉 А Symfony 2 напротив, содержит много инновационных в мире PHP фреймворков идей, поэтому прямо сегодня я бы посоветовал все-таки выполнить Jobeet а после него уже взяться за Symfony 2. Тем не менее это всего лишь моя личная рекоммендация 😉

  2. Глядя на все это, думаю, а так ли нужны эти namespaces в PHP )))))

    • Хз. Еще не понял. В Java меня import *** всегда раздражали, да. Типа require once в PHP. Вроде автолоадинг в PHP5 прекратил это. Теперь вот роллбэк к тому же самому но в новом качестве.

  3. Для Денвера можно не переименовывать web > www, Денвер сгенерит хосты и для него. Просто доступ к нему будет через поддомен: http://web.yourhost/

    • Согласен ) Просто меня поддомен раздражает немного. Чисто субъективно.

    • Я предпочитаю делать следующую структуру проекта на denwer

      project_name – название проекта
      project – здесь лежат все исходники
      hello
      src
      web
      www – симлинк на project/web – делаю в total плагином NTFS Link

      Так избавляюсь от поддоменов и необходимости редактировать файл host

      • Что за плагин такой? В NTFS есть аналог символических ссылок?

          • По-моему в NTFS для папок все-таки символические ссылки (junction). А для файлов — жесткие.
            В win 7 они очень активно юзаются. Например:
            – це:Documents and Settings это симлинка на це:Users
            – це:Users/username/Application Data это симлинка на це:Users/username/AppData/Roaming
            – …
            Для создания таких ссылок своими силами есть несколько способов:
            – плагин в тотал коммандере
            – стороние утилиты (linkd.exe, ln.exe, junction.exe, …) – актуально для винд до висты
            – родные утилиты (вроде начиная с висты и вин7): mklink.exe

  4. А почему вы больше склоняетесь к интеграции с facebook при помощи iframe, а не FBML? В пользу iframe можно сказать что есть возможность использования JS-библиотек (мой любимый Jquery=)), но и существует неудобство связанное с IE6/7, теряются переменные сессии (cookies). Было бы очень интересно услышать вашу точку зрения по этому вопросу))

    • Судя по опыту (>года) FBML тоже теряет сессии.

      Теперь к сути вопроса. Почему я против FBML? Я не против! Просто его не поддерживает команда Facebook. Взгляните-ка на roadmap: http://developers.facebook.com/roadmap

      No new FBML applications | End of 2010| We will stop allowing new FBML applications, but will continue to support existing FBML tabs and applications. Instead, we recommend using IFrames.

      Т.о. FBML вымрет (по моим прогнозам) к лету 2011 года.