<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>hudson@work &#187; symfony advent calendar&#8217;09</title>
	<atom:link href="http://hudson.su/tag/symfony-advent-calendar09/feed/" rel="self" type="application/rss+xml" />
	<link>http://hudson.su</link>
	<description>статьи о web-разработке, менеджменте IT проектов и контроле качества</description>
	<lastBuildDate>Fri, 20 Jan 2012 13:15:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
		<item>
		<title>The symfony 2009 Advent Calendar: день 3 &#8211; продвинутая маршрутизация (часть 2)</title>
		<link>http://hudson.su/2010/01/15/the-symfony-2009-advent-calendar-day3-advanced-routing-p2/</link>
		<comments>http://hudson.su/2010/01/15/the-symfony-2009-advent-calendar-day3-advanced-routing-p2/#comments</comments>
		<pubDate>Fri, 15 Jan 2010 01:39:27 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony advent calendar'09]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=740</guid>
		<description><![CDATA[Symfony 2009 advent calendar &#8211; это 24 урока продвинутого уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить. Перевод статьи Symfony 2009 Advent Calendar: Advanced Routing (part 2). Коллекции маршрутов Для завершения проекта Sympal Builder нам нужно создать админку, в которой каждый клиент [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p><strong>Symfony 2009 advent calendar</strong> &#8211; это 24 урока <strong>продвинутого</strong> уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить.</p></blockquote>
<p>Перевод статьи <a href="http://www.symfony-project.org/advent_calendar/3/en" target="_blank">Symfony 2009 Advent Calendar: Advanced Routing (part 2)</a>.</p>
<p><span id="more-740"></span></p>
<h2>Коллекции маршрутов</h2>
<p>Для завершения проекта Sympal Builder нам нужно создать админку, в которой каждый клиент может управлять страницами своего сайтика. Для того, чтобы это реализовать, нам нужен набор экшенов (actions), которые будут осуществлять list, create, update, delete для объектов Page. Так как эти действия стандартные, мы можем сгенерировать модуль автоматически. Выполним следующий таск из командной строки, для генерации модуля в апликации backend:</p>
<pre>$ php symfony doctrine:generate-module backend pageAdmin Page --with-doctrine-route --with-show
</pre>
<p>Будет сгенерирован модуль с файлом actions.class.php и соответствующие ему шаблоны, которые позволят выполнять необходимые действия для объекта Page. Кастомизацию сгенерированного CRUD оставим за пределами нашего рассказа ))</p>
<p>Не смотря на то, что мы сгенерировали нужный модуль автоматически, нам нужно еще создать маршрут для каждого экшена. Так как мы использовали при генерации опцию &#8211;with-doctrine-route, то каждый экшен был сгенерирован таким образом, чтобы работать с объектом маршрута. Это уменьшает объем кода в каждом экшене. Например, экшн <strong>edit </strong>состоит из одной строчки:</p>
<pre>public function executeEdit(sfWebRequest $request)
{
  $this-&gt;form = new PageForm($this-&gt;getRoute()-&gt;getObject());
}
</pre>
<p>В итоге, нам нужны маршруты для действий: <strong>index, new, create, edit, update, delete</strong>. Для создания этих маршрутов в <a href="http://en.wikipedia.org/wiki/Representational_State_Transfer">RESTful</a> стиле, внесем изменения в routing.yml:</p>
<pre>pageAdmin:
  url:         /pages
  class:       sfDoctrineRoute
  options:     { model: Page, type: list }
  params:      { module: page, action: index }
  requirements:
    sf_method: [get]
pageAdmin_new:
  url:        /pages/new
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: new }
  requirements:
    sf_method: [get]
pageAdmin_create:
  url:        /pages
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: create }
  requirements:
    sf_method: [post]
pageAdmin_edit:
  url:        /pages/:id/edit
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: edit }
  requirements:
    sf_method: [get]
pageAdmin_update:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: update }
  requirements:
    sf_method: [put]
pageAdmin_delete:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: delete }
  requirements:
    sf_method: [delete]
pageAdmin_show:
  url:        /pages/:id
  class:      sfDoctrineRoute
  options:    { model: Page, type: object }
  params:     { module: page, action: show }
  requirements:
    sf_method: [get]</pre>
<p>Для того чтобы визуально представить эти маршруты, воспользуемся таском <strong>app:routes</strong>, который отображает данные о каждом маршруте для указанной апликации (в рамках проекта):</p>
<pre>$ php symfony app:routes backend

&gt;&gt; app       Current routes for application "backend"
Name             Method Pattern
pageAdmin        GET    /pages
pageAdmin_new    GET    /pages/new
pageAdmin_create POST   /pages
pageAdmin_edit   GET    /pages/:id/edit
pageAdmin_update PUT    /pages/:id
pageAdmin_delete DELETE /pages/:id
pageAdmin_show   GET    /pages/:id
</pre>
<h3>Заменяем маршруты на коллекцию маршрутов</h3>
<p>К счастью, symfony предлагает нам более простой путь для создания маршрутов, которые относятся к традиционному CRUD. Заменим все что мы написали в routing.yml на один маршрут:</p>
<pre>pageAdmin:
  class:   sfDoctrineRouteCollection
  options:
    model:        Page
    prefix_path:  /pages
    module:       pageAdmin</pre>
<p>Еще раз выполним таск <strong>app:routes</strong> для того чтобы посмотреть что получилось. Как вы можете видеть, все наши маршруты остались на месте:</p>
<pre>$ php symfony app:routes backend

&gt;&gt; app       Current routes for application "backend"
Name             Method Pattern
pageAdmin        GET    /pages.:sf_format
pageAdmin_new    GET    /pages/new.:sf_format
pageAdmin_create POST   /pages.:sf_format
pageAdmin_edit   GET    /pages/:id/edit.:sf_format
pageAdmin_update PUT    /pages/:id.:sf_format
pageAdmin_delete DELETE /pages/:id.:sf_format
pageAdmin_show   GET    /pages/:id.:sf_format
</pre>
<p>Коллекция маршрутов это специальный тип объекта маршрута, который представляет более чем один маршрут. Маршрут <strong>sfDoctrineRouteCollection</strong>, например, автоматически генерирует семь основных маршрутов, необходимых для <strong>CRUD</strong>. За кулисами же, <strong>sfDoctrineRouteCollection </strong>кроме создания семи маршрутов, которые мы раньше описывали в routing.yml, ничего не делает. Коллекции маршрутов, в основном существуют в качестве ярлыка для создания типичных групп маршрутизации.</p>
<h3>Создание новой коллекции маршрутов</h3>
<p>Сейчас, каждый клиент имеет возможность модифицировать объекты Page при помощи CRUD по адресу <strong>/pages</strong>. К несчастью, каждый клиент сейчас также может видеть и модифицировать <strong>все </strong>страницы, в том числе и не принадлежащие ему. Например, по адресу <strong>http://pete.sympalbuilder.com/backend.php/pages</strong> отобразится список, состоящий из обеих страниц из нашей фикстуры (см. <a href="http://hudson.su/2010/01/721/" target="_blank">первую часть</a>) &#8211; страницы location из магазина зверюшек Пита и страницы menu из СитиПаба.</p>
<p>Для того чтобы поправить положение, мы используем ранее определенный для фронтэнда класс <strong>acClientObjectRoute</strong>. Класс <strong>sfDoctrineRouteCollection </strong>генерирует группу объектов <strong>sfDoctrineRoute</strong>. В нашем же приложении нужно сгенерировать группу объектов <strong>acClientObjectRoute</strong>.</p>
<p>Для того чтобы выполнить нашу задумку, нужно создать новый класс коллекции маршрутов. Создадим файл <strong>acClientObjectRouteCollection.class.php</strong> и разместим его в директории <strong>lib/routing</strong>. Его содержимое невероятно просто:</p>
<pre>// lib/routing/acClientObjectRouteCollection.class.php
class acClientObjectRouteCollection extends sfObjectRouteCollection
{
  protected
    $routeClass = 'acClientObjectRoute';
}</pre>
<p>Свойство $routeClass определяет класс, который будет использоваться для создания каждого маршрута. Теперь, когда мы изменили тип маршрутов, основная часть работы выполнена. Например, по адресу <strong>http://pete.sympalbuilder.com/backend.php/pages</strong> теперь будет отображаться только одна страница: location из магазина зверюшек Пита. Благодаря нашему классу маршрута, экшн index возвращает только те объекты Page, которые соответствуют клиенту, основываясь на поддомене из запроса. Написав всего-лишь несколько строк кода, мы создали модуль для админки, который может безопасно использоваться различными клиентами.</p>
<h3>Недостающая часть: создание новых страниц</h3>
<p>Сейчас, при создании (или редактировании) страницы, отображается селектбокс для выбора клиента, к которому она относится. Вместо того , чтобы давать пользователю возможность выбирать клиента (что есть потенциальная дыра в безопасности), будем устанавливать клиента автоматически, по поддомену из запроса.</p>
<p>Для начала, изменим объект PageForm (lib/form/PageForm.class.php):</p>
<pre>public function configure()
{
  $this-&gt;useFields(array(
    'title',
    'content',
  ));
}</pre>
<p>Теперь селектбокс не будет отображаться, чего мы и хотели. Но теперь при создании страницы мы не сможем установить client_id (( Для того чтобы исправить этот баг, укажем необходимого клиента для экшенов new и create:</p>
<pre>public function executeNew(sfWebRequest $request)
{
  $page = new Page();
  $page-&gt;Client = $this-&gt;getRoute()-&gt;getClient();
  $this-&gt;form = new PageForm($page);
}</pre>
<p>Тут мы используем функцию getClient(), которая пока еще не существует )) в классе <strong>acClientObjectRoute</strong>. Добавим этот метод путем небольших модификаций:</p>
<pre>// lib/routing/acClientObjectRoute.class.php
class acClientObjectRoute extends sfDoctrineRoute
{
  // ...

  protected $client = null;

  public function matchesUrl($url, $context = array())
  {
    // ...

    $this-&gt;client = $client;

    return array_merge(array('client_id' =&gt; $client-&gt;id), $parameters);
  }

