Пользователь
0,0
рейтинг
16 июля 2011 в 16:21

Разработка → Использование Zend GData в Symfony2-проекте



Ведущий разработчик фреймворка Symfony — Фабьен Потенсьер (Fabien Potencier) в 2009-м году выступил на Zend/PHP Conference с докладом о извлечении выгоды в совместном использовании Symfony 1.3/1.4 и Zend Framework. Основные тезисы его речи доступны в презентации, опубликованной на его персональном сайте [1].

Как известно, Symfony2 — это практически новый фреймворк, созданный с использованием новейших возможностей языка PHP. На данный момент разработка перешла в RC-цикл, и всё больше разработчиков, имеющих опыт работы на первой ветке Symfony (да и не только они), смотрят в сторону нового флагмана. Но, даже несмотря на значительное количество включенных в стандартное издание компонентов, Symfony2 не покрывает все нужды веб-разработчика, поэтому, рано или поздно, встает вопрос о подключении внешних библиотек.

Очевидно, что в этом свете объемный комплект библиотек Zend (Gdata, Search_Lucene, Pdf и т. д.) нельзя обойти стороной. В данном посте мной будет рассмотрен процесс интеграции Symfony2 и Zend на примере Zend Gdata — библиотеки для взаимодействия с Google Data API [2].


Кратко о установке Symfony2



Как указано в README стандартного дистрибутива symfony2, разработчики больше не рекомендуют использовать git, предлагая вместо этого скачивать архив с официального сайта. На момент написания поста последняя версия — RC4. Все нижесказанное будет работать и в более ранних версиях с некоторыми оговорками, т. к. был произведен значительный рефакторинг в Command, затронувший имена классов, а также появились генераторы кода. Я предпочитаю скачивать архив без вендоров (without vendors), т. к. при использовании GIT директория vendors так или иначе будет добавлена в .gitignore [3].

Для разработки достаточно веб-сервера Apache и PHP 5.3.2 (и выше). Детальные требования подробно изложены в документации [4].

Интеграция Zend Gdata в проект



На представленное в данной статье решение меня натолкнула презентация Фабьена, о которой уже говорилось выше. На просторах интернета можно найти достаточно безумные решения, наподобие обработки дистрибутива Zend регулярными выражениями. Буду рад выслушать иные предложения в комментариях. Также существует обсуждение на stackoverflow [5].

Так как внесение каких-либо изменений в дистрибутив подключаемой библиотеки может привести к сложностям при обновлении (и в целом считается дурным тоном), а привязка к переменным окружения сервера ограничивает разработчика, то предложенное решение использует только стандартные средства Symfony2 и php.

Сначала, следуем на сайт Zend Framework и скачиваем дистрибутив Gdata [6].

Создаем в проекте следующую структуру директорий:

vendor/
-> zend/
--> lib/
---> Zend/
----> [Zend directory from Zend GData package]
--> README
--> LICENSE


Файлы README и LICENSE копируем из дистрибутива Zend GData. Регистрируем префикс 'Zend_' в app/autoload.php:

	$loader->registerPrefixes(array(
	  // ... Предыдущие префиксы, такие как Twig и Twig_Extensions
	  'Zend_' => __DIR__.'/../vendor/zend/lib',
	));
	


Далее добавляем в конец файла (по аналогии со Swift Mailer) код из презентации Фабьена:

	// Zend Framework GData needs a special autoload fix too 
	set_include_path(__DIR__.'/../vendor/zend/lib'.PATH_SEPARATOR.get_include_path());
	


Теперь классы, входящие в Zend GData будут определяться загрузчиком Symfony2 по префиксу 'Zend_' и все многочисленные непривязанные require в дистрибутиве Gdata будут корректно работать, благодаря измененному include_path. Возможно, что на нагруженных серверах будет разумно указать значение include_path в php.ini.

Сервисы



Цитируя глоссарий Symfony2 [7], сервис — это общий термин для обозначения любых PHP-объектов, которые выполняют какую-либо задачу, используемую глобально. Примерами могут служить подключение к базе данных или объект, отправляющий электронные письма.

Основываясь на данном определении достаточно легко понять, что, в нашем случае, Zend_Gdata является сервисом.

Далее мы разработаем консольную команду, которая, используя сервис Google GData API, будет загружать записи из блога на blogspot.com в базу данных.

Первым делом создадим бандл GdataTestBundle. Теперь, когда появился генератор кода, это делается предельно просто:

	$ php app/console generate:bundle --namespace=Habr/GDataBundle --format=yml
	


