How to use plugins
Overview
If you want to extend symfony, the plugin system ensures you a great flexibility for distribution, upgrade and clean separation with the rest of the framework. Based on the PEAR extension, the symfony plugin system allows to distribute custom libraries or modules.
What is a plugin?
A plugin is a packaged extension for a symfony project.
Plugins can contain classes, helpers, configuration, tasks, modules, schemas and model extensions of their own. The symfony autoloading mechanism takes plugins into account, and the features offered by a plugin are available in the project as if part of the framework.
Plugins 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 plugins have the advantage of managing dependencies, being easier to upgrade and automatically discovered.
All the plugins installed in a given project appear as sub-directories of the plugins/ directory.
Installing a plugin
According to the way a plugin is packaged and to its method of broadcasting, the installation may differ. Always refer to the included README file and/or installation instructions on the plugin download page. Also, always clear the symfony cache after installing a plugin.
All the methods described below result in putting all the files of a plugin into a plugins/pluginName/ directory.
Archive plugins
If you download a plugin as an archive, then just unpack this archive into your project's plugin/ directory, clear the cache, and you're done.
PEAR plugins
Some plugins are hosted on PEAR channels. For instance, the symfony channel contains a few plugins. Install them with the plugin-install task, and don't forget to mention the channel name.
$ cd myproject
$ symfony plugin-install channelName/pluginName
$ symfony cc
You will find quite a number of plugins in the symfony wiki. Each plugin has its own page, and is bundled as an attachment to the page. To install such a plugin, use the plugin-install task with a full URL:
$ cd myproject
$ symfony plugin-install http://plugins.symfony-project.com/pluginName
$ symfony cc
Alternately, if you downloaded a PEAR package, replace the channel name by the absolute path to the package archive.
$ cd myproject
$ symfony plugin-install /home/john/downloads/pluginName.tgz
$ symfony cc
Plugins from a version control repository
Plugins 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 plugin as an external dependance so that every update of your project source code also updates the plugin source code. For instance, Subversion stores external dependencies in the svn:externals property. To add a plugin, just edit the property and update your source code.
$ cd myproject
$ svn propedit svn:externals plugins
pluginName http://svn.example.com/pluginName/trunk
$ svn up
$ symfony cc
Module activation
Some plugins contain a whole module. The only difference between module plugins and classical modules is that module plugins don't appear in the myproject/apps/myapp/modules/ directory (to keep them easily upgradeable) and need to be enabled in the settings.yml file:
all:
.settings:
enabled_modules: [default, sfMyPluginModule]
This is to avoid that a plugin module becomes available by mistake for an application that doesn't require it, which could open a security breach.
Upgrading and uninstalling
To uninstall a plugin, just remove its directory from plugins/ and clear the cache.
To upgrade a plugin, either use the plugin-upgrade task (for a PEAR plugin) or do a svn update (if you grabbed the plugin from a version control repository).
Additionally, call the plugin-upgrade-all task to upgrade all the plugins installed with the command line.
What you can put in a plugin
Plugin file structure
A plugin directory is organized more or less like a project directory. The files that you put in the plugin directories are loaded by symfony when needed, provided that you put them in the right place.
myPlugin/
config/
*schema.yml // data schema
*schema.xml
config.php // specific plugin configuration
data/
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
Plugin abilities
A few remarks concerning what you can put in these directories:
Schemas are detected by the propel tasks. When you call propel-build-model in your project, you rebuild the project model and all the plugins model. Beware that all classes defined in a plugin schema must provide a package attribute under the shape plugins.pluginName.lib.model.
propel:
my_plugin_foobar:
_attributes: { phpName: myPluginFoobar, package: plugins.myPlugin.lib.model }
id:
name: { type: varchar, size: 255, index: unique }
...
PHP Configuration is included in the application bootstrap script (config.php). In a plugin config.php, you have access to the file path constants, the sfConfig class (but the configuration is empty) but not to the autoloading. Use this file to add directories to the PHP include path for instance.
Fixtures files are taken into account by the propel-load-data command
Tasks are immediately available to the symfony command line as soon as the plugin is installed. Type symfony to see the list of available tasks, including the ones added bu plugins.
Custom classes are autoloaded just like the ones you put in your project lib/ folders.
Helpers are automatically found when you call a use_helper() inclusion in the templates of the project
Model classes in 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. Beware that you cannot override the generated model classes of a plugin in your own project directories.
Modules provide new actions accessible from the outside, provided that you declare them in the enabled_module setting in your application. Think about a plugin 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 plugin modules are not enabled by default.
Web assets are made available to the server, by creating a symlink to the project web/ directory, if the system allows it, or by copying the content of the module web/ directory into the project one. Beware that the module assets are automatically supported only when you install a plugin via the command line. If the plugin is installed from an archive or a version control repository, the copy to the project web/ directory has to be done by hand, and the README bundled with the plugin should mention it.
Manual plugin setup
There are some elements that you can't include automatically in a plugin that have to be done manually at installation:
custom application configuration can be used in the plugin code (for instance, by using sfConfig::get('app_myplugin_foo')) but you can't put the default values in an app.yml located in the plugin 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.
routing rules have to be added manually to the application routing.yml. Plugins with specific routing rules should put them in the embedded README file.
Customizing a plugin for an application
Whenever you want to customize a plugin, never alter the code of the plugins/ directory. IF you do so, you will lose all your modifications when you upgrade the plugin. For customization needs, plugins provide custom settings, and they support overriding.
Well designed plugins use settings that can be changed in the application app.yml.
// example plugin 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 value are often described in the README file.
You can replace the default contents of a plugin 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 plugin. It works fine if you create templates and configuration files of the same name as the ones of the plugins.
On the other hand, if a plugin wants to offer a module with the ability to override its actions, the actions.class.php in the plugin 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.
// 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
class mymoduleActions extends myPluginmymoduleActions
{
// Nothing
}
// in myapp/modules/mymodule/actions/actions.class.php
class mymoduleActions extends myPluginmymoduleActions
{
public function executeIndex()
{
// Override the plugin code there
}
}
How to package a plugin for PEAR
To override the plugins schema files, you must copy them to your projects config directory, and if they do not contain the PluginName,prefix them with the PluginName-, for the example above, you would have make a copy of the plugins schema.yml file, and prefix it PluginName-, to produce PluginName-schema.yml in your project's config directory. This file will then be used instead of the plugin file when building the model.
To override the plugins schema files, you must copy them to your projects config directory, and if they do not contain the PluginName,prefix them with the PluginName-, for the example above, you would have make a copy of the plugins schema.yml file, and prefix it PluginName-, to produce PluginName-schema.yml in your project's config directory. This file will then be used instead of the plugin file when building the model.
To override the plugins schema files, you must copy them to your projects config directory, and if they do not contain the PluginName,prefix them with the PluginName-, for the example above, you would have make a copy of the plugins schema.yml file, and prefix it PluginName-, to produce PluginName-schema.yml in your project's config directory. This file will then be used instead of the plugin file when building the model.
Plugins provided as PEAR packages are easier to upgrade, can declare dependencies and automatically deploy assets in the web/ directory. All you need to do to package a plugin as a PEAR package is to add a package.xml at the root of the plugin and call the pear package command.
package.xml
The package.xml follows the PEAR syntax. For instance, here is a typical symfony plugin package.xml:
<?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 the use of 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.com/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>
</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>0.8.0</min>
<max>0.9.999</max>
</package>
</required>
</dependencies>
<phprelease />
<changelog />
</package>
The interesting parts here are the <contents> and the <dependencies> tags. For the rest of the tags, there is nothing specific to symfony, so you can refer to the PEAR online manual.
Contents
The package.xml describes the plugin file structure with <dir> and <file> tags. You don't have to use <dir> tags, since you can use relative paths in the file names. All files must have a role="data" attribute.
Describe in the <contents> tag the file structure of your plugin.
Dependencies
Plugins are designed to work with a given set of versions of PHP, PEAR, symfony, PEAR packages or other plugins. 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 install, as a minimum requirement. If you don't know what to put, add a requirement for PHP 5.0, PEAR 1.4 and symfony 0.6 (the plugin system changed in an incompatible way between 0.6 and 0.8).
We also recommend that you add a maximum version of symfony for your plugin. This will cause an error message when trying to use your plugin with a more advanced version of the framework, and this will oblige you to make sure that your plugin works correctly with this version before releasing it again. It's better to have an alert and to download an upgrade rather than having a plugin fail silently.
Naming conventions
To keep the plugin/ directory clean, we recommend that all the plugin names are in camelCase, start with 'sf' (ex: 'sfShoppingCart', 'sfFeed', etc.) and end with 'Plugin'. Before naming your plugin, check that there is no existing plugin with the same name.
Note: plugins relying on Propel should contain 'Propel' in the name. For instance, an authentication plugin using the Propel data access objects should be called 'sfPropelAuth'.
Plugins should 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, interest of the plugin, effect, installation and configuration instructions, etc.
Building the plugin
The PEAR component has a command that creates the .tgz archive of the package, provided you call the following command from a directory containing a package.xml:
$ pear package
Once your plugin is built, check that it works by installing it yourself.
Hosting your plugin at symfony-project.com
- Create your plugin on your machine (don't forget to add some documentation, a license file, a
package.xml file, ...)
- Create a PEAR package for your plugin
- Create a new page on the wiki named
NamePlugin where name is your plugin name and Plugin is a mandatory suffix. So, if you create a plugin named Mojo, create a wiki page named MojoPlugin and your PEAR package must be named MojoPlugin-1.0.0.tgz (1.0.0 is the PEAR version).
- Describe the plugin usage, the license, the dependancies, in the wiki page. Check the existing plugins wiki pages and use them as an example.
- Add you PEAR package as an attachment to your wiki page (
MojoPlugin-1.0.0.tgz)
- Add your plugin to the plugin list on the wiki
|