  public function getClient()
  {
    return $this-&gt;client;
  }
}</pre>
<p>Путем добавления свойства $client (которое принимает значение в методе matchesUrl()), мы легким движением руки сделаем доступным объект клиента через маршрут. Колонка client_id для нового объекта Page теперь автоматически (и правильно) устанавливается на основании поддомена текущего хоста.</p>
<h2>Настройка коллекции маршрутов</h2>
<p>Используя маршрутизатор, мы легко решили проблемы, которые у нас возникали при создании нашего вымышленного приложения Sympal Builder. По мере роста приложения, разработчик должен иметь возможность повторного использования маршрутов для других модулей в админке (например, для создания фотогалерей для каждого клиента).</p>
<p>С другой стороны, создавать коллекции маршрутов можно также для добавления часто используемых маршрутов. Например, предположим, в проекте есть много моделей с колонкой is_active. В админке нужно иметь простой способ переключения статуса is_active для любого объекта. Для начала, модифицируем <strong>acClientObjectRouteCollection </strong>и добавим новый маршрут в коллекцию:</p>
<pre>// lib/routing/acClientObjectRouteCollection.class.php
protected function generateRoutes()
{
  parent::generateRoutes();

  if (isset($this-&gt;options['with_is_active']) &amp;&amp; $this-&gt;options['with_is_active'])
  {
    $routeName = $this-&gt;options['name'].'_toggleActive';

    $this-&gt;routes[$routeName] = $this-&gt;getRouteForToggleActive();
  }
}</pre>
<p>Метод <strong>sfRouteCollection::generateRoutes()</strong> вызывается когда объект коллекции инициализирован и готов к созданию всех необходимых маршрутов и добавлению их переменную класса $routes. Тут мы добавляем новый protected метод <strong>getRouteForToggleActive()</strong>:</p>
<pre>protected function getRouteForToggleActive()
{
  $url = sprintf(
    '%s/:%s/toggleActive.:sf_format',
    $this-&gt;options['prefix_path'],
    $this-&gt;options['column']
  );

  $params = array(
    'module' =&gt; $this-&gt;options['module'],
    'action' =&gt; 'toggleActive',
    'sf_format' =&gt; 'html'
  );

  $requirements = array('sf_method' =&gt; 'put');

  $options = array(
    'model' =&gt; $this-&gt;options['model'],
    'type' =&gt; 'object',
    'method' =&gt; $this-&gt;options['model_methods']['object']
  );

  return new $this-&gt;routeClass(
    $url,
    $params,
    $requirements,
    $options
  );
}</pre>
<p>Теперь остался один шаг &#8211; настройка коллекции маршрутов в routing.yml. Обратите внимание, что <strong>generateRoutes()</strong> ищет опцию with_is_active перед добавлением нового маршрута. Добавление этого куска кода дает нам лучший контроль в ситуации, когда мы хотим использовать acClientObjectRouteCollection где-то, где нет необходимости в маршруте toggleActive:</p>
<pre># apps/frontend/config/routing.yml
pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    model:          Page
    prefix_path:    /pages
    module:         pageAdmin
    with_is_active: true</pre>
<p>Выполним таск <strong>app:routes</strong> и удостоверимся что новый маршрут toggleActive наличествует в списке. Нам осталось лишь создать экшн, который будет делать грязную работу )) Так как вы возможно захотите использовать эту коллекцию маршрутов в различных модулях, создадим файл <strong>backendActions.class.php </strong>в директории <strong>apps/backend/lib/action</strong> (директорию надо будет создать):</p>
<pre># apps/backend/lib/action/backendActions.class.php
class backendActions extends sfActions
{
  public function executeToggleActive(sfWebRequest $request)
  {
    $obj = $this-&gt;getRoute()-&gt;getObject();

    $obj-&gt;is_active = !$obj-&gt;is_active;

    $obj-&gt;save();

    $this-&gt;redirect($this-&gt;getModuleName().'/index');
  }
}</pre>
<p>И, наконец, изменим базовый класс <strong>pageAdminActions</strong>, чтобы он наследовался от нашего только что созданного класса <strong>backendActions</strong>:</p>
<pre>class pageAdminActions extends backendActions
{
  // ...
}</pre>
<p>И чего же мы достигли? Теперь каждый новый модуль может автоматически использовать новый функционал всего лишь используя <strong>acClientObjectRouteCollection </strong>и наследуясь от класса <strong>backendActions</strong>. Таким образом, общий функционал может быть легко использоваться различными модулями.</p>
<h2>Свойства коллекции маршрутов</h2>
<p>Объект коллекции маршрутов содержит ряд свойств (опций), которые позволяют производить его гибкую настройку. Во многих случаях, разработчик может использовать эти свойства для конфигурирования коллекции без необходимости создавать новый класс коллекции маршрутов. Подробный список свойств коллекции маршрутов вы можете посмотреть в справочнике <a href="http://www.symfony-project.org/reference/1_3/en/10-Routing#chapter_10_sfobjectroutecollection">The symfony Reference Book</a>.</p>
<h3>Маршруты экшенов</h3>
<p>Каждый объект коллекции маршрутов принимает три различные опции, который определяют маршруты, которые будут сгенерированы в коллекции. Не вдаваясь в детали, следующая коллекция будет генерировать все семь маршрутов по-умолчанию, наряду с дополнительной коллекцией маршрутов и объектом маршрута:</p>
<pre>pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    actions:      [list, new, create, edit, update, delete, show]
    collection_actions:
      indexAlt:   [get]
    object_actions:
      toggle:     [put]</pre>
<h3>Колонка (Column)</h3>
<p>По умолчанию, первичный ключ модели используется во всех сгенерированных URLs и используется в запросах к БД которые выполняются для получения объектов. Это легко можно изменить. Например, следующий код будет использовать колонку slug вместо первичного ключа:</p>
<pre>pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    column: slug</pre>
<h3>Методы модели</h3>
<p>По умолчанию, маршрут получает все зависимые объекты для коллекции маршрутов по указанной колонке. Если такое поведение нужно изменить, добавьте свойство model_methods в маршрут. В этом примере методы fetchAll() и findForRoute() должны быть добавлены к классу PageTable. Оба метода будут получать массив параметров запроса как аргумент:</p>
<pre>pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    model_methods:
      list:       fetchAll
      object:     findForRoute</pre>
<h3>Параметры по умолчанию</h3>
<p>Наконец, предположим что вам нужно сделать особый параметр запроса, доступным в запросе для каждого маршрута в коллекции. Это легко сделать при помощи параметра default_params:</p>
<pre>pageAdmin:
  class:   acClientObjectRouteCollection
  options:
    # ...
    default_params:
      foo:   bar</pre>
