Symfony World blog is not maintained anymore. Check new sys.exit() programming blog.

Custom admin generator filter example

Scene from "The Silence of the Lambs" by Jonathan Demme (1991)

Once upon a time I needed to make custom filters for my admin generator...

Example case

Let's analyse a simple study case. Suppose we are running an online shop. The schema includes a product table which has two properties (among all other properties):

  • quantity - the number of products we have on our store
  • quantity_alarm - the shop manager should get alarmed when the number of products go below this

For example, we have such row in the product table:

name: bicycle
quantity: 1294
quantity_alarm: 100
This means we're having 1294 bikes on the store at the moment and the shop manager gets alarmed when the number of bikes reaches 100.

Simply, the manager of the shop wants to find the products he's running out of. He just wants to have something like a checkbox in his product admin generator filters. When he runs the filter with alarming attribute checked, he wants to see only those products which number is below the quantity_alarm.

Programming part

Configure the filters section of your admin generator:

filter:
  display: [ alarming ]
  fields:
    alarming:
      label: alarming quantity
      help: products needing supplies

Modify ProductFormFilter class. Let's begin with the configure method:

  public function configure()
  {
    // ...
 
    $this->manageFieldAlarming();
  }
 
  protected function manageFieldAlarming()
  {
    $this->widgetSchema ['alarming'] =
      new sfWidgetFormChoice(array(
        'choices' => array(
          '' => 'yes or no',
          1 => 'yes',
          0 => 'no'
    )));
    $this->validatorSchema ['alarming'] =
      new sfValidatorPass();
  }

Update the getFields method:

public function getFields()
{
  $fields = parent::getFields();
  $fields['alarming'] = 'alarming';
  return $fields;
}

Add the following method which will handle the database stuff:

public function addAlarmingColumnQuery($query, $field, $value)
{
  Doctrine::getTable('Product')
    ->applyAlarmingFilter($query, $value);
}

Now we need to create the table class method that we just called in the code above. Go to ProductTable model class and add the following:

  /**
   * Applies the alarming attribute to a given query retrieving products.
   *
   * @param Doctrine_Query $query - query to have alarming attribute applied.
   * @param Integer $value - alarming?
   */
  static public function applyAlarmingFilter($query, $value)
  {
    $rootAlias = $query->getRootAlias();
    switch ($value)
    {
      case '0':
        $query->where($rootAlias.'.quantity > '.$rootAlias.'.quantity_alarm');
        break;
      case '1':
        $query->where($rootAlias.'.quantity <= '\.$rootAlias.'.quantity_alarm');
        break;
    }
    return $query;
  }

The whole thing is complete!

The code could be written in many different ways, of course. The sfValidatorPass is used to pass unchanged filter values. There are three distinct values possible: empty string for 'yes or no', '0' for 'no' and '1' for 'yes'. If empty string is passed, we ignore it. If '0' or '1' is passed, we check it inside the switch statement.

If you're wondering about sfValidatorBoolean, it can't be used, since there are 3 options (yes, no, yes or no) and the boolean validator can handle only two values (but it can be used in other custom filters with no problems).

Notes

The above article is based on a magnificent symfony forum post by dlepage. It has been tested on symfony 1.4 but should work also with versions 1.2 and 1.3.

configuring symfony application on shared hosting

Scene from "Ashes and Diamonds" by Andrzej Wajda (1958)

Notes

The following article has been tested many times on symfony version 1.4 (and has been updated few months after initial publishing).

Introduction

Sometimes a symfony project has to be deployed on shared hosting. It may be especially difficult if no SSH access is provided - this is because one of the main symfony tool is the Command Line Interface. But there are more problems, such as the directory structure of the user's account on shared hosting, which may not be changed. You are forced to modify your app structure as well then. So let's face those problems!

Alternative directory structure

The main thing is to hide the application structure (especially configuration files) from the outside. This means that all directories except for web shall not be accessible from outside. I suggest splitting the whole symfony application into 3 separate directories:

  • one for symfony core libs
  • second one for our application content
  • and the last one for the public web stuff

Example application structure would look like this:

/home/user
  /public_html
    /images
    /css
    /js
    ...
  /symfony_1_4
    /data
    /lib
    /license
    /test
    ...
  /symfony_PROJECT_NAME
    /apps
    /config
    /data
    ...

Apache document root

As you can see, the web directory has been renamed to public_html. This has been done because usually the document root apache directory is named something different than web (which is used by symfony). Now the application has to be informed of what we've just done. Modify the config/ProjectConfiguration.class.php of your application:

