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.

7 comments:

  1. Wow, that's it !!!
    This helped me make my admin-filters so much more usable, the admin-users will kiss my feet from now on ...

    Thank You very much

    ReplyDelete
  2. Great Job !!!
    That was the final solution to my implementation of a filter of kind "Group is managed by" with choices "Mister_A, Mister_B, Mister_C, Unmanaged". In that case, I didn't want to have all the users shown, but only those ones, that are currently managing a group. Filtering the "Unmanaged" was an unsolved problem too, until I found that example/tutorial.
    Thanks a lot, You made my day shiny and bright.

    ReplyDelete
  3. Thank you :)
    I must ask makers of sf, why do we have to write so much bs to add 1 field to filter form?
    It's so frustrating! I had to change one field from TEXT type to SELECT, and had to write all this? Come on!
    Too much magic is done behind the scene, undocumented.

    Thanks again! :)

    ReplyDelete
  4. you might have used the sfValidatorChoice validator as follows:

    new sfValidatorChoice(array('required' => false, 'choices' => array('', 1, 0)))

    ReplyDelete
  5. 2012 an still this post is very useful :) dzieki!

    ReplyDelete
    Replies
    1. It's 2015 now and some people still have to deal with legacy code written in sf1 ;)

      Delete