Теперь, в конфигурационный файл src/Habr/GDataBundle/Resources/config/services.yml добавляем следующие строки (необходимо заменить ~ в строках, помеченных комментариями на реальные данные для доступа к блогу на blogger):

	parameters:
	  gdata.class: Zend_Gdata
	  gdata.http_client.class: Zend_Gdata_HttpClient
	  gdata.http_client_factory.class: Zend_Gdata_ClientLogin
	  
	  gdata.username: ~ # <email_address@gmail.com>
	  gdata.password: ~ # <password>
	  gdata.blog_id: ~ # <blog ID>
	  gdata.service_name: blogger

	services:
	  gdata_http_client:
	    class: %gdata.http_client.class%
	    factory_class: %gdata.http_client_factory.class%
	    factory_method: getHttpClient
	    arguments:
	      - %gdata.username%
	      - %gdata.password%
	      - %gdata.service_name%
	  gdata:
	    class: %gdata.class%
	    arguments: [@gdata_http_client]
	


Остановимся подробнее на содержимом данного конфигурационного файла. Symfony2 предоставляет множество способов для инициализации сервисов и разрешения зависимостей. В простейшем случае, на основе указанного в конфигурации имени класса создается объект, который возвращается в клиентский код с помощью метода get() из интерфейса ContainerInterface. В реальности же объекты имеют сложные зависимости между собой и, зачастую, инициализация одного сервиса требует экземпляра другого, переданного в качестве аргумента конструктору. Данный случай можно наблюдать выше для сервиса gdata.

Гораздо больший интерес представляет инициализация Zend_Gdata_HttpClient — экземпляр объекта данного класса создается с помощью вызова статического метода класса-фабрики (Zend_Gdata_ClientLogin), которому также передаются аргументы. Подробнее об использовании классов-фабрик для инициализации сервисов рассказано в специальной главе Symfony Cookbook [8].
Протестировать полученную конфигурацию мы сможем, когда создадим консольную команду, использующую определенный нами сервис.

Модель



Создадим простую модель для хранения выгруженных с Blogger.com записей, в чем нам также поможет встроенный генератор (в случае, если вы не хотите вносить собственные изменения в конфигурацию модели, то на все последующие вопросы в интерактивном режиме можно нажимать enter):

	$ php app/console doctrine:generate:entity --entity="HabrGDataBundle:Post" --fields="title:string(255) content:text remote_id:string(255) created_at:datetime"
	


Теперь в директории src/Habr/GDataBundle/Entity находится сгенерированный файл Blog.php, в котором присутствуют все необходимые геттеры и сеттеры. Далее, для отражения внесенных изменений на уровне СУБД, нам необходимо сперва настроить подключение к базе данных.

Для использования MySQL необходимо изменить соответствующую часть app/config/parameters.ini следующим образом, используя специфичные для вас настройки подключения:

	[parameters]
	; ...
	    database_driver   = pdo_mysql
	    database_host     = localhost
	    database_name     = habr
	    database_user     = root
	    database_password =
	; …
	


Следующим шагом является создание базы данных:

	$ php app/console doctrine:database:create
	


После создаем таблицы на основе моделей:
	$ php app/console doctrine:schema:update --force
	


Можете проверить результат выполнения команды в phpmyadmin или любом другом клиенте для используемой базы данных.

Команда