public function setup()
{
  ...
  $this->setWebDir($this->getRootDir().'/../public_html');
}

This part is done.

Application controllers

You'll have to modify your all controllers (all applications and all environments) to update the project configuration class path. The original web project directory has been renamed (this doesn't matter) and moved one directory up (and this matters - the path needs to be updated).

The original require_once code looks something like this:

require_once dirname(__FILE__).'/../config/ProjectConfiguration.class.php';
and should be replaced with:
require_once dirname(__FILE__).'/../symfony_PROJECT_NAME/config/ProjectConfiguration.class.php';

Symfony core lib

Now it's time to link symfony core libs to the project. There are some alternative ways to do that, one of them is to change the config/ProjectConfiguration.class.php again:

require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php';
shall be replaced with the appropriate path to the lib. But it's not the easiest way, since you'll force some difficulties with the symfony autoload mechanism. Better way is simply to create a symlink (execute from the lib/vendor directory):
ln -s ../../../symfony_1_4/ symfony
So our application is linked to the symfony lib which is inaccessible from the web, as well as the symfony application code (apps, config, lib, etc.) is inaccessible from the outside.

Alternative symfony core libs

Maybe you're curious why the symfony core lib directory is named symfony_1_4. In case you want to host many applications on one shared hosting account, there's no need to upload symfony libs into different paths (especially when their version is the same). Other applications may use this lib as well. Or you may want to host many applications on different versions of symfony, then you may have separate directories for each separate version of symfony:

/home/user
  /symfony_1_2
  /symfony_1_4 

If you want to host many symfony apps on one account, it's better to modify above structure. The best solution is to create subdomains (or any other way to access the site from a different URL) but never put everything (entire application structure) into apache's document root directory (e.g. public_html).

symfony development plugins

Symfony framework comes bundled with many great plugins, they can be extremely useful for your projects' functionalities. But there are very few plugins that provide documentation functionalities. While developing a big project, it's really easy to get messed up with working on dozens of model classes, modules, actions, etc.

There are few plugins I would like to recommend:
  1. sfDoxygenPlugin
  2. sfApplicationMapPlugin
  3. sfDoctrineGraphvizPlugin
Each of them performs different tasks, each of them may be found very useful while designing and developing your sites.

sfDoxygenPlugin

This plugin is an alternative to sfPhpDocPlugin. Doxygen seems to be the best cross-platform documentation tool available, including lots of configuration features (one of them is easy UTF-8 rendering, in comparison to phpDocumentor which still lacks support for any encoding different than ISO-8859-1).

You only need to have doxygen installed, execute few tasks, modify the configuration and your code documentation is ready. Remember that a developer has to go back to the application code even few years after finishing a project. If no documentation is found then, applying even small changes is... quite difficult.

sfApplicationMapPlugin

The last two plugins are based on GraphViz, a wonderful graph rendering tool. The plugin is provided with only one task and is easier to use than sfDoxygenPlugin. After installing you only need to run one task and the application map is already generated.

What is this application map? It's a graph in which each element of the controller has it's own node. That is, the project-root node has child apllication nodes (e. g. backend, frontend), application nodes have child module nodes (including both admin generators and custom modules) and, finally, module nodes have child action nodes. Each node is labeled with the name of the project, application, module or action, depending on the node type. Each action is additionally provided with the documentation code. All this makes sfApplicationMapPlugin a very useful tool when you want to take a brief look at you application and you don't like browsing lots of files - the plugin does it for you and generates nice images. Take a look at the plugin readme to see example application maps(#1, #2).

sfDoctrineGraphvizPlugin

Finally, database schema has to be documented as well. Many developers tend to use good old FabForce's DBDesigner 4. But, as it usually happens, many tiny differences are made during development, time is money and updating your DBDesigner schema is definitely NOT money, therefore the schema image is not up-to-date...

Why using DBDesigner and spending your time on updating a file which won't be used in your project, when all the database schema information you need is present in schema.yml files? This is where the sfDoctrineGraphvizPlugin comes to help. Just like sfApplicationMapPlugin - just install, run one task and the images are generated for you.

Conclusion

During site development, a developer usually doesn't care about documenting the project. But if he leaves it for a long time, or when a new developer comes to replace him, understanding of a project becomes a big problem. Don't forget to provide a good value documentation for you projects, it takes very little time - and eventually saves a lot of it.