Using Zend GData in a Symfony2 Project
The leading developer of the Symfony framework, Fabien Potencier, spoke at the Zend / PHP Conference in 2009 with a report on the benefits of sharing Symfony 1.3 / 1.4 and the Zend Framework. The main points of his speech are available in a presentation published on his personal website [1].
As you know, Symfony2 is an almost new framework created using the latest features of the PHP language. At the moment, development has moved to the RC cycle, and more and more developers with experience on the first Symfony branch (and not only them) are looking towards the new flagship. But, despite the significant number of components included in the standard edition, Symfony2 does not cover all the needs of a web developer, so sooner or later, the question arises of connecting external libraries.
Obviously, in this light, the voluminous set of Zend libraries (Gdata, Search_Lucene, Pdf, etc.) cannot be ignored. In this post, I will discuss the process of integrating Symfony2 and Zend using the example of Zend Gdata - a library for interacting with the Google Data API [2].
Briefly about installing Symfony2
As stated in the README standard symfony2 distribution, developers no longer recommend using git, suggesting instead to download the archive from the official site. At the time of writing, the latest version is RC4. All of the following will work in earlier versions with some caveats, since significant refactoring was performed in Command, which affected class names, and code generators appeared. I prefer to download the archive without vendors, because when using GIT the vendors directory will be added to .gitignore anyway [3].
For development, the Apache web server and PHP 5.3.2 (and higher) are enough. Detailed requirements are detailed in the documentation [4].
Integration of Zend Gdata into the project
The decision presented in this article was prompted by the presentation of Fabien, which was already mentioned above. On the Internet you can find quite crazy solutions, like processing the Zend distribution with regular expressions. I will be glad to hear other suggestions in the comments. There is also a discussion on stackoverflow [5].
Since making any changes to the distribution library of the plug-in library can lead to difficulties during the upgrade (and is generally considered bad style), and binding to server environment variables limits the developer, the proposed solution uses only standard Symfony2 and php tools.
First, follow the Zend Framework website and download the Gdata distribution [6].
We create the following directory structure in the project: Copy the README and LICENSE files from the Zend GData distribution. Register the prefix 'Zend_' in app / autoload.php:
vendor/
-> zend/
--> lib/
---> Zend/
----> [Zend directory from Zend GData package]
--> README
--> LICENSE
$loader->registerPrefixes(array(
// ... Предыдущие префиксы, такие как Twig и Twig_Extensions
'Zend_' => __DIR__.'/../vendor/zend/lib',
));
Next, add the code from the Fabienne presentation to the end of the file (similar to Swift Mailer):
// Zend Framework GData needs a special autoload fix too
set_include_path(__DIR__.'/../vendor/zend/lib'.PATH_SEPARATOR.get_include_path());
Now the classes included in Zend GData will be defined by the Symfony2 loader by the prefix 'Zend_' and all the numerous untethered require in the Gdata distribution will work correctly, thanks to the changed include_path. It is possible that on loaded servers it would be wise to specify the include_path value in php.ini.
Services
Quoting the glossary of Symfony2 [7], a service is a general term for any PHP objects that perform a task that is used globally. Examples include a database connection or an object sending emails.
Based on this definition, it is easy enough to understand that, in our case, Zend_Gdata is a service.
Next, we will develop a console team that, using the Google GData API service, will upload blog entries to blogspot.com to the database.
First, create the GdataTestBundle bundle. Now that the code generator has appeared, this is done extremely simply:
$ php app/console generate:bundle --namespace=Habr/GDataBundle --format=yml
Now, in the src / Habr / GDataBundle / Resources / config / services.yml configuration file, add the following lines (you need to replace ~ in the lines marked with comments with real data to access the blog on blogger):
parameters:
gdata.class: Zend_Gdata
gdata.http_client.class: Zend_Gdata_HttpClient
gdata.http_client_factory.class: Zend_Gdata_ClientLogin
gdata.username: ~ #
gdata.password: ~ #
gdata.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]
Let us dwell on the contents of this configuration file. Symfony2 provides many ways to initialize services and resolve dependencies. In the simplest case, based on the class name specified in the configuration, an object is created that is returned to the client code using the get () method from the ContainerInterface interface. In reality, objects have complex relationships with each other and, often, the initialization of one service requires an instance of another, passed as an argument to the constructor. This case can be observed above for the gdata service.
Initialization of Zend_Gdata_HttpClient is much more interesting - an instance of an object of this class is created by calling the static method of the factory class (Zend_Gdata_ClientLogin), which is also passed arguments. For more information on using factory classes to initialize services, see the special chapter on the Symfony Cookbook [8].
We can test the resulting configuration when we create a console command that uses the service we defined.
Model
Let's create a simple model for storing posts uploaded from Blogger.com, which will also be helped by the built-in generator (if you do not want to make your own changes to the model’s configuration, you can press enter for all subsequent questions interactively):
$ php app/console doctrine:generate:entity --entity="HabrGDataBundle:Post" --fields="title:string(255) content:text remote_id:string(255) created_at:datetime"
Now in the src / Habr / GDataBundle / Entity directory is the generated Blog.php file, which contains all the necessary getters and setters. Further, to reflect the changes made at the DBMS level, we first need to configure the connection to the database.
To use MySQL, you need to change the corresponding part of app / config / parameters.ini as follows, using your connection settings:
[parameters]
; ...
database_driver = pdo_mysql
database_host = localhost
database_name = habr
database_user = root
database_password =
; …
The next step is to create a database:
$ php app/console doctrine:database:create
After we create the tables based on the models:
$ php app/console doctrine:schema:update --force
You can check the result of the command in phpmyadmin or any other client for the database used.
Command
The entity, which was called “task” in Symfony 1.4, became a team in Symfony2. The current release does not yet have a generator for commands, so manually create the Command subdirectory inside the bundle’s root directory, where we put the FetchFeedCommand.php file (Command suffix is required). The implementation of the commands [9] (as well as the majority of the Symfony2 components) is very simple, therefore I give only the source code with comments:
// FetchFeedCommand.php
* @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();
}
}
Now you can run the command by passing ./app/console its name, defined in the call to setName.
$ ./app/console gdata:blogger:fetch-feed
The result should be all blog entries in the database post table.
Conclusion
The above example is somewhat synthetic, since authentication is not required to receive an open blog feed from Blogger.com. Those who wish to exercise and gain additional experience with Symfony2 can implement the addition or removal of a new blog entry on their own. A link to the detailed documentation has already been given above.
In general, I want to add that now Symfony2 has already grown from the status of "impressive, but unstable thing." The framework is ready for serious projects.
PS I thank everyone who helped in the publication of this post.
References
- Symfony & Zend Framework Together - 2009
- GData API
- Ignoring the vendor directory
- Requirements for running Symfony2
- Question about using Zend_GData in Symfony2 on Stackoverflow
- Zend GData download page
- Symfony2 glossary
- How to Use a Factory to Create Services
- How to create Console / Command-Line Commands