<h2>Заключение</h2>
<p>Основная работа маршрутизатора &#8211; сверять и генерировать URLы &#8211; в symfony переросла в гибкую систему, способную обслуживать сложные URL, необходимые в проектах. Под контролем объекта маршрута, структура URL может быть абстрагирована от бизнес-логики и целиком храниться в маршруте, которому принадлежит. В результате мы получаем больший контроль, большую гибкость и более управляемый код.</p>
<div style="overflow: hidden;width: 1px;height: 1px">&lt;blockquote&gt;&lt;strong&gt;Symfony 2009 advent calendar&lt;/strong&gt; &#8211; это 24 урока &lt;strong&gt;продвинутого&lt;/strong&gt; уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить.&lt;/blockquote&gt;</div>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/01/15/the-symfony-2009-advent-calendar-day3-advanced-routing-p2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The symfony 2009 Advent Calendar: день 2 &#8211; продвинутая маршрутизация (часть 1)</title>
		<link>http://hudson.su/2010/01/14/the-symfony-2009-advent-calendar-day2-advanced-routing-p1/</link>
		<comments>http://hudson.su/2010/01/14/the-symfony-2009-advent-calendar-day2-advanced-routing-p1/#comments</comments>
		<pubDate>Thu, 14 Jan 2010 01:23:29 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony advent calendar'09]]></category>
		<category><![CDATA[sympal]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=721</guid>
		<description><![CDATA[Symfony 2009 advent calendar &#8211; это 24 урока продвинутого уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить. Перевод статьи Symfony 2009 Advent Calendar: Advanced Routing (part 1). by Ryan Weaver Маршрутизатор (или же каркас маршрутизации), если смотреть изнутри, представляет из себя карту, [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p><strong>Symfony 2009 advent calendar</strong> &#8211; это 24 урока <strong>продвинутого</strong> уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить.</p></blockquote>
<p>Перевод статьи <a href="http://www.symfony-project.org/advent_calendar/2/en" target="_blank">Symfony 2009 Advent Calendar: Advanced Routing (part 1)</a>.</p>
<p><span id="more-721"></span></p>
<p><em>by Ryan Weaver</em></p>
<p>Маршрутизатор (или же каркас маршрутизации), если смотреть изнутри, представляет из себя карту, которая связывает каждый URL с некоторой локацией внутри проекта на symfony и наоборот. Он может с легкостью создать красивые URL, оставаясь независимым от логики приложения. Учитывая прогресс, которого удалось достигнуть в предыдущих версиях symfony, маршрутизатор делает следующий шаг вперед.</p>
<p>Эта глава иллюстрирует процесс создания простого веб-приложения, в котором каждый клиент использует отдельный субдомен (например client1.mydomain.com и client2.mydomain.com). Это будет совсем несложно, когда мы расширим возможности маршрутизатора ))</p>
<blockquote><p>Для примера из этой главы мы будем использовать Doctrine в качестве ORM</p></blockquote>
<h2>Настройка проекта: CMS для многих клиентов</h2>
<p>Для начала представим некую вымышленную компанию, Sympal Builder, которая желает создать CMS, с помощью которой ее клиенты смогут создавать вебсайты на поддоменах сайта sympalbuilder.com. Т.е. клиент XXX может просмотреть свой сайт по адресу xxx.sympalbuilder.com и использовать админку по адресу xxx.sympalbuilder.com/backend.php.</p>
<blockquote><p>Наименование Sympal мы позаимствовали у одноименного проекта Jonathan Wage &#8211; <a href="http://www.sympalphp.org/" target="_blank">Sympal</a>, CMF, созданная на symfony.</p></blockquote>
<p>Проект имеет два основных требования:</p>
<ul>
<li>Пользователь должен иметь возможность создавать страницы и указывать для них title, content и URL.</li>
<li>Приложение должно быть создано внутри одного проекта symfony, который контролирует фронтэнд и бэкэнд всех клентских сайтов, определяя клиента и загружая корректные данные для поддомена.</li>
</ul>
<blockquote><p>Для создания нашего приложения, вебсервер должен быть настроен направлять все запросы на *.sympalbuilder.com на тот же document root &#8211; web директорию нашего проекта на symfony.</p></blockquote>
<h3>Схема и данные</h3>
<p>База данных для проекта будет состоять из объектов Client и Page. Каждый Client представлен на своем поддомене и может содержать много объектов Page.</p>
<pre># config/doctrine/schema.yml
Client:
  columns:
    name:       string(255)
    subdomain:  string(50)
  indexes:
    subdomain_index:
      fields:   [subdomain]
      type:     unique

Page:
  columns:
    title:      string(255)
    slug:       string(255)
    content:    clob
    client_id:  integer
  relations:
    Client:
      alias:        Client
      foreignAlias: Pages
      onDelete:     CASCADE
  indexes:
    slug_index:
      fields:   [slug, client_id]
      type:     unique
</pre>
<blockquote><p>Хотя индексы для таблиц не являются необходимыми, создать их все же рекомендуется, поскольку приложение будет часто запрашивать данные из них.</p></blockquote>
<p>Для того чтобы наш проект заработал, необходимо разместить следующие тестовые данные в файле <code>data/fixtures/fixtures.yml</code>:</p>
<pre># data/fixtures/fixtures.yml
Client:
  client_pete:
    name:      Pete's Pet Shop
    subdomain: pete
  client_pub:
    name:      City Pub and Grill
    subdomain: citypub

Page:
  page_pete_location_hours:
    title:     Location and Hours | Pete's Pet Shop
    content:   We're open Mon - Sat, 8 am - 7pm
    slug:      location
    Client:    client_pete
  page_pub_menu:
    title:     City Pub And Grill | Menu
    content:   Our menu consists of fish, Steak, salads, and more.
    slug:      menu
    Client:    client_pub</pre>
<p>Это тестовые данные для двух вебсайтов, каждый из них имеет по одной странице. Полный URL каждой страницы определяется колонкой <strong>subdomain</strong> в таблице Client и колонкой <strong>slug </strong>в таблице Page.</p>
<pre>http://pete.sympalbuilder.com/location

http://citypub.sympalbuilder.com/menu
</pre>
<h3>Маршрутизация</h3>
<p>Каждая страница вебсайта Sympal Builder напрямую соответствует объекту Page, который определяет ее title и content. Для того чтобы связать каждый URL с его страницей, создадим маршрут типа <code>sfDoctrineRoute</code>, который использует поле slug:</p>
<pre># apps/frontend/config/routing.yml
page_show:
  url:        /:slug
  class:      sfDoctrineRoute
  options:
    model:    Page
    type:     object
  params:
    module:   page
    action:   show
</pre>
<p>Этот маршрут связывает страницу http://pete.sympalbuilder.com/location с соответствующим объектом Page. К несчастью, этот маршрут также соответствует URL&#8217;у http://pete.sympalbuilder.com/menu, т.е. меню ресторана (это другая страница, которая относится к клиенту citypub) также будет отбражаться и на сайте Пита!  На этом шаге маршрут не знает о важности клиентских поддоменов.</p>
<p>Для того чтобы проект заработал, маршрут должен стать более умным. Он должен находить корректную страницу основываясь на полях slug и client_id одновременно, что позволит определять соответствующий хост (например pete.sympalbuilder.com) по колонке subdomain в модели Client. Для того чтобы достичь этого, мы воспользуемся каркасом маршрутизации и создадим свой собственный класс маршрута.</p>
<p>Но, для начала, нам необходимо немного узнать о том, как работает система маршрутизации.</p>
<h3>Как работает система маршрутизации</h3>
<p>Не смотря на то, что маршруты в основном определены в YAML-файле, они трансформируются в объект в момент вызова при помощи специального класса, который называется cache config handler. В итоге, PHP код представляет каждый маршрут в приложении. Поскольку специфика данного процесса выходит за рамки данной статьи, давайте сразу перейдем к его окончанию, т.е. к компилированной версии маршрута page_show. Компилированный файл находится в файле cache/yourappname/envname/config/config_routing.yml.php для конкретного приложения (app) и окружения (env). Ниже приведена сокращенная версия того как выглядит в итоге маршрут page_show:</p>
<pre>new sfDoctrineRoute('/:slug', array (
  'module' =&gt; 'page',
  'action' =&gt; 'show',
), array (
  'slug' =&gt; '[^/\.]+',
), array (
  'model' =&gt; 'Page',
  'type' =&gt; 'object',
));</pre>
<blockquote><p>Наименование класса каждого маршрута определяется ключом class внутри файла routing.yml. Если этот ключ не указан, маршрут по умолчанию будет являться классом sfRoute. Другой стандартный класс маршрута &#8211; это sfRequestRoute, который позволяет разработчику создавать RESTful маршруты. Полный список классов маршрутов и доступных для них опций вы можете найти в с <a href="http://www.symfony-project.org/reference/1_3/en/10-Routing" target="_blank">правочнике symfony</a>.</p></blockquote>
<h3>Сверка входящего запроса с конкретным маршрутом</h3>
<p>Одно из основных занятий маршрутизатора &#8211; сверять каждый входящий URL с корректным объектом маршрута. Класс sfPatternRouting это ядро движка маршрутизации и занимается только этой задачей. Не смотря на его важность, разработчики редко взаимодействуют напрямую с sfPatternRouting.</p>
<p>Для определения корректного маршрута, sfPatternRouting циклически просматривает каждый sfRoute и &#8220;спрашивает&#8221;, подходит ли ему входящий URL. Изнутри это выглядит следующим образом: sfPatternRouting вызывает метод sfRoute::matchesUrl() для каждого объекта маршрута. Этот метод просто возвращает false, если маршрут не соответствует входящему URL.</p>
<p>А вот если маршрут <strong>соответствует </strong>входящему URL, sfRoute::matchesUrl() делает несколько больше, нежели просто возвращает true. Вместо этого, маршрут возвращает массив параметров, которые объединяются с объектом запроса. Например, URL http://pete.sympalbuilder.com/location соответствует маршруту page_show, чей метод matchesUrl() вернет следующий массив:</p>
<pre>array('slug' =&gt; 'location')</pre>
<p>Эти данные будут включены в объект запроса, именно поэтому можно получить доступ к переменным маршрута (например, slug) из экшена или из других мест где это требуется.</p>
<pre>$this-&gt;slug = $request-&gt;getParameter('slug');</pre>
<p>Как вы могли предположить, переопределение метода sfRoute::matchesUrl() это отличный способ настроить маршрут и сделать почти все что угодно.</p>
<h3>Создание собственного класса маршрута</h3>
<p>Для того чтобы расширить возможности маршрута page_show для того чтобы он осуществлял проверку, основываясь на поддомене из объекта Client, мы создадим свой собственный класс маршрута. Создадим файл acClientObjectRoute.class.php и разместим его в директории lib/routing нашего проекта (вам нужно будет создать эту директорию):</p>
<pre>// lib/routing/acClientObjectRoute.class.php
class acClientObjectRoute extends sfDoctrineRoute
{
  public function matchesUrl($url, $context = array())
  {
    if (false === $parameters = parent::matchesUrl($url, $context))
    {
      return false;
    }

    return $parameters;
  }
}</pre>
<p>Следующим шагом проинструктируем маршрут page_show чтобы он использовал наш класс маршрута. В routing.yml нужно обновить ключ class в маршруте:</p>
<pre># apps/fo/config/routing.yml
page_show:
  url:        /:slug
  class:      acClientObjectRoute
  options:
    model:    Page
    type:     object
  params:
    module:   page
    action:   show
</pre>
<p>Пока что acClientObjectRoute не добавляет функциональности, но тем не менее все части на своих местах. Метод matchesUrl имеет две специфичные функции.</p>
<h3>Добавляем логику в наш маршрут</h3>
<p>Для того чтобы добавить в маршрут необходимую функциональность, заменим содержимое файла acClientObjectRoute.class.php на следующее:</p>
<pre>class acClientObjectRoute extends sfDoctrineRoute
{
  protected $baseHost = '.sympalbuilder.com';

  public function matchesUrl($url, $context = array())
  {
    if (false === $parameters = parent::matchesUrl($url, $context))
    {
      return false;
    }

    // return false if the baseHost isn't found
    if (strpos($context['host'], $this-&gt;baseHost) === false)
    {
      return false;
    }

    $subdomain = str_replace($this-&gt;baseHost, '', $context['host']);

    $client = Doctrine_Core::getTable('Client')
      -&gt;findOneBySubdomain($subdomain)
    ;

    if (!$client)
    {
      return false;
    }

    return array_merge(array('client_id' =&gt; $client-&gt;id), $parameters);
  }
}</pre>
<p>Вызов parent::matchesUrl() необходим чтобы запустить обычный процесс проверки соответствия маршрутов. В нашем примере, так как URL /location соответствует маршруту page_show, метод parent::matchesUrl() должен вернуть соответствующий параметр slug:</p>
<pre>array('slug' =&gt; 'location')</pre>
<p>Другими словами, всю тяжелую работу по сравнению маршрутов сделали за нас ))) это позволяет нам сфокусироваться на проверке соответствия, основываясь на поддомене клиента.</p>
<pre>public function matchesUrl($url, $context = array())
{
  // ...

  $subdomain = str_replace($this-&gt;baseHost, '', $context['host']);

  $client = Doctrine_Core::getTable('Client')
    -&gt;findOneBySubdomain($subdomain)
  ;

  if (!$client)
  {
    return false;
  }

  return array_merge(array('client_id' =&gt; $client-&gt;id), $parameters);
}</pre>
<p>Путем выполнения простой строковой замены мы можем изолировать кусочек, соответствующий поддомену и запросить базу данных, есть ли клиент с таким поддоменом. Если ни один клиент не соответствует этому критерию, тогда мы с чистой совестью вернем false, подразумевая этим, что запрос не соответствует маршруту. Если же мы нашли клиента с текущим поддоменом, мы объединяем дополнительный параметр client_id с возвращаемым массивом.</p>
<blockquote><p>Массив $context, передаваемый в matchesUrl заполняется кучей полезной информации о текущем запросе, включая host, is_secure, request_uri, HTTP метод и так далее&#8230;</p></blockquote>
<p>И чего же мы в конце концов добились? Класс acClientObjectRoute теперь делает следующее:</p>
<ul>
<li>Входящий $url будет соответствовать только если host содержит поддомен, соответствующий одному их клиентских объектов.</li>
<li>Если маршрут соответствует, метод возвращает дополнительный параметр &#8211; client_id соответствующего клиента, объединяемый с параметрами запроса.</li>
</ul>
<h3>Используем новый маршрут</h3>
<p>Теперь, когда acClientObjectRoute возвращает корректный параметр client_id, мы можем получить его значение через объект запроса. Например, экшен page/show может использовать client_id для определения корректного объекта Page:</p>
<pre>public function executeShow(sfWebRequest $request)
{
  $this-&gt;page = Doctrine_Core::getTable('Page')-&gt;findOneBySlugAndClientId(
    $request-&gt;getParameter('slug'),
    $request-&gt;getParameter('client_id')
  );

  $this-&gt;forward404Unless($this-&gt;page);
}</pre>
<blockquote><p>Метод findOneBySlugAndClientId() это один из магических поисковых методов(<a href="http://www.doctrine-project.org/upgrade/1_2#Expanded%20Magic%20Finders%20to%20Multiple%20Fields">magic finder</a>) &#8211; нововведение Doctrine 1.2, которое позволяет искать объекты, основываясь на нескольких полях.</p></blockquote>
<p>Маршрутизатор может предложить даже еще более элегантное решение. Для начала добавим следующий метод в класс acClientObjectRoute:</p>
<pre>protected function getRealVariables()
{
  return array_merge(array('client_id'), parent::getRealVariables());
}</pre>
<p>Теперь, экшен может полностью полагаться на маршрут, который вернет правильный объект. Теперь экшен page/show может уместиться в одну строку:</p>
<pre>public function executeShow(sfWebRequest $request)
{
  $this-&gt;page = $this-&gt;getRoute()-&gt;getObject();
}</pre>
<p>Без лишних телодвижений, этот код будет запрашивать объект Page, основываясь на обеих колонках: slug и client_id. Кроме того, как и все другие объекты маршрутов, экшен будет автоматически перенаправлен на страницу 404, если соответствующий объект не найден.</p>
<p>Как же это работает? Объекты маршрутов, например sfDoctrineRoute, который расширяет наш класс acClientObjectRoute, автоматически запрашивает соответствующий объект, основываясь на переменных в ключе <strong>url </strong>маршрута. Например, маршрут page_show, который содержит переменную :slug в его url, будет запрашивать объект Page, основываясь на колонке slug.</p>
<p>В этом приложении, маршрут page_show должен также запрашивать объекты Page, основываясь на колонке client_id. Для того чтобы добиться этого, мы переопределили метод sfObjectRoute::getRealVariables(), который вызывается внутри для определения какие колонки нужно использовать для запроса объекта. Т.о., добавив в этот массив client_id, мы дали возможность acClientObjectRoute сформировать запрос используя и slug и client_id.</p>
<blockquote><p>Объекты маршрутов автоматически игнорируют любые переменные, которые не соответствуют реальным столбцам в базе данных. Например, если URL содержит переменную :page, но соответствующая таблица такой колонки не имеет, эта переменная будет проигнорирована.</p></blockquote>
<p>Итак, теперь наш маршрут умеет все что нам необходимо, при этом мы затратили очень мало усилий чтобы достичь этого. В следующей части мы используем наш маршрут еще раз, чтобы создать админку для пользователей.</p>
<h3>Генерируем корректный URL</h3>
<p>У нас осталась одна маленькая проблема, связанная с генерацией нашего маршрута. Допустим нам нужно создать ссылку на страницу при помощи следующего кода:</p>
<pre>&lt;?php echo link_to('Locations', 'page_show', $page) ?&gt;