Сущность, которая называлась в Symfony 1.4 «task» (задачей), в Symfony2 стала командой. В текущем релизе пока ещё нет генератора для команд, поэтому внутри корневой директории бандла вручную создаем поддиректорию Command, куда кладем файл FetchFeedCommand.php (суффикс Command обязателен). Реализация команд [9] (как и большинства компонентов Symfony2) очень проста, поэтому привожу только исходный код с комментариями:

	// FetchFeedCommand.php
	<?php
	namespace Habr\GDataBundle\Command;

	use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
	use Symfony\Component\Console\Input\InputDefinition;
	use Symfony\Component\Console\Input\InputOption;
	use Symfony\Component\Console\Input\InputArgument;
	use Symfony\Component\Console\Input\InputInterface;
	use Symfony\Component\Console\Output\OutputInterface;
	use Habr\GDataBundle\Entity\Post;

	/**
	 * FetchFeedCommand
	 * @author temochka <http://temochka.habrahabr.ru>
	 * @package HabrGDataBundle
	 * @subpackage command
	 * @version 0.1
	 */
	class FetchFeedCommand extends ContainerAwareCommand
	{
	    /**
	     * Конфигурация команды
	     */
		protected function configure()
		{
	        $this->setName('gdata:blogger:fetch-feed');
		}

	    /**
	     *
	     * @param InputInterface $input
	     * @param OutputInterface $output 
	     */
		protected function execute(InputInterface $input, OutputInterface $output)
		{
	        // Инициализируем экземпляр сервиса gdata (Объект Zend_Gdata)
	        $gdClient = $this->getContainer()->get('gdata');
	        
	        // Из настроек получаем
	        $blogID = $this->getContainer()->getParameter('gdata.blog_id');
	        
	        // Создаем запрос к API
	        $query = new \Zend_Gdata_Query('http://www.blogger.com/feeds/' . $blogID . '/posts/default');        
	        $feed = $gdClient->getFeed($query);        

	        // Получаем экземпляр EntityManager'а из сервиса doctrine
	        $em = $this->getContainer()->get('doctrine')->getEntityManager();
	        foreach($feed->entries as $entry)
	        {
	            // Создаем записи
	            $post = new Post;
	            $post->setTitle($entry->title->text);
	            $post->setRemoteId($entry->id);
	            $post->setContent($entry->content);
	            $post->setCreatedAt(new \DateTime($entry->published->text));
	            $em->persist($post);
	            
	            $output->writeln(sprintf("\tNew post %s has been added.\n", $entry->title->text));
	        }
	        // Сохраняем записи в БД
	        $em->flush();
		}
	}
	


Теперь запустить команду можно, передав ./app/console её имя, определенное в вызове setName.
	$ ./app/console gdata:blogger:fetch-feed
	


Результатом должны стать все записи блога в таблице post базы данных.

Заключение



Приведенный пример является несколько синтетическим, так как для получения открытой ленты записей с Blogger.com не требуется аутентификация. Желающие поупражняться и получить дополнительный опыт с Symfony2 могут реализовать добавление или удаление новой записи в блог самостоятельно. Ссылка на подробную документацию уже была приведена выше.

В целом хочется добавить, что сейчас Symfony2 уже вырос из статуса «впечатляющая, но нестабильная штука». Фреймворк готов к серьезным проектам.
P.S. Благодарю всех, кто помог в публикации данного поста.

Ссылки


  1. Symfony & Zend Framework Together — 2009
  2. GData API
  3. Ignoring the vendor directory
  4. Requirements for running Symfony2
  5. Вопрос про использование Zend_GData в Symfony2 на Stackoverflow
  6. Zend GData download page
  7. Symfony2 Glossary
  8. How to Use a Factory to Create Services
  9. How to create Console/Command-Line Commands

Артем Чистяков @temochka
карма
12,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (10)

  • +2
    В целом хочется добавить, что сейчас Symfony2 уже вырос из статуса «впечатляющая, но нестабильная штука». Фреймворк готов к серьезным проектам.

    Подтверждаю
  • +3
    А почему ZF — это как бы девочка, а SF — как бы мальчик?
    • +1
      Не ищи во всем символического значения, а то ведь найдешь.

      Виктор Пелевин, Поколение «П».
    • +1
      Хотелось, чтобы Symfony2 был слева. По сути же главное, чтобы сущности интегрировались друг с другом :)
      • 0
        Ну вы же интегрируете часть ZF с Symfony. Я думаю, подходящей метафорой был бы, например, пирог (шарлотка — поскольку тут речь идет о Гугле… да и в сторону Корпорации Бабла ненавязчиво плюнуть можем… а тот факт, что речь идет о каком-никаком, а о пироге подразумевает, что девочка корпела над ним), который девочка дарит парню… И вот на девушке написано ZF, на парне Symfony 2 (кстати, да — он же маленький пока еще совсем), а на пироге GData…

        Никому полит технолог не нужен?
  • –1
    Интересно, что будет с использованием ресурсов (в частности, памяти) у такого гибрида. Ведь обе библиотеки весят немало даже сами по себе.
    • 0
      Доверьтесь Service Container'у. Экземпляр Zend_Gdata будет создан только при запросе сервиса. Согласно документации, даже от объявления огромного числа сервисов оверхэд отсутствует или очень мал.
  • 0
    Вместо echo в командах лучше все таки использовать использовать объект $output
    • 0
      Спасибо, полностью согласен. Это что-то вроде рудимента от первого рабочего наброска. Исправлю, как буду за каналом пошире, чтобы не подавать дурной пример.
  • +2
    Love is… Простите, дальше не смог читать. :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.