You will probably need to reuse a piece of code that you developed for one of your symfony applications. If you can package this piece of code into a single class, no problem: Drop the class in one of the lib/ folders of another application and the autoloader will take care of the rest. But if the code is spread across more than one file, such as a complete new theme for the administration generator or a combination of JavaScript files and helpers to automate your favorite visual effect, just copying the files is not the best solution.
Plug-ins offer a way to package code disseminated in several files and to reuse this code across several projects. Into a plug-in, you can package classes, filters, mixins, helpers, configuration, tasks, modules, schemas and model extensions, fixtures, web assets, etc. Plug-ins are easy to install, upgrade, and uninstall. They can be distributed as a .tgz archive, a PEAR package, or a simple checkout of a code repository. The PEAR packaged plug-ins have the advantage of managing dependencies, being easier to upgrade and automatically discovered. The symfony loading mechanisms take plug-ins into account, and the features offered by a plug-in are available in the project as if the plug-in code was part of the framework.
So, basically, a plug-in is a packaged extension for a symfony project. With plug-ins, not only can you reuse your own code across applications, but you can also reuse developments made by other contributors and add third-party extensions to the symfony core.
The symfony project website contains a page dedicated to symfony plug-ins. It is in the symfony wiki and accessible with the following URL:
http://www.symfony-project.org/plugins/
Each plug-in listed there has its own page, with detailed installation instructions and documentation.
Some of these plug-ins are contributions from the community, and some come from the core symfony developers. Among the latter, you will find the following:
sfFeedPlugin: Automates the manipulation of RSS and Atom feedssfThumbnailPlugin: Creates thumbnails--for instance, for uploaded imagessfMediaLibraryPlugin: Allows media upload and management, including an extension for rich text editors to allow authoring of images inside rich textsfShoppingCartPlugin: Allows shopping cart managementsfPagerNavigationPlugin: Provides classical and Ajax pager controls, based on an sfPager objectsfGuardPlugin: Provides authentication, authorization, and other user management features above the standard security feature of symfonysfPrototypePlugin: Provides prototype and script.aculo.us JavaScript files as a standalone librarysfSuperCachePlugin: Writes pages in cache directory under the web root to allow the web server to serve them as fast as possiblesfOptimizerPlugin: Optimizes your application's code to make it execute faster in the production environment (see the next chapter for details)sfErrorLoggerPlugin: Logs every 404 and 500 error in a database and provides an administration module to browse these errorssfSslRequirementPlugin: Provides SSL encryption support for actionsThe wiki also proposes plug-ins designed to extend your Propel objects, also called behaviors. Among them, you will find the following:
sfPropelParanoidBehaviorPlugin: Disables object deletion and replaces it with the updating of a deleted_at columnsfPropelOptimisticLockBehaviorPlugin: Implements optimistic locking for Propel objectsYou should regularly check out the symfony wiki, because new plug-ins are added all the time, and they bring very useful shortcuts to many aspects of web application programming.
Apart from the symfony wiki, the other ways to distribute plug-ins are to propose a plug-ins archive for download, to host them in a PEAR channel, or to store them in a public version control repository.
The plug-in installation process differs according to the way it's packaged. Always refer to the included README file and/or installation instructions on the plug-in download page. Also, always clear the symfony cache after installing a plug-in.
Plug-ins are installed applications on a per-project basis. All the methods described in the following sections result in putting all the files of a plug-in into a myproject/plugins/pluginName/ directory.
Plug-ins listed on the symfony wiki are bundled as PEAR packages attached to a wiki page. To install such a plug-in, use the plugin-install task with a full URL, as shown in Listing 17-15.
Listing 17-15 - Installing a Plug-In from the Symfony Wiki
> cd myproject > php symfony plugin-install http://plugins.symfony-project.com/pluginName > php symfony cc
Alternatively, you can download the plug-in and install it from the disk. In this case, replace the channel name with the absolute path to the package archive, as shown in Listing 17-16.
Listing 17-16 - Installing a Plug-In from a Downloaded PEAR Package
> cd myproject > php symfony plugin-install /home/path/to/downloads/pluginName.tgz > php symfony cc
Some plug-ins are hosted on PEAR channels. Install them with the plugin-install task, and don't forget to mention the channel name, as shown in Listing 17-17.
Listing 17-17 - Installing a Plug-In from a PEAR Channel
> cd myproject > php symfony plugin-install channelName/pluginName > php symfony cc
These three types of installation all use a PEAR package, so the term "PEAR plug-in" will be used indiscriminately to talk about plug-ins installed from the symfony wiki, a PEAR channel, or a downloaded PEAR package.
Some plug-ins come as a simple archive of files. To install those, just unpack the archive into your project's plugins/ directory. If the plug-in contains a web/ subdirectory, make a copy or a symlink of this directory into the project's web/ directory, as demonstrated in Listing 17-18. Finally, don't forget to clear the cache.
Listing 17-18 - Installing a Plug-In from an Archive
> cd plugins > tar -zxpf myPlugin.tgz > cd .. > ln -sf plugins/myPlugin/web web/myPlugin > php symfony cc
Plug-ins sometimes have their own source code repository for version control. You can install them by doing a simple checkout in the plugins/ directory, but this can be problematic if your project itself is under version control.
Alternatively, you can declare the plug-in as an external dependency so that every update of your project source code also updates the plug-in source code. For instance, Subversion stores external dependencies in the svn:externals property. So you can add a plug-in by editing this property and updating your source code afterwards, as Listing 17-19 demonstrates.
Listing 17-19 - Installing a Plug-In from a Source Version Repository
> cd myproject > svn propedit svn:externals plugins pluginName http://svn.example.com/pluginName/trunk > svn up > php symfony cc
NOTE
If the plug-in contains a web/ directory, you must create a symlink to it the same way as for an archive plug-in.
Some plug-ins contain whole modules. The only difference between module plug-ins and classical modules is that module plug-ins don't appear in the myproject/apps/myapp/modules/ directory (to keep them easily upgradeable). They also need to be activated in the settings.yml file, as shown in Listing 17-20.
Listing 17-20 - Activating a Plug-In Module, in myapp/config/settings.yml
all:
.settings:
enabled_modules: [default, sfMyPluginModule]
This is to avoid a situation where the plug-in module is mistakenly made available for an application that doesn't require it, which could open a security breach. Think about a plug-in that provides frontend and backend modules. You will need to enable the frontend modules only in your frontend application, and the backend ones only in the backend application. This is why plug-in modules are not activated by default.
TIP
The default module is the only enabled module by default. That's not really a plug-in module, because it resides in the framework, in $sf_symfony_data_dir/modules/default/. This is the module that provides the congratulations pages, and the default error pages for 404 and credentials required errors. If you don't want to use the symfony default pages, just remove this module from the enabled_modules setting.
If a glance at your project's plugins/ directory can tell you which plug-ins are installed, the plugin-list task tells you even more: the version number and the channel name of each installed plug-in (see Listing 17-21).
Listing 17-21 - Listing Installed Plug-Ins
> cd myproject > php symfony plugin-list Installed plugins: sfPrototypePlugin 1.0.0-stable # pear.symfony-project.com (symfony) sfSuperCachePlugin 1.0.0-stable # pear.symfony-project.com (symfony) sfThumbnail 1.1.0-stable # pear.symfony-project.com (symfony)
To uninstall a PEAR plug-in, call the plugin-uninstall task from the root project directory, as shown in Listing 17-22. You must prefix the plug-in name with its installation channel (use the plugin-list task to determine this channel).
Listing 17-22 - Uninstalling a Plug-In
> cd myproject > php symfony plugin-uninstall pear.symfony-project.com/sfPrototypePlugin > php symfony cc
TIP
Some channels have an alias. For instance, the pear.symfony-project.com channel can also be seen as symfony, which means that you can uninstall the sfPrototypePlugin as in Listing 17-22 by calling simply php symfony plugin-uninstall symfony/sfPrototypePlugin.
To uninstall an archive plug-in or an SVN plug-in, remove manually the plug-in files from the project plugins/ and web/ directories, and clear the cache.
To upgrade a plug-in, either use the plugin-upgrade task (for a PEAR plug-in) or do an svn update (if you grabbed the plug-in from a version control repository). Archive plug-ins can't be upgraded easily.
Plug-ins are written using the PHP language. If you can understand how an application is organized, you can understand the structure of the plug-ins.
A plug-in directory is organized more or less like a project directory. The plug-in files have to be in the right directories in order to be loaded automatically by symfony when needed. Have a look at the plug-in file structure description in Listing 17-23.
Listing 17-23 - File Structure of a Plug-In
pluginName/
config/
*schema.yml // Data schema
*schema.xml
config.php // Specific plug-in configuration
data/
generator/
sfPropelAdmin
*/ // Administration generator themes
template/
skeleton/
fixtures/
*.yml // Fixtures files
tasks/
*.php // Pake tasks
lib/
*.php // Classes
helper/
*.php // Helpers
model/
*.php // Model classes
modules/
*/ // Modules
actions/
actions.class.php
config/
module.yml
view.yml
security.yml
templates/
*.php
validate/
*.yml
web/
* // Assets
Plug-ins can contain a lot of things. Their content is automatically taken into account by your application at runtime and when calling tasks with the command line. But for plug-ins to work properly, you must respect a few conventions:
propel- tasks. When you call propel-build-model in your project, you rebuild the project model and all the plug-in models with it. Note that a plug-in schema must always have a package attribute under the shape plugins.pluginName. lib.model, as shown in Listing 17-24.Listing 17-24 - Example Schema Declaration in a Plug-In, in myPlugin/config/schema.yml
propel:
_attributes: { package: plugins.myPlugin.lib.model }
my_plugin_foobar:
_attributes: { phpName: myPluginFoobar }
id:
name: { type: varchar, size: 255, index: unique }
...
config.php). This file is executed after the application and project configuration, so symfony is already bootstrapped at that time. You can use this file, for instance, to add directories to the PHP include path or to extend existing classes with mixins.data/fixtures/ directory are processed by the propel-load-data task.symfony to see the list of available tasks, including the ones added by plug-ins.lib/ folders.use_helper() in templates. They must be in ahelper/ subdirectory of one of the plug-in's lib/ directory.myplugin/lib/model/ specialize the model classes generated by the Propel builder (in myplugin/lib/model/om/ and myplugin/lib/model/map/). They are, of course, autoloaded. Be aware that you cannot override the generated model classes of a plug-in in your own project directories.enabled_modules setting in your application.web/ directory if the system allows it, or copies the content of the module web/ directory into the project one. If the plug-in is installed from an archive or a version control repository, you have to copy the plug-in web/ directory by hand (as the README bundled with the plug-in should mention).There are some elements that the plugin-install task cannot handle on its own, and which require manual setup during installation:
sfConfig::get('app_myplugin_foo')), but you can't put the default values in an app.yml file located in the plug-in config/ directory. To handle default values, use the second argument of the sfConfig::get() method. The settings can still be overridden at the application level (see Listing 17-25 for an example).routing.yml.filters.yml.factories.yml.Generally speaking, all the configuration that should end up in one of the application configuration files has to be added manually. Plug-ins with such manual setup should embed a README file describing installation in detail.
Whenever you want to customize a plug-in, never alter the code found in the plugins/ directory. If you do so, you will lose all your modifications when you upgrade the plug-in. For customization needs, plug-ins provide custom settings, and they support overriding.
Well-designed plug-ins use settings that can be changed in the application app.yml, as Listing 17-25 demonstrates.
Listing 17-25 - Customizing a Plug-In That Uses the Application Configuration
// example plug-in code $foo = sfConfig::get('app_my_plugin_foo', 'bar'); // Change the 'foo' default value ('bar') in the application app.yml all: my_plugin: foo: barbar
The module settings and their default values are often described in the plug-in's README file.
You can replace the default contents of a plug-in module by creating a module of the same name in your own application. It is not really overriding, since the elements in your application are used instead of the ones of the plug-in. It works fine if you create templates and configuration files of the same name as the ones of the plug-ins.
On the other hand, if a plug-in wants to offer a module with the ability to override its actions, the actions.class.php in the plug-in module must be empty and inherit from an autoloading class, so that the method of this class can be inherited as well by the actions.class.php of the application module. See Listing 17-26 for an example.
Listing 17-26 - Customizing a Plug-In Action
// In myPlugin/modules/mymodule/lib/myPluginmymoduleActions.class.php class myPluginmymoduleActions extends sfActions { public function executeIndex() { // Some code there } } // In myPlugin/modules/mymodule/actions/actions.class.php require_once dirname(__FILE__).'/../lib/myPluginmymoduleActions.class.php'; class mymoduleActions extends myPluginmymoduleActions { // Nothing } // In myapp/modules/mymodule/actions/actions.class.php class mymoduleActions extends myPluginmymoduleActions { public function executeIndex() { // Override the plug-in code there } }
Only plug-ins packaged as PEAR packages can be installed with the plugin-install task. Remember that such plug-ins can be distributed via the symfony wiki, a PEAR channel, or a simple file download. So if you want to author a plug-in, it is better to publish it as a PEAR package than as a simple archive. In addition, PEAR packaged plug-ins are easier to upgrade, can declare dependencies, and automatically deploy assets in the web/ directory.
Suppose you have developed a new feature and want to package it as a plug-in. The first step is to organize the files logically so that the symfony loading mechanisms can find them when needed. For that purpose, you have to follow the structure given in Listing 17-23. Listing 17-27 shows an example of file structure for an sfSamplePlugin plug-in.
Listing 17-27 - Example List of Files to Package As a Plug-In
sfSamplePlugin/
README
LICENSE
config/
schema.yml
data/
fixtures/
fixtures.yml
tasks/
sfSampleTask.php
lib/
model/
sfSampleFooBar.php
sfSampleFooBarPeer.php
validator/
sfSampleValidator.class.php
modules/
sfSampleModule/
actions/
actions.class.php
config/
security.yml
lib/
BasesfSampleModuleActions.class.php
templates/
indexSuccess.php
web/
css/
sfSampleStyle.css
images/
sfSampleImage.png
For authoring, the location of the plug-in directory (sfSamplePlugin/ in Listing 17-27) is not important. It can be anywhere on the disk.
TIP Take examples of the existing plug-ins and, for your first attempts at creating a plug-in, try to reproduce their naming conventions and file structure.
The next step of plug-in authoring is to add a package.xml file at the root of the plug-in directory. The package.xml follows the PEAR syntax. Have a look at a typical symfony plug-in package.xml in Listing 17-28.
Listing 17-28 - Example package.xml for a Symfony Plug-In
<?xml version="1.0" encoding="UTF-8"?> <package packagerversion="1.4.6" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd"> <name>sfSamplePlugin</name> <channel>pear.symfony-project.com</channel> <summary>symfony sample plugin</summary> <description>Just a sample plugin to illustrate PEAR packaging</description> <lead> <name>Fabien POTENCIER</name> <user>fabpot</user> <email>fabien.potencier@symfony-project.com</email> <active>yes</active> </lead> <date>2006-01-18</date> <time>15:54:35</time> <version> <release>1.0.0</release> <api>1.0.0</api> </version> <stability> <release>stable</release> <api>stable</api> </stability> <license uri="http://www.symfony-project.org/license">MIT license</license> <notes>-</notes> <contents> <dir name="/"> <file role="data" name="README" /> <file role="data" name="LICENSE" /> <dir name="config"> <!-- model --> <file role="data" name="schema.yml" /> </dir> <dir name="data"> <dir name="fixtures"> <!-- fixtures --> <file role="data" name="fixtures.yml" /> </dir> <dir name="tasks"> <!-- tasks --> <file role="data" name="sfSampleTask.php" /> </dir> </dir> <dir name="lib"> <dir name="model"> <!-- model classes --> <file role="data" name="sfSampleFooBar.php" /> <file role="data" name="sfSampleFooBarPeer.php" /> </dir> <dir name="validator"> <!-- validators -> > <file role="data" name="sfSampleValidator.class.php" /> </dir> </dir> <dir name="modules"> <dir name="sfSampleModule"> <file role="data" name="actions/actions.class.php" /> <file role="data" name="config/security.yml" /> <file role="data" name="lib/BasesfSampleModuleActions.class.php" /> <file role="data" name="templates/indexSuccess.php" /> </dir> </dir> <dir name="web"> <dir name="css"> <!-- stylesheets --> <file role="data" name="sfSampleStyle.css" /> </dir> <dir name="images"> <!-- images --> <file role="data" name="sfSampleImage.png" /> </dir> </dir> </dir> </contents> <dependencies> <required> <php> <min>5.0.0</min> </php> <pearinstaller> <min>1.4.1</min> </pearinstaller> <package> <name>symfony</name> <channel>pear.symfony-project.com</channel> <min>1.0.0</min> <max>1.1.0</max> <exclude>1.1.0</exclude> </package> </required> </dependencies> <phprelease /> <changelog /> </package>
The interesting parts here are the <contents> and the <dependencies> tags, described next. For the rest of the tags, there is nothing specific to symfony, so you can refer to the PEAR online manual (http://pear.php.net/manual/en/) for more details about the package.xml format.
The <contents> tag is the place where you must describe the plug-in file structure. This will tell PEAR which files to copy and where. Describe the file structure with <dir> and <file> tags. All <file> tags must have a role="data" attribute. The <contents> part of Listing 17-28 describes the exact directory structure of Listing 17-27.
NOTE
The use of <dir> tags is not compulsory, since you can use relative paths as name values in the <file> tags. However, it is recommended so that the package.xml file remains readable.
Plug-ins are designed to work with a given set of versions of PHP, PEAR, symfony, PEAR packages, or other plug-ins. Declaring these dependencies in the <dependencies> tag tells PEAR to check that the required packages are already installed, and to raise an exception if not.
You should always declare dependencies on PHP, PEAR, and symfony, at least the ones corresponding to your own installation, as a minimum requirement. If you don't know what to put, add a requirement for PHP 5.0, PEAR 1.4, and symfony 1.0.
It is also recommended to add a maximum version number of symfony for each plug-in. This will cause an error message when trying to use a plug-in with a more advanced version of the framework, and this will oblige the plug-in author to make sure that the plug-in works correctly with this version before releasing it again. It is better to have an alert and to download an upgrade rather than have a plug-in fail silently.
The PEAR component has a command (pear package) that creates the .tgz archive of the package, provided you call the command shown in Listing 17-29 from a directory containing a package.xml.
Listing 17-29 - Packaging a Plug-In As a PEAR Package
> cd sfSamplePlugin > pear package Package sfSamplePlugin-1.0.0.tgz done
Once your plug-in is built, check that it works by installing it yourself, as shown in Listing 17-30.
Listing 17-30 - Installing the Plug-In
> cp sfSamplePlugin-1.0.0.tgz /home/production/myproject/ > cd /home/production/myproject/ > php symfony plugin-install sfSamplePlugin-1.0.0.tgz
According to their description in the <contents> tag, the packaged files will end up in different directories of your project. Listing 17-31 shows where the files of the sfSamplePlugin should end up after installation.
Listing 17-31 - The Plug-In Files Are Installed on the plugins/ and web/ Directories
plugins/
sfSamplePlugin/
README
LICENSE
config/
schema.yml
data/
fixtures/
fixtures.yml
tasks/
sfSampleTask.php
lib/
model/
sfSampleFooBar.php
sfSampleFooBarPeer.php
validator/
sfSampleValidator.class.php
modules/
sfSampleModule/
actions/
actions.class.php
config/
security.yml
lib/
BasesfSampleModuleActions.class.php
templates/
indexSuccess.php
web/
sfSamplePlugin/ ## Copy or symlink, depending on system
css/
sfSampleStyle.css
images/
sfSampleImage.png
Test the way the plug-in behaves in your application. If it works well, you are ready to distribute it across projects--or to contribute it to the symfony community.
A symfony plug-in gets the broadest audience when distributed by the symfony-project.org website. Even your own plug-ins can be distributed this way, provided that you follow these steps:
README file describes the way to install and use your plug-in, and that the LICENSE file gives the license details. Format your README with the Markdown Formatting syntax (http://daringfireball.net/projects/markdown/syntax).pear package command, and test it. The PEAR package must be named sfSamplePlugin-1.0.0.tgz (1.0.0 is the plug-in version).sfSamplePlugin-1.0.0.tgz).If you follow this procedure, users will be able to install your plug-in by simply typing the following command in a project directory:
> php symfony plugin:install sfSamplePlugin
To keep the plugins/ directory clean, ensure all the plug-in names are in camelCase and end with Plugin (for example, shoppingCartPlugin, feedPlugin, and so on). Before naming your plug-in, check that there is no existing plug-in with the same name.
NOTE
Plug-ins relying on Propel should contain Propel in the name. For instance, an authentication plug-in using the Propel data access objects should be called sfPropelAuth.
Plug-ins should always include a LICENSE file describing the conditions of use and the chosen license. You are also advised to add a README file to explain the version changes, purpose of the plug-in, its effect, installation and configuration instructions, etc.