Генерируется url: /location?client_id=1</pre>
<p>Как вы видите, client_id автоматически добавляется к url. Это происходит потому что маршрут пытается использовать все его переменные для генерации URL. Так как маршрут теперь осведомлен о двух параметрах &#8211; slug и client_id, он использует их оба для создания маршрута.</p>
<p>Для того чтобы поправить этот баг, добавим следующий метод к классу acClientObjectRoute :</p>
<pre>protected function doConvertObjectToArray($object)
{
  $parameters = parent::doConvertObjectToArray($object);

  unset($parameters['client_id']);

  return $parameters;
}</pre>
<p>Когда объект маршрута создается, он пытается получить всю необходимую информацию, вызывая doConvertObjectToArray(). По умолчанию, client_id возвращается в массиве $parameters. Когда мы его удаляем (unset), мы предотвращаем включение его в сгенерированный URL. Помните, мы позволили себе подобную роскошь, поскольку информация о клиенте содержится в его поддомене.</p>
<blockquote><p>Вы можете переопределить doConvertObjectToArray процесс целиком и обрабатывать его самостоятельно, путем добавления метода toParams в класс модели. Этот метод должен возвращать массив параметров, которые вы хотите использовать в процессе генерации маршрута.</p></blockquote>
<p>Продолжение следует! ;&#8211;)</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/01/14/the-symfony-2009-advent-calendar-day2-advanced-routing-p1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The symfony 2009 Advent Calendar: введение/Introduction</title>
		<link>http://hudson.su/2009/12/30/the-symfony-2009-advent-calendar-introduction/</link>
		<comments>http://hudson.su/2009/12/30/the-symfony-2009-advent-calendar-introduction/#comments</comments>
		<pubDate>Wed, 30 Dec 2009 00:37:14 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony advent calendar'09]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=705</guid>
		<description><![CDATA[Symfony 2009 advent calendar &#8211; это 24 урока продвинутого уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить. Перевод введения к новой книге (или циклу статей если вам угодно) от идеолога и родоначальника symfony, Fabien Potencier, The symfony 2009 Advent Calendar: Introduction). На [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p><strong>Symfony 2009 advent calendar</strong> &#8211; это 24 урока <strong>продвинутого</strong> уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить.</p></blockquote>
<p>Перевод введения к новой книге (или циклу статей если вам угодно) от идеолога и родоначальника symfony, <strong>Fabien Potencier</strong>, <a href="http://www.symfony-project.org/advent_calendar/1/en" target="_blank"><strong>The symfony 2009 Advent Calendar: Introduction</strong></a>).</p>
<p><span id="more-705"></span><br />
На момент написания этих слов, symfony отпраздновал знаменательную дату &#8211; свой <a href="http://trac.symfony-project.org/changeset/1" target="_blank">четвертый день рождения</a>. Всего за четыре года, symfony framework вырос и стал одним из наиболее популярных PHP-фреймворков в мире, питая своей энергией такие сайты как <a href="http://sf-to.org/delicious">Delicious</a>, <a href="http://sf-to.org/bookmarks">Yahoo Bookmarks</a> и <a href="http://sf-to.org/dailymotion">Daily Motion</a>. Но, с недавним релизом symfony 1.4 (ноябрь 2009) мы добрались до конца цикла (или если угодно, исчерпали возможности развития, подошли к качественному скачку). Эта книга &#8211; прекрасный способ окончить цикл разработки версии 1.xxx и, таким образом, вы собираетесь прочесть последнюю книгу о symfony 1го бранча, которая будет опубликована проектной группой symfony. Наша следующая книга будет скорее всего о symfony 2.0, который ожидается в конце 2010 года.</p>
<h3>Зачем нужна еще одна книга?</h3>
<p>Мы уже опубликовали две книги о symfony 1.3 и 1.4: &#8220;<a href="http://books.sensiolabs.com/book/9782918390169">Practical symfony</a>&#8221; и &#8220;<a href="http://books.sensiolabs.com/book/9782918390145">The symfony reference guide</a>&#8220;. Первая из них &#8211; отличный способ начать изучение symfony, так как вы изучаете основы его через разработку реального проекта шаг за шагом. Другая же книга &#8211; это справочник, который содержит бОльшую часть информации о конфигурировании symfony, которая вам может понадобиться в повседневной разработке.</p>
<p>&#8220;More with symfony&#8221; (она же &#8220;The symfony 2009 Advent Calendar&#8221;) &#8211; это книга, о более продвинутых аспектах symfony-разработки. <strong>Это не та книга</strong>, которую нужно читать в первую очередь при знакомстве с symfony, но она будет полезна тем, кто уже разработал несколько небольших проектов на symfony. Если вы хотите узнать <strong>как устроен symfony</strong>, как он работает, или же вы хотите <strong>расширить возможности фреймворка</strong> для выполнения различных специфических задач &#8211; <strong>эта книга для вас</strong>. Таким образом, &#8220;More with symfony&#8221; это то что вам нужно, чтобы поднять свои навыки по symfony на новый качественный уровень.</p>
<p>Так как книга построена как набор руководств по различным направлениям, вы вольны читать ее с любой главы и в любом порядке, в зависимости от ваших потребностей или интересов.</p>
<blockquote><p>Я, к примеру, начал читать ее с раздела, посвященного facebook-разработке ))) // hudson</p></blockquote>
<h3>Об этой книге</h3>
<p>Эта книга особенная, потому что она написана сообществом и для сообщества. Масса людей вложило свой труд в ее написание, начиная от авторов и заканчивая переводчиками, корректорами; множество их усилий слились в этой книге.</p>
<p>Эта книга будет опубликована как минимум на пяти языках (английском, французском, испанском, итальянском, японском). Мы бы не смогли этого сделать без самоотверженной работы нашей команды переводчиков.</p>
<p>Эта книга появилась на свет благодаря духу open source и она публикуется под лицензией open source. Уже один этот факт меняет все. Это означает что никто не оплачивает работу над книгой: все, кто работал над ее созданием, трудились в поте лиц потому что они этого хотели. Каждый, кто хотел поделиться своими знаниями, получить что-то от сообщества, помочь распространению информации о symfony и, наконец, получить удовольствие и стать знаменитыми )))</p>
<p>Эта книга была написана десятью авторами, которые либо ежедневно используют symfony изо дня в день в качестве разработчиков, либо менеджерами проектов. Они обладают глубокими познаниями о фреймворке и они постарались поделиться своими знаниями и опытом.</p>
<h3>Выражения благодарности</h3>
<p>Когда я начал подумывать о написании еще одной книги о symfony в августе 2009 года, у меня сразу возникла сумасшедшая идея: а что если написать книгу за два месяца и опубликовать ее сразу на пяти языках! Конечно, вовлечение сообщества в проект таких масштабов было просто необходимо. Я начал рассказывать об этой идее в ходе PHP-конференции в Японии и в считанные часы японская команда переводчиков была готова к работе. Это было просто невероятно! Реакция авторов и переводчиков в равной степени обнадеживала и спустя немного времени, проект &#8220;More with symfony&#8221; был запущен.</p>
<p>Я хочу поблагодарить всех, кто принял участие тем или иным образом во время создания этой книги: Ryan Weaver, Geoffrey Bachelet, Hugo Hamon, Jonathan Wage, Thomas Rabaix, Fabrice Bernhard, Kris Wallsmith, Stefan Koopmanschap, Laurent Bonnet, Julien Madelin, Franck Bodiot, Javier Eguiluz, Nicolas Ricci, Fabrizio Pucci, Francesco Fullone, Massimiliano Arione, Daniel Londero, Xavier Briand, Guillaume Bretou, Akky Akimoto, Hidenori Goto, Hideki Suzuki, Katsuhiro Ogawa, Kousuke Ebihara, Masaki Kagaya, Masao Maeda, Shin Ohno, Tomohiro Mitsumune, и Yoshihiro Takahara.</p>
<h3>Прежде чем начать</h3>
<p>Эта книга была написана для версий symfony 1.3 и 1.4. Написание книги для двух версий одного и того же программного обеспечения &#8211; явление несколько необычное, в этой секции будут даны разъяснения по поводу основных различий этих двух версий, чтобы вы могли выбрать одну из них для ваших проектов.</p>
<p>Обе версии symfony 1.3 и 1.4 вышли в релиз примерно в одно и то же время (в конце 2009 года). Собственно говоря, обе они имеют <strong>абсолютно одинаковый набор фич</strong>. Единственное отличие этих двух версий в том, как они поддерживают обратную совместимость с более ранними версиями symfony.</p>
<p>Symfony 1.3 это релиз который вам нужен, если нужно делать upgrade для существующего проекта, который работает со старой версией symfony (1.0, 1.1, 1.2). Он содержит слой для обратной совместимости и все фичи, которые устарели и не рекомендованы к применению во время отладки и стабилизации ветки 1.3. Это означает, что апгрейд будет простым и безопасным.</p>
<p>Если сегодня вы начинаете новый проект, вы должны использовать symfony 1.4. Эта версия имеет те же возможности что и 1.3, но все устаревшие фичи, включая слоя для обеспечения обратной совместимости были удалены. Эта версия более &#8220;чистая&#8221; с точки зрения кода и немного более быстрая по сравнению с 1.3. Другой плюс от использования symfony 1.4 заключается в более длительное поддержке этой версии &#8211; она будет поддерживаться три года (до ноября 2012 года).</p>
<p>Конечно же, вы можете мигрировать свои проекты на 1.3 и затем понемногу модифицировать ваш код, удаляя устаревшие фичи и в конечном счете перейти на 1.4 для того чтобы иметь более длительную поддержку. У вас достаточно времени для планирования, так как Symfony 1.3 будет поддерживаться в течение года (до ноября 2010 года).</p>
<p>Так как в этой книге не упоминаются устаревшие фичи, все пример работают идентично в обеих версиях.</p>
<blockquote><p>От переводчика: вообще говоря, введение несет не очень много полезной информации, но мне понравилось как Фабиен описывает книгу и как она появилось, поэтому решил перевести и его. // hudson</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2009/12/30/the-symfony-2009-advent-calendar-introduction/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Symfony 2009 advent calendar: день 19 – Разработка для Facebook (часть 2)</title>
		<link>http://hudson.su/2009/12/29/symfony-2009-advent-calendar-day19-facebook-p2/</link>
		<comments>http://hudson.su/2009/12/29/symfony-2009-advent-calendar-day19-facebook-p2/#comments</comments>
		<pubDate>Tue, 29 Dec 2009 12:24:54 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[facebook]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony advent calendar'09]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=685</guid>
		<description><![CDATA[Symfony 2009 advent calendar &#8211; это 24 урока продвинутого уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить. Перевод статьи Symfony 2009 Advent Calendar: Developing for Facebook (part 2). Facebook Connect О том как Facebook Connect работает и о различных стратегиях интеграции Facebook [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p><strong>Symfony 2009 advent calendar</strong> &#8211; это 24 урока <strong>продвинутого</strong> уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить.</p></blockquote>
<p>Перевод статьи <a href="http://www.symfony-project.org/advent_calendar/19/en" target="_blank"><strong>Symfony 2009 Advent Calendar: Developing for Facebook (part 2)</strong></a>.</p>
<p><span id="more-685"></span></p>
<h3>Facebook Connect</h3>
<h4>О том как Facebook Connect работает и о различных стратегиях интеграции</h4>
<p>Facebook Connect, прежде всего, предоставляет свою сессию для совместного доступа вашему вебсайту. Работа этой функции становится возможной благодаря копированию аутентификационных кукисов с Facebook через iframe на сайте, который указывает на страницу Facebook, которая в свою очередь открывает iframe на сайт. Для этого, Facebook Connect должен иметь доступ к вебсайту, что делает невозможным использовать или тестировать Facebook Connect на локальном сервере или в интранет-сети. Входной точкой является xd_receiver.htm, который поставляется с <strong>sfFacebookConnectPlugin</strong>&#8216;ом. Не забудьте выполнить таск <strong>plugin:publish-assets</strong> для того чтобы этот файл стал доступен.</p>
<p>Когда подготовительные процедуры выполнены, официальная библиотека Facebook получает возможность использовать сессию Facebook. В дополнение к этому, sfFacebookConnectPlugin создает пользователя sfGuard, связанного с сессией Facebook и который прочно связан с существующим сайтом на symfony. Вот почему плагин по умолчанию редиректит пользователя на экшен <strong>sfFacebookConnectAuth/signIn</strong> после того как тот нажмет кнопку Facebook Connect и сессия пройдет валидацию. Первым делом плагин ищет существующего пользователя с тем же UID&#8217;ом или с тем же хэшем Email (см. также раздел &#8220;<a href="#connect_existing_users">Сопоставляем существующих пользователей с их аккаунтами на Facebook</a>&#8221; в конце этой статьи). Если пользователь не найден, создается новый пользователь.</p>
<p>Другая стратегия регистрации заключается в том, чтобы не создавать пользователей напрямую, а сначала перенаправлять их на особую форму регистрации. На этой форме, с использованием сессии Facebook, например, можно сразу заполнить базовую информацию. Для того чтобы это сделать, можно добавить следующий код в форму регистрации:</p>
<pre>public function setDefaultsFromFacebookSession()
{
  if ($fb_uid = sfFacebook::getAnyFacebookUid())
  {
    $ret = sfFacebook::getFacebookApi()-&gt;users_getInfo(
      array(
        $fb_uid
      ),
      array(
        'first_name',
        'last_name',
      )
    );

    if ($ret &amp;&amp; count($ret)&gt;0)
    {
      if (array_key_exists('first_name', $ret[0]))
      {
        $this-&gt;setDefault('first_name',$ret[0]['first_name']);
      }
      if (array_key_exists('last_name', $ret[0]))
      {
        $this-&gt;setDefault('last_name',$ret[0]['last_name']);
      }
    }
  }
</pre>
<p>Для использования второй стратегии, просто разрешите в app.yml редирект после Facebook Connect и маршрут, который необходимо будет использовать.</p>
<pre># default values
all:
  facebook:
    redirect_after_connect: true
    redirect_after_connect_url: '@register_with_facebook'
</pre>
<h4>Фильтр Facebook Connect</h4>
<p>Другая важная особенность Facebook Connect заключается в том, что пользователи Facebook, при просмотре интернет-страниц, зачастую уже залогинены в Facebook. Вот тут во всю раскрывается сила <strong>sfFacebookConnectRememberMeFilter</strong>. Если пользователь, зашедший на ваш вебсайт, уже залогинен в Facebook, sfFacebookConnectRememberMeFilter автоматически залогинит его на вашем сайте, примерно так, как это делает фильтр &#8220;Remember me&#8221;.</p>
<pre>$sfGuardUser = sfFacebook::getSfGuardUserByFacebookSession();
if ($sfGuardUser)
{
  $this-&gt;getContext()-&gt;getUser()-&gt;signIn($sfGuardUser, true);
}
</pre>
<p>Тем не менее, эта плюшка имеет один серьёзный недостаток: пользователи не могут разлогиниться, пока они залогинены на Facebook (т.к. они каждый раз будут логиниться автоматически). Используйте эту фичу осторожно (ну или на свой страх и риск).</p>
<h4>Как избежать фатального JavaScript бага в IE</h4>
<p>Один из самых ужасных багов, который вы можете получить на вебсайте, является <strong>ошибка &#8220;Operation aborted&#8221; в IE</strong>, которая запросто рушит рендеринг вебсайта на стороне клиента. Это следствие кривого движка рендеринга в IE6/7, который может обрушиться, если вы добавляете DOM-элементы к элементу BODY из скрипта, который не является прямым потомком элемента BODY. К несчастью, это как правило случается, если вы загружаете JavaScript для Facebook Connect и не озаботились загрузить его напрямую из body в конце документа. Тем не менее, этого легко избежать при помощи slot&#8217;ов. Используйте slot для того чтобы включить скрипт Facebook Connect&#8217;а когда это необходимо, в ваш шаблон лэйаута в конце документа (перед тагом &lt;/body&gt;):</p>
<pre>// in a template that uses a XFBML tag or a Facebook Connect button
slot('fb_connect');
include_facebook_connect_script();
end_slot();

// just before &lt;/body&gt; in the layout to avoid problems in IE
if (has_slot('fb_connect'))
{
  include_slot('fb_connect');
}
</pre>
<h3>Лучшие практики для Facebook-приложений</h3>
<p>Благодаря sfFacebookConnectPlugin, интеграция с sfGuardPlugin выполняется гладко и выбор, будет ли приложение FBML, IFrame или Facebook Connect, можно отложить на последок. Для того чтобы мы могли двигаться дальше и создавать навороченные приложения для Facebook, ниже приведено несколько важных советов.</p>
<h4>Используйте environments в symfony для того чтобы поднять несколько тестовых серверов для Facebook Connect</h4>
<p>Очень важный аспект философии symfony заключается в быстрой отладке и качественном тестировании приложения. Интеграция с Facebook может серьезно осложнить эту задачу, так как почти все время будет требоваться соединение с Internet для коммуникаций с сервером Facebook и открытый 80й порт для обмена аутентификационными кукисами. Кроме того, есть еще ограничение: приложение, использующее Facebook Connect может быть подключено только к одному хосту. Это становится настоящей проблемой, когда приложение разрабатывается на одной машине, тестируется на другой, помещается в пре-продакшн на третий сервер и, наконец, используется в продуктовом окружении на четвертом.</p>
<p>В этой ситуации наиболее простым решением будет создать приложение для каждого сервера и создать в symfony environment для каждого из них. В симфони это делается легким движением руки ))) делаем копию frontend_dev.php (или его аналога), в frontend_preprod.php и редактируем строку во вновь созданном файле для изменения dev environment&#8217;а на новый, который мы назовем preprod:</p>
<pre>// заменяем энвайронмент на новый:
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'preprod', true);
</pre>
<p>Далее, вносим поправки в app.yml для того чтобы сконфигурировать новое Facebook-приложение для каждого нового окружения:</p>
<pre>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</pre>
</pre>
<p>Теперь приложение тестируемо на каждом отдельном сервере с использованием соответствующей среде frontend_xxx.php точки входа.</p>
<h4>Используйте систему логгирования symfony для отладки FBML</h4>
<p>Решение с переключением лэйаутов позволяет разрабатывать и тестировать большинство FBML приложений вне сайта Facebook. Тем не менее, финальный тест внутри Facebook может выдавать какие-то невразумительные ошибки. Действительно, основная проблема рендеринга FBML напрямую в Facebook заключается в том, что ошибка 500 обрабатывается и заменяется на не очень информативную стандартную страницу с ошибкой. Кроме того, web debug toolbar, не отображается в Facebook-фрейме. К счастью, на помощь к нам приходит замечательная система логгирования symfony. sfFacebookConnectPlugin автоматически логгирует многие важные действия, вы также с легкостью можете добавить строки в файл лога в любой точке приложения:</p>
<pre>if (sfConfig::get('sf_logging_enabled'))
{
  sfContext::getInstance()-&gt;getLogger()-&gt;info($message);
}
</pre>
<h4>Используйте Proxy во избежание ошибочных редиректов Facebook</h4>
<p>Имеется странный баг Facebook, который заключается в том, что когда Facebook Connect сконфигурирован в приложении, Facebook Connect рассматривается как домашняя директория (home) приложения. Не смотря на то, что home может быть сконфигурирован, он должен быть расположен на том же домене, что и Facebook Connect хост. Таким образом, не существует другого решения кроме как сдаться и сконфигурировать home как простой экшен, осуществляющий перенаправление куда потребуется. Следующий код осуществляет перенаправление на Facebook-приложение:</p>
<pre>public function executeRedirect(sfWebRequest $request)
{
  return $this-&gt;redirect('http://apps.facebook.com'.sfConfig::get('app_facebook_app_url'));
}
</pre>
<h4>Используйте fb_url_for() хелпер</h4>
<p>Для создания универсального приложения, которое может быть использовано как FBML внути Facebook или XFBML в iframe в любую (даже последнюю минуту), необходимо уделить внимание проблеме маршрутизации:</p>
<ul>
<li>Для FBML-приложения, ссылки внутри приложения должны указывать на /app-name/symfony-route;</li>
<li>Для iframe приложения важно передавать информацию о сессии facebook при переходе между страницами.</li>
</ul>
<p>Плагин sfFacebookConnectPlugin предоставляет особый хелпер, который решает указанные проблемы, это <strong>fb_url_for()</strong> хелпер.</p>
<h4>Редиректы внутри FBML приложения</h4>
<p>Symfony разработчики быстро привыкают выполнять редирект после успешного POST, и это в общем-то хорошая практика для избежания дублирования данных в БД. Перенаправление в FBML апликации же не работает как ожидается. Вместо этого необходимо применять специализированный таг <code>&lt;fb:redirect&gt;</code> для того чтобы Facebook выполнил редирект. Для сохранения независимости от контекста (FBML таг или обычный редирект) существует специальный статический метод в классе sfFacebook, который может быть использован, например, в экшене сохранения формы:</p>
<pre>
if ($form-&gt;isValid())
{
  $form-&gt;save();
  return sfFacebook::redirect($url);
}
</pre>
<h4 id="connect_existing_users">Сопоставляем существующих пользователей с их аккаунтами на Facebook</h4>
<p>Одна из целей Facebook Connect - упростить регистрацию для новых пользователей. Кроме того, интерес также может представлять сопоставление существующих пользователей с их аккаунтами на Facebook как для получения более полной информации о них, так и для взаимодействия с их фидом. Эта вспомогательная цель может быть достигнута двумя способами:</p>
<ul>
<li>Заставить существующих пользователей кликать на кнопку Connect with Facebook. Экшен sfFacebookConnectAuth/signIn не будет создавать нового пользователя, если он уже залогинен, он лишь будет сохранять данные Faceook пользователя для текущего sfGuard-пользователя. Это просто.</li>
<li>Использовать Facebook-систему распознавания email'ов. Когда пользователь использует Facebook Connect на сайте, Facebook может предоставить специальный хэш его email-адреса, который можно сравнить с хэшами в существующей базе данных для того чтобы определить аккаунт, принадлежащий пользователю. Тем не менее, по соображениям безопасности, Facebook предоставляет указанные хэши только если пользователь был зарегистрирован ранее при помощи его API! Таким образом, необходимо регистрировать email'ы всех новых пользователей, чтобы можно было опознать их впоследствии. Этим и занимается таск <strong>registerUsers</strong>. Этот таск должен запускаться как минимум каждую ночь для того чтобы регистрировать вновь созданных пользователей. В принципе, вы также можете использовать метод registerUsers из класса sfFacebookConnect: <code>sfFacebookConnect::registerUsers(array($sfGuardUser));</code></li>
</ul>
<h3>Что дальше?</h3>
<p>Надеюсь эта статья выполнит свое назначение: поможет вам начать разработку приложения для Facebook c использованием symfony и пояснит основные возможности разработки для Facebook. Тем не менее sfFacebookConnectPlugin не заменяет собой Facebook API, поэтому чтение оригинальной <a href="http://developers.facebook.com/" target="_blank">документации</a> никто не отменял, тем более что там вы можете получить самую полную информацию о разработке для facebook.</p>
<p>В заключение, хочу выразить благодарность сообществу symfony за качество и отзывчивость, в особенности тем, кто уже внес вклад в развитие sfFacebookConnectPlugin своими комментариями и патчами: Damien Alexandre, Thomas Parisot, Maxime Picaud, Alban Creton (и да простят меня все кого я тут не упомянул ))) И, конечно же, если вам есть что сказать или дополнить, не колеблясь вносите свой вклад!</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2009/12/29/symfony-2009-advent-calendar-day19-facebook-p2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Symfony 2009 advent calendar: день 18 &#8211; Разработка для Facebook (часть 1)</title>
		<link>http://hudson.su/2009/12/22/symfony-2009-advent-calendar-day18-facebook-p1/</link>
		<comments>http://hudson.su/2009/12/22/symfony-2009-advent-calendar-day18-facebook-p1/#comments</comments>
		<pubDate>Tue, 22 Dec 2009 01:14:14 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[facebook]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony advent calendar'09]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=636</guid>
		<description><![CDATA[Symfony 2009 advent calendar &#8211; это 24 урока продвинутого уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить. Перевод статьи Symfony 2009 advent calendar: day 18 &#8211; Developing for Facebook (part 1). Предисловие: тема разработки для facebook меня достаточно серьезно заинтересовала с полгода [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p><strong>Symfony 2009 advent calendar</strong> &#8211; это 24 урока <strong>продвинутого</strong> уровня о symfony. Все уроки представлены на 5 языках: английском, французском, испанском, итальянском, японском. Русского нет, это досадное упущение постараюсь исправить.</p></blockquote>
<p>Перевод статьи <a href="http://www.symfony-project.org/advent_calendar/18/en" target="_blank"><strong>Symfony 2009 advent calendar: day 18 &#8211; Developing for Facebook (part 1)</strong></a>.</p>
<p>Предисловие: тема <strong>разработки для facebook</strong> меня достаточно серьезно заинтересовала с полгода назад. После неудачной попытки доложиться по этому вопросу на phpconf&#8217;09 (тянул до последнего и не попал в программу))) я хотел систематизировать свои наблюдения и наработки в виде серии статей, однако все было недосуг. И вот, вижу данный урок на symfony, основном моем инструменте последних лет. Поэтому, немного сломав порядок перевода статей advent calendar, задуманный мной, начну с освещения этого интересного материала.</p>
<p><span id="more-636"></span></p>
<p>Facebook, со своими 350 миллионами пользователей, де-факто стал стандартом для социальных сетей. Одна из его самых интересных особенностей &#8211; это <strong>Facebook Platform</strong>, API, который позволяет сторонним разработчикам разрабатывать приложения как &#8220;внутри&#8221; facebook, так и подключать другие сайты и с системой аутентификации facebook (т.н. facebook connect).</p>
<p>Так как фронтэнд facebook&#8217;а написан на PHP, неудивительно, что официальная клиентская библиотека к этому API также написана на PHP. Этот факт делает symfony логичным выбором для быстрой разработки качественных приложенией для facebook, а также facebook connect&#8217;ед сайтов. Более того, разработка для facebook реально показывает, как вы можете пользоваться всей мощью функциональности symfony, экономя драгоценное время при сохранении высокого качества.</p>
<p>Далее мы рассмотрим следующие вопросы: после краткого описания что из себя представляет facebook api и с чем его едят. Мы расскажем как лучше всего использовать symfony при разработке facebook приложений, как получить отдачу от коммьюнити с помощью sfFacebookPlugin, продемонстрируем все на простом приложении &#8220;Hello you!&#8221; и, наконец, дадим несколько советов, как решить наиболее типичные проблемы.</p>
<h3>Разработка для facebook</h3>
<p>Не смотря на то, что API в целом одинаков в обоих случаях, есть два различных юзкейса: мы можем создать приложение &#8220;внутри&#8221; facebook или же реализовать facebook connect на внешнем сайте.</p>
<h4>Facebook приложения</h4>
<p>Facebook приложения &#8211; это веб приложения внутри facebook. Их основной плюс в том, что они напрямую встраиваются в социальную сеть с более чем 300 миллионами пользователей, что позволяет приложению наращивать посещаемость потрясающими темпами с использованием виральных (вирусных) технологий. Последний и наиболее успешный пример &#8211; Farmville, с 60 миллионами активных пользователей ежемесячно и с 2 миллионами поклонников, и эти показатели достигнуты всего за несколько месяцев! Эти цифры эквиваленты населению Франции, которое ежемесячно возвращается для того чтобы работать на своей виртуальной ферме. Facebook приложения взаимодействуют с сайтом и социальной сетью различными способами. Ниже приведены различные варианты того, где приложение может появиться:</p>
<h4>Канвас (the Canvas)</h4>
<blockquote><p>Канвас переводится с английского как холст, но в среде русскоговорящих разработчиков более принято прямое произношение. Я буду далее использовать именно его. // hudson</p></blockquote>
<p>Итак, канва, это, как правило, основная часть приложения. Это небольшой вебсайт, встраиваемый внутрь фрейма facebook.</p>
<h4>Вкладка профиля (the Profile Tab)</h4>
<p>Приложение также может располагаться на вкладке пользовательского профайла или фанатской страницы. Имеются следующие ограничения:</p>
<ul>
<li>Только одна страница. Нет возможности определять ссылки на внутренние страницы приложения на вкладке.</li>
<li>Никой динамики в виде flash или JavaScript во время загрузки. Для динамической функциональности приложение должно ожидать пользовательского действия на странице (клика на ссылку или кнопку)</li>
</ul>
<h4>Ящик профиля (the Profile Box)</h4>
<p>Эта функциональность осталась от старых версий facebook и фактически мало кем используется. Этот атавизм можно найти на вкладке &#8220;Boxes&#8221; пользовательского профайла.</p>
<h4>Дополнительная информационная вкладка (the Information Tab&#8217;s Addendum)</h4>
<p>Некоторая статическая информация, привязанная к тому или иному пользователю и приложение могут отображаться на информационной вкладке пользовательского профайла.</p>
<h4>Публикация заметок и новостной поток (Publishing Notices and in the News Stream)</h4>
<p>Приложение может публиковать новости, ссылки, картинки, видеоролики в потоке новостей (news stream) на стенах друзей или напрямую модифицировать статус пользователя.</p>
<blockquote><p>Не смотря на то, что нотификациям и стриму (и прочим виральным техникам) уделено несколько слов сейчас и еще несколько слов будет ниже, на самом деле они заслуживают как минимум отдельной статьи. Если вам будет интересно &#8211; пишите мне на email или оставьте комментарий. //hudson</p></blockquote>
<h4>Информационная страница (the Information page)</h4>
<p>Это аналог профайла, но для приложения. Эта страница создается автоматически. Это место, где создатель приложения может контактировать с пользователями обычным на facebook образом. Данная страница должна больше заботить маркетологов, нежели девелоперов )))</p>
<h4>Facebook Connect</h4>
<p>Непереводимая игра слов (прим.пер.) ))) Коннект позволяет подключить к любому вебсайту кое-что из немалой функциональности facebook и предоставить ее пользователям сайта. Уже подключенные сайты легко распознать по большой синей кнопке с текстом &#8220;connect with facebook&#8221;. Наиболее известные из них &#8211; digg.com, cnet.com, netvibes.com, yelp.com и т.д. Ниже перечислены четыре основные причины для того чтобы подключить ваш сайт к facebook:</p>
<ul>
<li><strong>Аутентификация одним кликом</strong>. Как и OpenID, Facebook Connect предоставляет вашему сайту автоматический логин с использованием facebook сессии. Когда соединение между вебсайтом и facebook установлено пользователем, автоматически создается facebook сессия, тем самым сохраняется время разработчика, которое он потратил бы на свою собственную систему аутентификации.</li>
<li><strong>Получение более детальной информации о пользователе</strong>.Другой полезной особенностью facebook connect является количество данных о пользователе, которые можно получить. Обычно пользователи оставляют на сайтах немного информации о себе, connect позволяет быстро получить дополнительную информацию о пользователе, например пол, местоположение, возраст, аватарку и т.д. Важно помнить, что <strong>facebook запрещает хранить персональную информацию пользователей</strong>, если пользователь не дал на то явного согласия. С другой стороны можно пользоваться этими данными, не храня их у себя (просто запрашивать у facebook, когда необходимо).</li>
<li><strong>Виральные коммуникации с использованием новостной ленты</strong>. Возможность взаимодействия с новостной лентой пользователя, приглашать друзей или публиковать что-либо на стенах друзей позволяет вебсайту использовать весь виральный потенциал facebook. Любой сайт с социальным уклоном может получить реальную выгоду от этих возможностей, до тех пор пока публикуемая информация имеет социальное значение и может заинтересовать друзей и друзей друзей.</li>
<li><strong>Преимущества уже существующей социальной сети</strong>. Для вебсайтов, которые зависят от социальной сети (типа сети друзей или знакомых), цена построения первого коммьюнити с достаточным числом связей между пользователями, как правило, очень высока. Предоставляя простой доступ к списку друзей пользователя, facebook connect значительно снижает эту цену, устраняя необходимость искать друзей пользователей.</li>
</ul>
<h3>Настройка первого проекта, с использованием sfFacebookConnectPlugin</h3>
<h4>Создание приложения</h4>
<p>Для начала нужен аккаунт на facebook, с установленным приложением <a href="http://www.facebook.com/developers" target="_blank">Developer</a>. Для того чтобы создать приложение, достаточно указать его имя.</p>
<h4>Установка и настройка sfFacebookConnectPlugin</h4>
<p>Следующим шагом будет связка пользователей facebook с пользователями <strong>sfGuard</strong>. Это основное свойство <strong>sfFacebookConnectPlugin</strong>. После установки плагина его обязательно нужно сконфигурировать, но это не сложно. <strong>API key</strong>, <strong>application secret</strong> и <strong>application ID</strong> должны быть определены в файле app.yml:</p>
<pre># default values
all:
  facebook:
    api_key: xxx
    api_secret: xxx
    api_id: xxx
    redirect_after_connect: false
    redirect_after_connect_url: ''
    connect_signin_url: 'sfFacebookConnectAuth/signin'
    app_url: '/my-app'
    guard_adapter: ~
    js_framework: none # none, jQuery or prototype.

  sf_guard_plugin:
    profile_class: sfGuardUserProfile
    profile_field_name: user_id
    profile_facebook_uid_name: facebook_uid # WARNING this column must be of type varchar! 100000398093902 is a valid uid for example!
    profile_email_name: email
    profile_email_hash_name: email_hash

  facebook_connect:
    load_routing:     true
    user_permissions: []
