Extending plugins in PHP and Symfony

Plugins are great but they are never what you exactly wanted. When they are designed properly, the best way to customize them is to extend them instead of directly editing them.

Now, imagine I have:1

# Penguin.class.php
class Penguin
{
  public function __construct()
  {
    echo "Windows is bad\n";
  }
}
 
# Herd.class.php
class Herd
{
  public function __construct()
  {
    while ($i++ < 42) new Penguin();
  }
}
 
# test
new Herd();

I want to change Penguin's behavior to something a bit more positive. However I don't want to extend Herd especially if it's tied to Penguin everywhere... I'm stuck with the Penguin class and can't use another one.

With some languages you can alter classes dynamically, and it can be referred as monkeypatching. While it is actually possible in PHP, it is ugly at best; you end up typing code in character strings, losing proper syntax highlighting and the opcode caching. Unless there is a very good language support for these methods it's best to avoid them.

A handful of plugins for Symfony, like sfGuardPlugin (one of the most popular plugins) already have some kind of solution. They come with two classes, a dummy one and a real one:

# Penguin.class.php
class Penguin extends pluginPenguin
{
}
 
# pluginPenguin.class.php
class pluginPenguin
{
  public function __construct()
  {
    echo "Windows is bad\n";
  }
}

Which means you just have to edit the (almost) blank Penguin class.
This is a clean, object-oriented way to solve the problem.

However, there are practicals problems that will arise.

  • You still edit an existing file; your modifications would be erased by upgrading to a newer version of the plugin
  • It will confuse version control systems if you use one to retrieve the plugin
  • Your custom code is in the plugin directory, which is just illogical
  • You could chose no to ship any of the blank files, but the user would have to create all of them

There is a very simple solution to overcome all that: the Symfony autoloader will pick classes from local folders (like the lib folder of your project) first.
Which means you can just copy the Penguin.class.php file and customize it:

# Penguin.class.php your project's lib/ folder
class Penguin extends pluginPenguin
{
  public function __construct()
  {
    echo "Linux is good\n";
  }
}

If you don't use Symfony and/or a similar autoloader, there is another solution:

# Penguin.class.php in the sfHerdPlugin directory
require dirname(__FILE__).'/plugin/plugin'.basename(__FILE__);
 
if (is_readable(dirname(__FILE__).'/../sfHerdPluginCustom/'.basename(__FILE__)):
  require dirname(__FILE__).'/../sfHerdPluginCustom/'.basename(__FILE__);
else:
  class Penguin extends pluginPenguin
  {
  }
endif;
 
# Penguin.class.php in the sfHerdPluginCustom directory (optional)
class Penguin extends pluginPenguin
{
  public function __construct()
  {
    echo "Linux is good\n";
  }
}

The cool aspect is that if you don't create any corresponding files in the sfHerdPluginCustom directory, it will still work perfectly.

  1. The Herd and Penguin classes are references to GOTO++, a funny programming language. []
Share:
  • del.icio.us
  • Twitter
  • Reddit
  • Digg
  • Facebook
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Wikio
  • Identi.ca
Posted in PHP, Symfony | Tagged , , , , , | Leave a comment

PHP serialization optimization

I recently had to use the serialize() function to store objects in Memcache.

However, I realized that a lot of these objects (Propel objects precisely) were unnecessarily huge when stored: they had a lot of properties with quite long names having their default class value.

That's when I realized I could use the __sleep() function to store only what I wanted. I gave up on using this function before as it would have been to hard to maintain it ...unless you use some meta-programming features of PHP to store only the variables not having their default value! That's what I did, and the results are a faster unserialize(), less memory usage, and no maintenance whatsoever.

Read More »

Share:
  • del.icio.us
  • Twitter
  • Reddit
  • Digg
  • Facebook
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Wikio
  • Identi.ca
Posted in PHP, Symfony | Tagged , , , , , | Leave a comment

Symfony and lighttpd

I've seen some articles on how to configure lighttpd to serve a Symfony project, however they usually did at least one mistake:

  • Assuming that requests with periods ('.') are for static files (the period is a default separator in Symfony, and is extensively used in the new admin generators).
  • Ignoring parameters after a '?' (they are not widely used, except... in the new admin generators, and can be very useful if your application)

For the first part, there is a much simpler solution to handle static files: most of them are in specific directories, except for a very limited number of ones.

My solution also handles assets published by plugins (you might want to edit the corresponding line to a more liberal one though).

You might want to add your sitemap.xml.gz or robots.txt to this list if you generate them statically.

For the second part, you simply have to match explicitly the '?' part.

Here is the magic:

 
alias.url = (
  "/sf/" => "/home/web/symfony_12/data/web/sf/"
)
 
url.rewrite-once = (
  "^/css/.+" => "$0", # directories with static files
  "^/js/.+" => "$0",
  "^/images/.+" => "$0",
  "^/uploads/.+" => "$0",
  "^/favicon\.ico$" => "$0", # static file example
  "^/sf[A-z]+Plugin.*" => "$0", # plugins
  "^/sf/.+" => "$0", # symfony assets
  "^/backend\.php(/[^\?]*)(\?.*)?" => "/backend.php$1$2", # allow access to another application
  "^(/[^\?]*)(\?.*)?" => "/index.php$1$2" # default application
)
 

I guess the usage of periods in the rules also had the benefit of allowing the access to any alternative application automatically. With my solution you have to add each appname.php file manually, unless you use:

 
  "^/([a-z]+)\.php(/[^\?]*)(\?.*)?" => "/$1.php$2$3", # any app (prod)
 

Or for allowing any environment:

 
  "^/([a-z_]+)\.php(/[^\?]*)(\?.*)?" => "/$1.php$2$3", # any app (any env)
 

Note: your application must contain only lowercase letters, but you're free to adapt it to your own usage.

Share:
  • del.icio.us
  • Twitter
  • Reddit
  • Digg
  • Facebook
  • Google Bookmarks
  • DZone
  • LinkedIn
  • Wikio
  • Identi.ca
Posted in Symfony, Sysadmin | Tagged , , , , | 5 Comments