[ Index ]
 

Code source de Symfony 1.0.0

Accédez au Source d'autres logiciels libresSoutenez Angelica Josefina !

title

Body

[fermer]

/doc/ -> 17-Extending-Symfony.txt (source)

   1  Chapter 17 - Extending Symfony
   2  ==============================
   3  
   4  Eventually, you will need to alter symfony's behavior. Whether you need to modify the way a certain class behaves or add your own custom features, the moment will inevitably happen--all clients have specific requirements that no framework can forecast. As a matter of fact, this situation is so common that symfony provides a mechanism to extend existing classes, called a mixin. You can even replace the core symfony classes on your own, using the factories settings. Once you have built an extension, you can easily package it as a plug-in, so that it can be reused in other applications--or by other symfony users.
   5  
   6  Mixins
   7  ------
   8  
   9  Among the current limitations of PHP, one of the most annoying is you can't have a class extend more than one class. Another limitation is you can't add new methods to an existing class or override existing methods. To palliate these two limitations and to make the framework truly extendable, symfony introduces a class called `sfMixer`. It is in no way related to cooking devices, but to the concept of mixins found in object-oriented programming. A mixin is a group of methods or functions that can be mixed into a class to extend it.
  10  
  11  ### Understanding Multiple Inheritance
  12  
  13  Multiple inheritance is the ability for a class to extend more than one class and inherit these class properties and methods. Let's consider an example. Imagine a `Story` and a `Book` class, each with its own properties and methods--just like in Listing 17-1.
  14  
  15  Listing 17-1 - Two Example Classes
  16  
  17      [php]
  18      class Story
  19      {
  20        protected $title = '';
  21        protected $topic = '';
  22        protected $characters = array();
  23  
  24        public function __construct($title = '', $topic = '', $characters = array())
  25        {
  26          $this->title = $title;
  27          $this->topic = $topic;
  28          $this->characters = $characters;
  29        }
  30  
  31        public function getSummary()
  32        {
  33          return $this->title.', a story about '.$this->topic;
  34        }
  35      }
  36  
  37      class Book
  38      {
  39        protected $isbn = 0;
  40  
  41        function setISBN($isbn = 0)
  42        {
  43          $this->isbn = $isbn;
  44        }
  45  
  46        public function getISBN()
  47        {
  48          return $this->isbn;
  49        }
  50      }
  51  
  52  A `ShortStory` class extends `Story`, a `ComputerBook` class extends `Book`, and logically, a `Novel` should extend both `Story` and `Book` and take advantage of all their methods. Unfortunately, this is not possible in PHP. You cannot write the `Novel` declaration as in Listing 17-2.
  53  
  54  Listing 17-2 - Multiple Inheritance Is Not Possible in PHP
  55  
  56      [php]
  57      class Novel extends Story, Book
  58      {
  59      }
  60  
  61      $myNovel = new Novel();
  62      $myNovel->getISBN();
  63  
  64  One possibility would be to have Novel implements two interfaces instead of having it extend two classes, but this would prevent you from having the methods actually written in the parent classes.
  65  
  66  ### Mixing Classes
  67  
  68  The `sfMixer` class takes another approach to the problem, taking an existing class and extending it a posteriori, provided that the class contains the proper hooks. The process involves two steps:
  69  
  70    * Declaring a class as extendable
  71    * Registering extensions (or mixins), after the class declaration
  72  
  73  Listing 17-3 shows how you would implement the `Novel` class with `sfMixer`.
  74  
  75  Listing 17-3 - Multiple Inheritance Is Possible via `sfMixer`
  76  
  77      [php]
  78      class Novel extends Story
  79      {
  80        public function __call($method, $arguments)
  81        {
  82          return sfMixer::callMixins();
  83        }
  84      }
  85  
  86      sfMixer::register('Novel', array('Book', 'getISBN'));
  87      $myNovel = new Novel();
  88      $myNovel->getISBN();
  89  
  90  One of the classes (`Story`) is chosen as the main parent, in line with PHP's ability to only inherit from one class. The `Novel` class is declared as extendable by the code located in the `__call()` method. The method of the other class (`Book`) is added afterwards to the `Novel` class by a call to `sfMixer::register()`. The next sections will explicitly explain this process.
  91  
  92  When the `getISBN()` method of the `Novel` class is called, everything happens as if the class had been defined as in Listing 17-2--except it's the magic of the `__call()` method and of the `sfMixer` static methods that simulate it. The `getISBN()` method is mixed in the `Novel` class.
  93  
  94  >**SIDEBAR**
  95  >When to use mixins
  96  >
  97  >The symfony mixin mechanism is useful in many cases. Simulating multiple inheritance, as described previously, is just one of them.
  98  >
  99  >You can use mixins to alter a method after its declaration. For example, when building a graphic library, you will probably implement a `Line` object--representing a line. It will have four attributes (the coordinates for both ends) and a `draw()` method to render itself. A `ColoredLine` should have the same properties and methods, but with an additional attribute, `color`, to specify its color. Furthermore, the `draw()` method of a `ColoredLine` is a little different from the one of a simple `Line`, to use the object's color. You could package the abilities of a graphical element to deal with color into a `ColoredElement` class. This would allow you to reuse the color methods for other graphical elements (`Dot`, `Polygon`, and so on). In this case, the ideal implementation of the `ColoredLine` class would be an extension of the `Line` class, with methods from the `ColoredElement` class mixed in. The final `draw()` method would be a mix between the original one from `Line` and the one from `ColoredElement`.
 100  >
 101  >Mixins can also be seen as a way to add new methods to an existing class. For instance, the symfony action class, called `sfActions`, is defined in the framework. One of the constraints of PHP is that you cannot change the sfActions definition after its initial declaration. You may want to add a custom method to sfActions in one of your applications only--for instance, to forward a request to a special web service. For that purpose, PHP alone falls short, but the mixin mechanism provides a perfect solution.
 102  
 103  ### Declaring a Class As Extendable
 104  
 105  To declare a class as extendable, you must insert one or several "hooks" into the code, which the `sfMixer` class can later identify. These hooks are calls to the `sfMixer::callMixins()` method. Many of the symfony classes already contain such hooks, including `sfRequest`, `sfResponse`, `sfController`, `sfUser`, `sfAction`, and others.
 106  
 107  The hook can be placed in different parts of the class, according to the desired degree of extensibility:
 108  
 109    * To be able to add new methods to a class, you must insert the hook in the `__call()` method and return its result, as demonstrated in Listing 17-4.
 110  
 111  Listing 17-4 - Giving a Class the Ability to Get New Methods
 112  
 113      [php]
 114      class SomeClass
 115      {
 116        public function __call($method, $arguments)
 117        {
 118          return sfMixer::callMixins();
 119        }
 120      }
 121  
 122    * To be able to alter the way an existing method works, you must insert the hook inside the method, as demonstrated in Listing 17-5. The code added by the mixin class will be executed where the hook is placed.
 123  
 124  Listing 17-5 - Giving a Method the Ability to Be Altered
 125  
 126      [php]
 127      class SomeOtherClass
 128      {
 129        public function doThings()
 130        {
 131          echo "I'm working...";
 132          sfMixer::callMixins();
 133        }
 134      }
 135  
 136  You may want to place more than one hook in a method. In this case, you must name the hooks, so that you can define which hook is to be extended afterwards, as demonstrated in Listing 17-6. A named hook is a call to `callMixins()` with a hook name as a parameter. This name will be used afterwards, when registering a mixin, to tell where in the method the mixin code must be executed.
 137  
 138  Listing 17-6 - A Method Can Contain More Than One Hook, In Which Case They Must Be Named
 139  
 140      [php]
 141      class AgainAnotherClass
 142      {
 143        public function doMoreThings()
 144        {
 145          echo "I'm ready.";
 146          sfMixer::callMixins('beginning');
 147          echo "I'm working...";
 148          sfMixer::callMixins('end');
 149          echo "I'm done.";
 150        }
 151      }
 152  
 153  Of course, you can combine these techniques to create classes with the ability to be assigned new and extendable methods, as Listing 17-7 demonstrates.
 154  
 155  Listing 17-7 - A Class Can Be Extendable in Various Ways
 156  
 157      [php]
 158      class BicycleRider
 159      {
 160        protected $name = 'John';
 161  
 162        public function getName()
 163        {
 164          return $this->name;
 165        }
 166  
 167        public function sprint($distance)
 168        {
 169          echo $this->name." sprints ".$distance." meters\n";
 170          sfMixer::callMixins(); // The sprint() method is extendable
 171        }
 172  
 173        public function climb()
 174        {
 175          echo $this->name.' climbs';
 176          sfMixer::callMixins('slope'); // The climb() method is extendable here
 177          echo $this->name.' gets to the top';
 178          sfMixer::callMixins('top'); // And also here
 179        }
 180  
 181        public function __call($method, $arguments)
 182        {
 183          return sfMixer::callMixins(); // The BicyleRider class is extendable
 184        }
 185      }
 186  
 187  >**CAUTION**
 188  >Only the classes that are declared as extendable can be extended by `sfMixer`. This means that you cannot use this mechanism to extend a class that didn't "subscribe" to this service.
 189  
 190  ### Registering Extensions
 191  
 192  To register an extension to an existing hook, use the `sfMixer::register()` method. Its first argument is the element to extend, and the second argument is a PHP callable and represents the mixin.
 193  
 194  The format of the first argument depends on what you try to extend:
 195  
 196    * If you extend a class, use the class name.
 197    * If you extend a method with an anonymous hook, use the `class:method` pattern.
 198    * If you extend a method with a named hook, use the `class:method:hook` pattern.
 199  
 200  Listing 17-8 illustrates this principle by extending the class defined in Listing 17-7. The extended object is automatically passed as first parameter to the mixin methods (except, of course, if the extended method is static). The mixin method also gets access to the parameters of the original method call.
 201  
 202  Listing 17-8 - Registering Extensions
 203  
 204      [php]
 205      class Steroids
 206      {
 207        protected $brand = 'foobar';
 208  
 209        public function partyAllNight($bicycleRider)
 210        {
 211          echo $bicycleRider->getName()." spends the night dancing.\n";
 212          echo "Thanks ".$brand."!\n";
 213        }
 214  
 215        public function breakRecord($bicycleRider, $distance)
 216        {
 217          echo "Nobody ever made ".$distance." meters that fast before!\n";
 218        }
 219  
 220        static function pass()
 221        {
 222          echo " and passes half the peloton.\n";
 223        }
 224      }
 225  
 226      sfMixer::register('BicycleRider', array('Steroids', 'partyAllNight'));
 227      sfMixer::register('BicycleRider:sprint', array('Steroids', 'breakRecord'));
 228      sfMixer::register('BicycleRider:climb:slope', array('Steroids', 'pass'));
 229      sfMixer::register('BicycleRider:climb:top', array('Steroids', 'pass'));
 230  
 231      $superRider = new BicycleRider();
 232      $superRider->climb();
 233      => John climbs and passes half the peloton
 234      => John gets to the top and passes half the peloton
 235      $superRider->sprint(2000);
 236      => John sprints 2000 meters
 237      => Nobody ever made 2000 meters that fast before!
 238      $superRider->partyAllNight();
 239      => John spends the night dancing.
 240      => Thanks foobar!
 241  
 242  The extension mechanism is not only about adding methods. The partyAllNight() method uses an attribute of the `Steroids` class. This means that when you extend the `BicycleRider` class with a method of the `Steroids` class, you actually create a new `Steroids` instance inside the `BicycleRider` object.
 243  
 244  >**CAUTION**
 245  >You cannot add two methods with the same name to an existing class. This is because the `callMixins()` call in the `__call()` methods uses the mixin method name as a key. Also, you cannot add a method to a class that already has a method with the same name, because the mixin mechanism relies on the magic `__call()` method and, in that particular case, it would never be called.
 246  
 247  The second argument of the `register()` call is a PHP callable, so it can be a `class::method` array, or an `object->method` array, or even a function name. See examples in Listing 17-9.
 248  
 249  Listing 17-9 - Any Callable Can Be Registered As a Mixer Extension
 250  
 251      [php]
 252      // Use a class method as a callable
 253      sfMixer::register('BicycleRider', array('Steroids', 'partyAllNight'));
 254  
 255      // Use an object method as a callable
 256      $mySteroids = new Steroids();
 257      sfMixer::register('BicycleRider', array($mySteroids, 'partyAllNight'));
 258  
 259      // Use a function as a callable
 260      sfMixer::register('BicycleRider', 'die');
 261  
 262  The extension mechanism is dynamic, which means that even if you already instantiated an object, it can take advantage of further extensions in its class. See an example in Listing 17-10.
 263  
 264  Listing 17-10 - The Extension Mechanism Is Dynamic and Can Occur Even After Instantiation
 265  
 266      [php]
 267      $simpleRider = new BicycleRider();
 268      $simpleRider->sprint(500);
 269      => John sprints 500 meters
 270      sfMixer::register('BicycleRider:sprint', array('Steroids', 'breakRecord'));
 271      $simpleRider->sprint(500);
 272      => John sprints 500 meters
 273      => Nobody ever made 500 meters that fast before!
 274  
 275  ### Extending with More Precision
 276  
 277  The `sfMixer::callMixins()` instruction is actually a shortcut to something a little bit more elaborate. It automatically loops over the list of registered mixins and calls them one by one, passing to it the current object and the current method parameters. In short, an `sfMixer::callMixins()` call behaves more or less like Listing 17-11.
 278  
 279  Listing 17-11 - `callMixin()` Loops Over the Registered Mixins and Executes Them
 280  
 281      [php]
 282      foreach (sfMixer::getCallables($class.':'.$method.':'.$hookName) as $callable)
 283      {
 284        call_user_func_array($callable, $parameters);
 285      }
 286  
 287  If you want to pass other parameters or to do something special with the return value, you can write the foreach loop explicitly instead of using the shortcut method. Look at Listing 17-12 for an example of a mixin more integrated into a class.
 288  
 289  Listing 17-12 - Replacing `callMixin()` by a Custom Loop
 290  
 291      [php]
 292      class Income
 293      {
 294        protected $amout = 0;
 295  
 296        public function calculateTaxes($rate = 0)
 297        {
 298          $taxes = $this->amount * $rate;
 299          foreach (sfMixer::getCallables('Income:calculateTaxes') as $callable)
 300          {
 301            $taxes += call_user_func($callable, $this->amount, $rate);
 302          }
 303  
 304          return $taxes;
 305        }
 306      }
 307  
 308      class FixedTax
 309      {
 310        protected $minIncome = 10000;
 311        protected $taxAmount = 500;
 312  
 313        public function calculateTaxes($amount)
 314        {
 315          return ($amount > $this->minIncome) ? $this->taxAmount : 0;
 316        }
 317      }
 318  
 319      sfMixer::register('Income:calculateTaxes', array('FixedTax', 'calculateTaxes'));
 320  
 321  >**SIDEBAR**
 322  >Propel Behaviors
 323  >
 324  >Propel behaviors, discussed previously in Chapter 8, are a special kind of mixin: They extend Propel-generated objects. Let's look at an example.
 325  >
 326  >The Propel objects corresponding to the tables of the database all have a delete() method, which deletes the related record from the database. But for an `Invoice` class, for which you can't delete a record, you may want to alter the `delete()` method to be able to keep the record in the database and change the value of an is_deleted attribute to true instead. Usual object retrieval methods (`doSelect()`, `retrieveByPk()`) would only consider the records for which `is_deleted` is false. You would also need to add another method called `forceDelete()`, which would allow you to really delete the record. In fact, all these modifications can be packaged into a new class, called `ParanoidBehavior`. The final `Invoice` class extends the Propel `BaseInvoice` class and has methods of the `ParanoidBehavior` mixed in.
 327  >
 328  >So a behavior is a mixin on a Propel object. Actually, the term "behavior" in symfony covers one more thing: the fact that the mixin is packaged as a plug-in. The `ParanoidBehavior` class just mentioned corresponds to a real symfony plug-in called `sfPropelParanoidBehaviorPlugin`. Refer to the symfony wiki ([http://www.symfony-project.com/trac/wiki/sfPropelParanoidBehaviorPlugin](http://www.symfony-project.com/trac/wiki/sfPropelParanoidBehaviorPlugin)) for details on installation and use of this plug-in.
 329  >
 330  >One last word about behaviors: To be able to support them, the generated Propel objects must contain quite a number of hooks. These may slow down execution a little and penalize performance if you don't use behaviors. That's why the hooks are not enabled by default. In order to add them and enable behavior support, you must first set the `propel.builder.addBehaviors` property to `true` in the `propel.ini` file and rebuild the model.
 331  
 332  Factories
 333  ---------
 334  
 335  A factory is the definition of a class for a certain task. Symfony relies on factories for its core features such as the controller and session capabilities. For instance, when the framework needs to create a new request object, it searches in the factory definition for the name of the class to use for that purpose. The default factory definition for requests is `sfWebRequest`, so symfony creates an object of this class in order to deal with requests. The great advantage of using a factory definition is that it is very easy to alter the core features of the framework: Just change the factory definition, and symfony will use your custom request class instead of its own.
 336  
 337  The factory definitions are stored in the `factories.yml` configuration file. Listing 17-13 shows the default factory definition file. Each definition is made of the name of an autoloaded class and (optionally) a set of parameters. For instance, the session storage factory (set under the `storage:` key) uses a `session_name` parameter to name the cookie created on the client computer to allow persistent sessions.
 338  
 339  Listing 17-13 - Default Factories File, in `myapp/config/factories.yml`
 340  
 341      cli:
 342        controller:
 343          class: sfConsoleController
 344        request:
 345          class: sfConsoleRequest
 346  
 347      test:
 348        storage:
 349          class: sfSessionTestStorage
 350  
 351      #all:
 352      #  controller:
 353      #    class: sfFrontWebController
 354      #
 355      #  request:
 356      #    class: sfWebRequest
 357      #
 358      #  response:
 359      #    class: sfWebResponse
 360      #
 361      #  user:
 362      #    class: myUser
 363      #
 364      #  storage:
 365      #    class: sfSessionStorage
 366      #    param:
 367      #      session_name: symfony
 368      #
 369      #  view_cache:
 370      #    class: sfFileCache
 371      #    param:
 372      #      automaticCleaningFactor: 0
 373      #      cacheDir:                %SF_TEMPLATE_CACHE_DIR%
 374  
 375  The best way to change a factory is to create a new class inheriting from the default factory and to add new methods to it. For instance, the user session factory is set to the `myUser` class (located in `myapp/lib/`) and inherits from `sfUser`. Use the same mechanism to take advantage of the existing factories. Listing 17-14 shows an example of a new factory for the request object.
 376  
 377  Listing 17-14 - Overriding Factories
 378  
 379      [php]
 380      // Create a myRequest.class.php in an autoloaded directory,
 381      // For instance in myapp/lib/
 382      <?php
 383  
 384      class myRequest extends sfRequest
 385      {
 386        // Your code here
 387      }
 388  
 389      // Declare this class as the request factory in factories.yml
 390      all:
 391        request:
 392          class: myRequest
 393  
 394  Bridges to Other Framework Components
 395  -------------------------------------
 396  
 397  If you need capabilities provided by a third-party class, and if you don't want to copy this class in one of the symfony `lib/` dirs, you will probably install it outside of the usual places where symfony looks for files. In that case, using this class will imply a manual `require` in your code, unless you use the symfony bridge to take advantage of the autoloading.
 398  
 399  Symfony doesn't (yet) provide tools for everything. If you need a PDF generator, an API to Google Maps, or a PHP implementation of the Lucene search engine, you will probably need a few libraries from the Zend Framework. If you want to manipulate images directly in PHP, connect to a POP3 account to read e-mails, or design a console interface, you might choose the libraries from eZcomponents. Fortunately, if you define the right settings, the components from both these libraries will work out of the box in symfony.
 400  
 401  The first thing that you need to declare (unless you installed the third-party libraries via PEAR) is the path to the root directory of the libraries. This is to be done in the application `settings.yml`:
 402  
 403      .settings:
 404        zend_lib_dir:   /usr/local/zend/library/
 405        ez_lib_dir:     /usr/local/ezcomponents/
 406  
 407  Then, extend the autoload routine by specifying which library to consider when the autoloading fails with symfony:
 408  
 409      .settings:
 410        autoloading_functions:
 411          - [sfZendFrameworkBridge, autoload]
 412          - [sfEzComponentsBridge,  autoload]
 413  
 414  Note that this setting is distinct from the rules defined in `autoload.yml` (see Chapter 19 for more information about this file). The `autoloading_functions` setting specifies bridge classes, and `autoload.yml` specifies paths and rules for searching. The following describes what will happen when you create a new object of an unloaded class:
 415  
 416    1. The symfony autoloading function (`sfCore::splAutoload()`) first looks for a class in the paths declared in the `autoload.yml` file.
 417    2. If none is found, the callback methods declared in the `sf_autoloading_functions` setting will be called one after the other, until one of them returns `true`:
 418    3. `sfZendFrameworkBridge::autoload()`
 419    4. `sfEzComponentsBridge::autoload()`
 420    5. If these also return false, if you use PHP 5.0.X, symfony will throw an exception saying that the class doesn't exist. Starting with PHP 5.1, the error will be generated by PHP itself.
 421  
 422  This means that the other framework components benefit from the autoload mechanism, and you can use them even more easily than within their own environment. For instance, if you want to use the `Zend_Search` component in the Zend Framework to implement an equivalent of the Lucene search engine in PHP, you have to write this:
 423  
 424      [php]
 425      require_once 'Zend/Search/Lucene.php';
 426      $doc = new Zend_Search_Lucene_Document();
 427      $doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
 428      ...
 429  
 430  With symfony and the Zend Framework bridge, it is simpler. Just write this:
 431  
 432      [php]
 433      $doc = new Zend_Search_Lucene_Document(); // The class is autoloaded
 434      $doc->addField(Zend_Search_Lucene_Field::Text('url', $docUrl));
 435      ...
 436  
 437  The available bridges are stored in the `$sf_symfony_lib_dir/addon/bridge/` directory.
 438  
 439  Plug-Ins
 440  --------
 441  
 442  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.
 443  
 444  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.
 445  
 446  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.
 447  
 448  ### Finding Symfony Plug-Ins
 449  
 450  The symfony project website contains a page dedicated to symfony plug-ins. It is in the symfony wiki and accessible with the following URL:
 451  
 452      http://www.symfony-project.com/trac/wiki/SymfonyPlugins
 453  
 454  Each plug-in listed there has its own page, with detailed installation instructions and documentation.
 455  
 456  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:
 457  
 458    * `sfFeedPlugin`: Automates the manipulation of RSS and Atom feeds
 459    * `sfThumbnailPlugin`: Creates thumbnails--for instance, for uploaded images
 460    * `sfMediaLibraryPlugin`: Allows media upload and management, including an extension for rich text editors to allow authoring of images inside rich text
 461    * `sfShoppingCartPlugin`: Allows shopping cart management
 462    * `sfPagerNavigationPlugin`: Provides classical and Ajax pager controls, based on an `sfPager` object
 463    * `sfGuardPlugin`: Provides authentication, authorization, and other user management features above the standard security feature of symfony
 464    * `sfPrototypePlugin`: Provides prototype and script.aculo.us JavaScript files as a standalone library
 465    * `sfSuperCachePlugin`: Writes pages in cache directory under the web root to allow the web server to serve them as fast as possible
 466    * `sfOptimizerPlugin`: Optimizes your application's code to make it execute faster in the production environment (see the next chapter for details)
 467    * `sfErrorLoggerPlugin`: Logs every 404 and 500 error in a database and provides an administration module to browse these errors
 468    * `sfSslRequirementPlugin`: Provides SSL encryption support for actions
 469  
 470  The wiki also proposes plug-ins designed to extend your Propel objects, also called behaviors. Among them, you will find the following:
 471  
 472    * `sfPropelParanoidBehaviorPlugin`: Disables object deletion and replaces it with the updating of a `deleted_at` column
 473    * `sfPropelOptimisticLockBehaviorPlugin`: Implements optimistic locking for Propel objects
 474  
 475  You 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.
 476  
 477  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.
 478  
 479  ### Installing a Plug-In
 480  
 481  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.
 482  
 483  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.
 484  
 485  #### PEAR Plug-Ins
 486  
 487  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.
 488  
 489  Listing 17-15 - Installing a Plug-In from the Symfony Wiki
 490  
 491      > cd myproject
 492      > php symfony plugin-install http://plugins.symfony-project.com/pluginName
 493      > php symfony cc
 494  
 495  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.
 496  
 497  Listing 17-16 - Installing a Plug-In from a Downloaded PEAR Package
 498  
 499      > cd myproject
 500      > php symfony plugin-install /home/path/to/downloads/pluginName.tgz
 501      > php symfony cc
 502  
 503  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.
 504  
 505  Listing 17-17 - Installing a Plug-In from a PEAR Channel
 506  
 507      > cd myproject
 508      > php symfony plugin-install channelName/pluginName
 509      > php symfony cc
 510  
 511  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.
 512  
 513  #### Archive Plug-Ins
 514  
 515  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.
 516  
 517  Listing 17-18 - Installing a Plug-In from an Archive
 518  
 519      > cd plugins
 520      > tar -zxpf myPlugin.tgz
 521      > cd ..
 522      > ln -sf plugins/myPlugin/web web/myPlugin
 523      > php symfony cc
 524  
 525  #### Installing Plug-Ins from a Version Control Repository
 526  
 527  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.
 528  
 529  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.
 530  
 531  Listing 17-19 - Installing a Plug-In from a Source Version Repository
 532  
 533      > cd myproject
 534      > svn propedit svn:externals plugins
 535        pluginName   http://svn.example.com/pluginName/trunk
 536      > svn up
 537      > php symfony cc
 538  
 539  >**NOTE**
 540  >If the plug-in contains a `web/` directory, you must create a symlink to it the same way as for an archive plug-in.
 541  
 542  #### Activating a Plug-In Module
 543  
 544  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.
 545  
 546  Listing 17-20 - Activating a Plug-In Module, in `myapp/config/settings.yml`
 547  
 548      all:
 549        .settings:
 550          enabled_modules:  [default, sfMyPluginModule]
 551  
 552  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.
 553  
 554  >**TIP**
 555  >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.
 556  
 557  #### Listing the Installed Plug-Ins
 558  
 559  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).
 560  
 561  Listing 17-21 - Listing Installed Plug-Ins
 562  
 563      > cd myproject
 564      > php symfony plugin-list
 565  
 566      Installed plugins:
 567      sfPrototypePlugin               1.0.0-stable # pear.symfony-project.com (symfony)
 568      sfSuperCachePlugin              1.0.0-stable # pear.symfony-project.com (symfony)
 569      sfThumbnail                     1.1.0-stable # pear.symfony-project.com (symfony)
 570  
 571  #### Upgrading and Uninstalling Plug-Ins
 572  
 573  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).
 574  
 575  Listing 17-22 - Uninstalling a Plug-In
 576  
 577      > cd myproject
 578      > php symfony plugin-uninstall pear.symfony-project.com/sfPrototypePlugin
 579      > php symfony cc
 580  
 581  >**TIP**
 582  >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`.
 583  
 584  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.
 585  
 586  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.
 587  
 588  ### Anatomy of a Plug-In
 589  
 590  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.
 591  
 592  #### Plug-In File Structure
 593  
 594  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.
 595  
 596  Listing 17-23 - File Structure of a Plug-In
 597  
 598      pluginName/
 599        config/
 600          *schema.yml        // Data schema
 601          *schema.xml
 602          config.php         // Specific plug-in configuration
 603        data/
 604          generator/
 605            sfPropelAdmin
 606              */             // Administration generator themes
 607                templates/
 608                skeleton/
 609          fixtures/
 610            *.yml            // Fixtures files
 611          tasks/
 612            *.php            // Pake tasks
 613        lib/
 614          *.php              // Classes
 615          helper/
 616            *.php            // Helpers
 617          model/
 618            *.php            // Model classes
 619        modules/
 620          */                 // Modules
 621            actions/
 622              actions.class.php
 623            config/
 624              module.yml
 625              view.yml
 626              security.yml
 627            templates/
 628              *.php
 629            validate/
 630              *.yml
 631        web/
 632          *                  // Assets
 633  
 634  #### Plug-In Abilities
 635  
 636  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:
 637  
 638    * Database schemas are detected by the `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.
 639  
 640  Listing 17-24 - Example Schema Declaration in a Plug-In, in `myPlugin/config/schema.yml`
 641  
 642      propel:
 643        _attributes:    { package: plugins.myPlugin.lib.model }
 644        my_plugin_foobar:
 645          _attributes:    { phpName: myPluginFoobar }
 646            id:
 647            name:           { type: varchar, size: 255, index: unique }
 648            ...
 649  
 650    * The plug-in configuration is to be included in the plug-in bootstrap script (`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.
 651    * Fixtures files located in the plug-in `data/fixtures/` directory are processed by the `propel-load-data` task.
 652    * Tasks are immediately available to the symfony command line as soon as the plug-in is installed. It is a best practice to prefix the task by something meaningful--for instance, the plug-in name. Type `symfony` to see the list of available tasks, including the ones added by plug-ins.
 653    * Custom classes are autoloaded just like the ones you put in your project `lib/` folders.
 654    * Helpers are automatically found when you call `use_helper()` in templates. They must be in a` helper/` subdirectory of one of the plug-in's `lib/` directory.
 655    * 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. Be aware that you cannot override the generated model classes of a plug-in in your own project directories.
 656    * Modules provide new actions accessible from the outside, provided that you declare them in the `enabled_modules` setting in your application.
 657    * Web assets (images, scripts, style sheets, etc.) are made available to the server. When you install a plug-in via the command line, symfony creates a symlink to the project `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).
 658  
 659  #### Manual Plug-In Setup
 660  
 661  There are some elements that the `plugin-install` task cannot handle on its own, and which require manual setup during installation:
 662  
 663    * Custom application configuration can be used in the plug-in code (for instance, by using `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).
 664    * Custom routing rules have to be added manually to the application `routing.yml`.
 665    * Custom filters have to be added manually to the application `filters.yml`.
 666    * Custom factories have to be added manually to the application `factories.yml`.
 667  
 668  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.
 669  
 670  #### Customizing a Plug-In for an Application
 671  
 672  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.
 673  
 674  Well-designed plug-ins use settings that can be changed in the application `app.yml`, as Listing 17-25 demonstrates.
 675  
 676  Listing 17-25 - Customizing a Plug-In That Uses the Application Configuration
 677  
 678      [php]
 679      // example plug-in code
 680      $foo = sfConfig::get('app_my_plugin_foo', 'bar');
 681  
 682      // Change the 'foo' default value ('bar') in the application app.yml
 683      all:
 684        my_plugin:
 685          foo:       barbar
 686  
 687  The module settings and their default values are often described in the plug-in's `README` file.
 688  
 689  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.
 690  
 691  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.
 692  
 693  Listing 17-26 - Customizing a Plug-In Action
 694  
 695      [php]
 696      // In myPlugin/modules/mymodule/lib/myPluginmymoduleActions.class.php
 697      class myPluginmymoduleActions extends sfActions
 698      {
 699        public function executeIndex()
 700        {
 701          // Some code there
 702        }
 703      }
 704  
 705      // In myPlugin/modules/mymodule/actions/actions.class.php
 706      class mymoduleActions extends myPluginmymoduleActions
 707      {
 708        // Nothing
 709      }
 710  
 711      // In myapp/modules/mymodule/actions/actions.class.php
 712      class mymoduleActions extends myPluginmymoduleActions
 713      {
 714        public function executeIndex()
 715        {
 716          // Override the plug-in code there
 717        }
 718      }
 719  
 720  ### How to Write a Plug-In
 721  
 722  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.
 723  
 724  #### File Organization
 725  
 726  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.
 727  
 728  Listing 17-27 - Example List of Files to Package As a Plug-In
 729  
 730      sfSamplePlugin/
 731        README
 732        LICENSE
 733        config/
 734          schema.yml
 735        data/
 736          fixtures/
 737            fixtures.yml
 738          tasks/
 739            sfSampleTask.php
 740        lib/
 741          model/
 742            sfSampleFooBar.php
 743            sfSampleFooBarPeer.php
 744          validator/
 745            sfSampleValidator.class.php
 746        modules/
 747          sfSampleModule/
 748            actions/
 749              actions.class.php
 750            config/
 751              security.yml
 752            lib/
 753              BasesfSampleModuleActions.class.php
 754            templates/
 755              indexSuccess.php
 756        web/
 757          css/
 758            sfSampleStyle.css
 759          images/
 760            sfSampleImage.png
 761  
 762  For authoring, the location of the plug-in directory (`sfSamplePlugin/` in Listing 17-27) is not important. It can be anywhere on the disk.
 763  
 764  >**TIP**
 765  >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.
 766  
 767  #### Creating the package.xml File
 768  
 769  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.
 770  
 771  Listing 17-28 - Example `package.xml` for a Symfony Plug-In
 772  
 773      [xml]
 774      <?xml version="1.0" encoding="UTF-8"?>
 775      <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">
 776       <name>sfSamplePlugin</name>
 777       <channel>pear.symfony-project.com</channel>
 778       <summary>symfony sample plugin</summary>
 779       <description>Just a sample plugin to illustrate PEAR packaging</description>
 780       <lead>
 781        <name>Fabien POTENCIER</name>
 782        <user>fabpot</user>
 783        <email>fabien.potencier@symfony-project.com</email>
 784        <active>yes</active>
 785       </lead>
 786       <date>2006-01-18</date>
 787       <time>15:54:35</time>
 788       <version>
 789        <release>1.0.0</release>
 790        <api>1.0.0</api>
 791       </version>
 792       <stability>
 793        <release>stable</release>
 794        <api>stable</api>
 795       </stability>
 796       <license uri="http://www.symfony-project.com/license">MIT license</license>
 797       <notes>-</notes>
 798       <contents>
 799        <dir name="/">
 800         <file role="data" name="README" />
 801         <file role="data" name="LICENSE" />
 802         <dir name="config">
 803          <!-- model -->
 804          <file role="data" name="schema.yml" />
 805         </dir>
 806         <dir name="data">
 807          <dir name="fixtures">
 808           <!-- fixtures -->
 809           <file role="data" name="fixtures.yml" />
 810          </dir>
 811          <dir name="tasks">
 812           <!-- tasks -->
 813           <file role="data" name="sfSampleTask.php" />
 814          </dir>
 815         </dir>
 816         <dir name="lib">
 817          <dir name="model">
 818           <!-- model classes -->
 819           <file role="data" name="sfSampleFooBar.php" />
 820           <file role="data" name="sfSampleFooBarPeer.php" />
 821          </dir>
 822          <dir name="validator">
 823           <!-- validators ->>
 824           <file role="data" name="sfSampleValidator.class.php" />
 825          </dir>
 826         </dir>
 827         <dir name="modules">
 828          <dir name="sfSampleModule">
 829           <file role="data" name="actions/actions.class.php" />
 830           <file role="data" name="config/security.yml" />
 831           <file role="data" name="lib/BasesfSampleModuleActions.class.php" />
 832           <file role="data" name="templates/indexSuccess.php" />
 833          </dir>
 834         </dir>
 835         <dir name="web">
 836          <dir name="css">
 837           <!-- stylesheets -->
 838           <file role="data" name="sfSampleStyle.css" />
 839          </dir>
 840          <dir name="images">
 841           <!-- images -->
 842           <file role="data" name="sfSampleImage.png" />
 843          </dir>
 844         </dir>
 845        </dir>
 846       </contents>
 847       <dependencies>
 848        <required>
 849         <php>
 850          <min>5.0.0</min>
 851         </php>
 852         <pearinstaller>
 853          <min>1.4.1</min>
 854         </pearinstaller>
 855         <package>
 856          <name>symfony</name>
 857          <channel>pear.symfony-project.com</channel>
 858          <min>1.0.0</min>
 859          <max>1.1.0</max>
 860          <exclude>1.1.0</exclude>
 861         </package>
 862        </required>
 863       </dependencies>
 864       <phprelease />
 865       <changelog />
 866      </package>
 867  
 868  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/](http://pear.php.net/manual/en/)) for more details about the `package.xml` format.
 869  
 870  #### Contents
 871  
 872  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.
 873  
 874  >**NOTE**
 875  >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.
 876  
 877  #### Plug-In Dependencies
 878  
 879  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.
 880  
 881  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.
 882  
 883  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.
 884  
 885  #### Building the Plug-In
 886  
 887  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`.
 888  
 889  Listing 17-29 - Packaging a Plug-In As a PEAR Package
 890  
 891      > cd sfSamplePlugin
 892      > pear package
 893  
 894      Package sfSamplePlugin-1.0.0.tgz done
 895  
 896  Once your plug-in is built, check that it works by installing it yourself, as shown in Listing 17-30.
 897  
 898  Listing 17-30 - Installing the Plug-In
 899  
 900      > cp sfSamplePlugin-1.0.0.tgz /home/production/myproject/
 901      > cd /home/production/myproject/
 902      > php symfony plugin-install sfSamplePlugin-1.0.0.tgz
 903  
 904  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.
 905  
 906  Listing 17-31 - The Plug-In Files Are Installed on the `plugins/` and `web/` Directories
 907  
 908      plugins/
 909        sfSamplePlugin/
 910          README
 911          LICENSE
 912          config/
 913            schema.yml
 914          data/
 915            fixtures/
 916              fixtures.yml
 917            tasks/
 918              sfSampleTask.php
 919          lib/
 920            model/
 921              sfSampleFooBar.php
 922              sfSampleFooBarPeer.php
 923            validator/
 924              sfSampleValidator.class.php
 925          modules/
 926            sfSampleModule/
 927              actions/
 928                actions.class.php
 929              config/
 930                security.yml
 931              lib/
 932                BasesfSampleModuleActions.class.php
 933              templates/
 934                indexSuccess.php
 935      web/
 936        sfSamplePlugin/               ## Copy or symlink, depending on system
 937          css/
 938            sfSampleStyle.css
 939          images/
 940            sfSampleImage.png
 941  
 942  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.
 943  
 944  #### Hosting Your Plug-In in the Symfony Project Website
 945  
 946  A symfony plug-in gets the broadest audience when distributed by the `symfony-project.com` website. Even your own plug-ins can be distributed this way, provided that you follow these steps:
 947  
 948    1. Make sure the `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 Wiki Formatting syntax ([http://www.symfony-project.com/trac/wiki/WikiFormatting](http://www.symfony-project.com/trac/wiki/WikiFormatting)).
 949    2. Create a PEAR package for your plug-in by calling the `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).
 950    3. Create a new page on the symfony wiki named `sfSamplePlugin` (`Plugin` is a mandatory suffix). In this page, describe the plug-in usage, the license, the dependencies, and the installation procedure. You can reuse the contents of the plug-in `README` file. Check the existing plug-ins' wiki pages and use them as an example.
 951    4. Attach your PEAR package to the wiki page (`sfSamplePlugin-1.0.0.tgz`).
 952    5. Add the new plug-in wiki page (`[wiki:sfSamplePlugin]`) to the list of available plug-ins, which is also a wiki page ([http://www.symfony-project.com/trac/wiki/SymfonyPlugins](http://www.symfony-project.com/trac/wiki/SymfonyPlugins)).
 953  
 954  If you follow this procedure, users will be able to install your plug-in by simply typing the following command in a project directory:
 955  
 956      > php symfony plugin-install http://plugins.symfony-project.com/sfSamplePlugin
 957  
 958  #### Naming Conventions
 959  
 960  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.
 961  
 962  >**NOTE**
 963  >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`.
 964  
 965  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.
 966  
 967  Summary
 968  -------
 969  
 970  The symfony classes contain `sfMixer` hooks that give them the ability to be modified at the application level. The mixins mechanism allows multiple inheritance and class overriding at runtime even if the PHP limitations forbid it. So you can easily extend the symfony features, even if you have to modify the core classes for that--the factories configuration is here for that.
 971  
 972  Many such extensions already exist; they are packaged as plug-ins, to be easily installed, upgraded, and uninstalled through the symfony command line. Creating a plug-in is as easy as creating a PEAR package, and provides reusability across applications.
 973  
 974  The symfony wiki contains many plug-ins, and you can even add your own. So now that you know how to do it, we hope that you will enhance the symfony core with a lot of useful extensions!


Généré le : Fri Mar 16 22:42:14 2007 par Balluche grâce à PHPXref 0.7