</pre>
<blockquote><p>Для более ранних версий symfony (&lt;1.3), необходимо установить опцию load_routing в false, так как она используется новой системой маршрутизации.</p></blockquote>
<h4>Настройка facebook приложения</h4>
<p>Если ваш проект это facebook приложение (а не connect&#8217;ed сайт), тогда есть только один необходимый параметр, который вам нужно указать &#8211; этот параметр <strong>app_url</strong>, который указывает относительный путь к приложению на facebook. Например, для приложения <em>http://apps.facebook.com/my-app</em>, значением app_url будет <em>/my-app</em></p>
<h4>Настройка facebook connect сайта</h4>
<p>Если ваш проект &#8211; это сайт с facebook connect, следующие конфигурационные параметры могут быть оставлены со значениями по-умолчанию в большинстве случаев:</p>
<ul>
<li><strong>redirect_after_connect</strong> &#8211; позволяет изменить поведение сайта после клика на кнопку &#8220;connect with facebook&#8221;. По умолчанию, плагин воспроизводит поведение sfGuardPlugin после регистрации.</li>
<li><strong>js_framework</strong> &#8211; может быть использован для указания js фреймворка, который вы будете использовать. Очень рекомендуем воспользоваться этой возможностью и остановить выбор каком либо из фреймворков типа jQuery, так как JavaScript в Facebook Connect очень тяжел и может вызывать фатальные сбои (!) в IE6 будучи не загруженным в нужный момент.</li>
<li><strong>user_permissions</strong> &#8211; массив прав доступа, которые будут присвоены новому facebook пользователю.</li>
</ul>
<h4>Коннектим sfGuard с Facebook</h4>
<p>Связь между пользователем facebook осуществляется благодаря использованию колонки <strong>facebook_uid</strong> в таблице <strong>Profile</strong>. Плагин полагает что связь между sfGuardUser и его профайлом с использованием метода getProfile(). Это поведение по-умолчанию для sfPropelGuardPlugin, но для sfDoctrineGuardPlugin потребуется немного <del>танцев с бубном</del> донастроить. Вот возможная схема для <strong>Propel</strong>:</p>
<pre># schema.yml
sf_guard_user_profile:
  _attributes: { phpName: UserProfile }
  id:
  user_id:            { type: integer, foreignTable: sf_guard_user, foreignReference: id, onDelete: cascade }
  first_name:         { type: varchar, size: 30 }
  last_name:          { type: varchar, size: 30 }
  facebook_uid:       { type: varchar, size: 20 }
  email:              { type: varchar, size: 255 }
  email_hash:         { type: varchar, size: 255 }
  _uniques:
    facebook_uid_index: [facebook_uid]
    email_index:        [email]
    email_hash_index:   [email_hash]
</pre>
<p>А вот для <strong>Doctrine</strong>:</p>
<pre>sfGuardUserProfile:
  tableName:     sf_guard_user_profile
  columns:
    user_id:          { type: integer(4), notnull: true }
    first_name:       { type: string(30) }
    last_name:        { type: string(30) }
    facebook_uid:     { type: string(20) }
    email:            { type: string(255) }
    email_hash:       { type: string(255) }
  indexes:
    facebook_uid_index:
      fields: [facebook_uid]
      unique: true
    email_index:
      fields: [email]
      unique: true
    email_hash_index:
      fields: [email_hash]
      unique: true
  relations:
    sfGuardUser:
      type: one
      foreignType: one
      class: sfGuardUser
      local: user_id
      foreign: id
      onDelete: cascade
      foreignAlias: Profile
</pre>
<blockquote><p>А что делать если проект использует Doctrine и foreignAlias не Profile? В этом случае плагин работать не будет. Но простой метод getProfile() в классе sfGuardUser.class.php, который ссылается на таблицу Profile &#8211; решит эту проблему!</p></blockquote>
<p>Имейте в виду, что колонка facebook_uid должна быть VARCHAR, так как новые профайлы facebook имеют uids свыше 10^15. Лучше жить спокойно с индексированным VARCHAR, чем пытаться заставить работать BIGINT с различными ORM.</p>
<p>Другие две колонки менее важны: email и email_hash нужны только для случая facebook connect сайта с уже существующими пользователями. В этом случае facebook предоставляет сложный процесс сопоставления существующих аккаунтов с новыми аккаунтами facebook connect с использованием хэша email. Мы постарались упростить этот процесс при помощи CLI таска, который поставляется вместе с sfFacebookConnectPlugin. Этот таск будет описан в конце статьи.</p>
<h4>Выбор между FBML и XFBML разрешен в пользу symfony ))</h4>
<p>Теперь, когда все настроено, мы можем начать кодировать собственно приложение. Facebook предоставляет много специальных тагов, которые отвечают за рендеринг всего функционала, например форму &#8220;invite friends&#8221;, или полнофункциональную систему комментирования. Эти таги называются <strong>FBML</strong> или <strong>XFBML</strong> тагами. FBML и XFBML таги в целом похожи, но от выбора между ними зависит, будет ли приложение рендериться внутри facebook или нет. Если проект &#8211; это сайт с Facebook connect, ваш выбор только XFBML. Если же это Facebook приложение, есть две возможности:</p>
<ul>
<li>Встроить приложение в IFrame внутри страницы facebook-приложения и использовать XFBML внутри IFrame/</li>
<li>Встроить приложение &#8220;прозрачно&#8221; и использовать FBML.</li>
</ul>
<p>Facebook поощряет разработчиков в использовании их &#8220;прозрачного встраивания&#8221; или, как его чаще называют, &#8220;FBML applicaton&#8221;. В самом деле, такие приложения предоставляют ряд интересных особенностей:</p>
<ul>
<li>Никаких IFrame&#8217;ов, которые всегда усложняют управление</li>
<li>FBML таги автоматически интерпретируются на стороне facebook и позволяют отображать данные пользователя без необходимости заранее получать их от facebook</li>
<li>Нет необходимости явно передавать facebook сессию со страницы на страницу</li>
</ul>
<p>Тем не менее, FBML имеет и недостатки:</p>
<ul>
<li>Любой JavaScript должен быть определен непосредственно внутри страницы, что делает невозможным использование сторонних библиотек типа googlemaps, jquery или системы сбора статистики, отличной от Google Analytics, которая официально поддерживается facebook (к слову сказать, в версии FBJS2 обещают добавить поддержку сторонних JS библиотек в FBML приложения // прим. пер.)</li>
<li>Считается, что FBML таги позволяют ускорить работу приложения, по сравнению с тем, если бы вы пользовались вызовами API. Тем не менее, если приложение не сложное, хостинг на своем сервере будет быстрее.</li>
<li>FBML приложения сложнее отлаживать, в том числе 500я ошибка перехватывается faceook&#8217;ом и заменяется на стандартную.</li>
</ul>
<blockquote><p>Согласен с тем что отладка FBML приложения более сложная. Тем не менее, недавно я переводил 30 лучших практик для symfony, там в частности рекомендовалось использовать логгеры вместо привычных многим PHP-разработчикам echo &amp; var_dump. Тут я могу порекомендовать то же самое. Это работает, не зависимо от того, пишите вы на symfony или же на каком-то самописном решении. А вот с JavaScript-отладкой готовьте матюгальник. Без шуток. Даже firebug почти не помогает. Все embedded скрипты парсятся и модифицируются на стороне facebook перед показом. В общем-то вы очень ограничены в выборе клиентских средств (см. FBJS). // hudson</p></blockquote>
<p>Итак, что же выбрать? Хорошая новость! Symfony и sfFacebookConnectPlugin не требуют от вас подобного выбора. Вы можете писать приложение независимо от его типа и переключаться с IFrame на FBML или на сайт с Facebook Connect имея в основе тот же код. Это возможно, так как технически основное различие между этими типами приложений в их шаблоне (layout), которые очень просто переключать в symfony. Вот вам примеры двух различных шаблонов:</p>
<p>Шаблон для FBML приложения:</p>
<pre>&lt;?php sfConfig::set('sf_web_debug', false); ?&gt;
&lt;fb:title&gt;&lt;?php echo sfContext::getInstance()-&gt;getResponse()-&gt;getTitle() ?&gt;&lt;/fb:title&gt;
&lt;?php echo $sf_content ?&gt;
</pre>
<p>Шаблон для IFrame/Facebook Connect сайта</p>
<pre>&lt;?php use_helper('sfFacebookConnect')?&gt;
&lt;!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"&gt;
&lt;html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml"&gt;
  &lt;head&gt;
    &lt;?php include_http_metas() ?&gt;
    &lt;?php include_metas() ?&gt;
    &lt;?php include_title() ?&gt;
    &lt;script type="text/javascript" src="/sfFacebookConnectPlugin/js/animation/animation.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;?php echo $sf_content ?&gt;
    &lt;?php echo include_facebook_connect_script() ?&gt;
  &lt;/body&gt;
&lt;/html&gt;
</pre>
<p>Для автоматического переключения между ними, просто добавьте следующий код в actions.class.php:</p>
<pre>public function preExecute()
{
  if (sfFacebook::isInsideFacebook())
  {
    $this-&gt;setLayout('layout_fbml');
  }
  else
  {
    $this-&gt;setLayout('layout_connect');
  }
}
</pre>
<p>Есть правда небольшое отличие между FBML и XFBML, которое не решается сменой лэйаута: FBML таги могут быть закрытыми, а XFBML нет. Т.о. всего лишь изменяем</p>
<pre>[html]
 &lt;fb:profile-pic uid="12345" size="normal" width="400" /&gt;
</pre>
<p>на</p>
<pre>[html]
 &lt;fb:profile-pic uid="12345" size="normal" width="400"&gt;&lt;/fb:profile-pic&gt;
</pre>
<p>Конечно, для того чтобы проделывать эти манипуляции, приложение должно быть настроено также и для Facebook Connect в настройках, даже если оно задумывалось лишь как FBML. Но огромная польза от этого заключается в возможности тестировать приложение локально!</p>
<p>Если вы создаете приложение для facebook и планируете использовать FBML таги, которых по большому счету не миновать по тем или иным причинам, елинственный способ протестировать приложение заключается в размещении кода онлайн и тестировать непосредственно на facebook. К счастью, благодаря Facebook Connect, XFBML таги могут рендериться вне facebook.com. И, как уже было сказано, единственное их отличие в layout&#8217;ах. Таким образом, это решение позволяет локально рендерить FBML таги, коль сокоро на локальной машине есть выход в интернет (с facebook так или иначе коннектиться придется).</p>
<p>К тому же, если ваша девелоперская среда доступна через интернет, сервер ли это или у вас домашний компьютер с белым IP и вебсервером на 80м порту, система аутентификации будет работать на facebook.com, благодаря Facebook Connect.</p>
<h4>Простейшее приложение</h4>
<p>При помощи нижеследующего кода в шаблоне мы закончим разработку нашего приложения &#8220;Hello you!&#8221;:</p>
<pre>
&lt;?php $sfGuardUser = sfFacebook::getSfGuardUserByFacebookSession(); ?&gt;
Hello &lt;fb:name uid="&lt;?php echo $sfGuardUser?$sfGuardUser-&gt;getProfile()-&gt;getFacebookUid():'' ?&gt;"&gt;&lt;/fb:name&gt;
</pre>
<p>sfFacebookConnectPlugin автоматически конвертирует пользователя facebook в пользователя sfGuard. Это позволяет быструю интеграцию существующего кода, основанного на sfGuardPlugin.</p>
<blockquote><p>Продолжение следует! ))<br />
P.S. Если вы прочитали статью до этого места &#8211; поставьте пожалуйста оценку. Или даже обоснуйте ее в комментарии. Вам не сложно, а мне приятно и наука на будущее ))</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2009/12/22/symfony-2009-advent-calendar-day18-facebook-p1/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
	</channel>
</rss>

