Tuesday, January 4, 2011

Sort By Foreign Key or Custom Column in Symfony Admin Generator

This solution is works with Doctrine ORM.
  1. Put this to the backend module's actions.class.php
    protected function addSortQuery($query)
      {
        $rootAlias = $query->getRootAlias();
        if (array(null, null) == ($sort = $this->getSort()))
        {
          return;
        }
        $s = $sort[0];
        $fields = $this->varHolder->get('configuration')->getFieldsDefault();
        if ($fields != null)
        {
          $field = $fields[$s];
          if ($field != null)
          {
            if (isset($field['sortBy']))
            {
              $criterion = $field['sortBy'];
              if ($criterion != null)
              {
                $s = $criterion;
              }
            }
          }
        }
        
        if (isset($field['noRootAlias']) && $field['noRootAlias'] == true)
        {
          $query->addOrderBy($s . ' ' . $sort[1]);
        }
        else
        {
          $query->addOrderBy($rootAlias.'.'.$s . ' ' . $sort[1]);
        }
      }
    
  2. Let's see first the foreign key sorting with an example. If you want to show the related object's name instead of its id, you must use the relation name (in this case 'Group'). To enable sorting, add the sortBy parameter to the field.
    config:
          ...
          fields:
            name:    { label: Name }
            Group:   { label: Group, sortBy: Group.name }  
          list:
            title:   Users
            display: [id, name, Group]
    
    That's all, the sorting now works, but the column's name is not clickable yet. You have to change the generator's template. As I use the sfAdminThemejRollerPlugin, I will do this with that.
  3. Open the \ plugins \ sfAdminThemejRollerPlugin \ data \ generator \ sfDoctrineModule \ jroller \ template \ templates \ _list_th_tabular.php file and change the 4th row from this:
    <?php if ($field->isReal()): ?>
    to this:
    <?php if ($field->isReal() || $field->getConfig('sortBy')): ?>
    Now the foreign key sorting must work.
  4. Let's see the custom column sorting with an example. The data you will want to see in the custom column is somewhere in the database. You have to add your custom column to the original sql statement. Use joins, relations or simply subqueries. You can overwrite the sql in backend module's actions.class.php:
    protected function buildQuery()
      {
         $query = parent::buildQuery();
         // do what ever you like with the query    
         // use the die($query->getSqlQuery()); row to see the original sql statement
         return $query->addSelect('*, (SELECT foo FROM bar) as foobarcolumn');
      }
    As you can see I didn't change the query output, I only add an extra column to the query.
  5. Now we have to add this column to the generator.yml...
    config:
          ...
          fields:
            name:    { label: Name }
            Group:   { label: Group, sortBy: Group.name }
            foobar:  { label: FooBar, sortBy: foobarcolumn, noRootAlias: true }
          list:
            title:   Users
            display: [id, name, Group, foobar]
    
I hope this helps. The noRootAlias param is created by me, perhaps you won't need it or you can avoid using it. If you have an easier/better solution, please tell me!

5 comments:

  1. Great post man! But you must override the isValidSortColumn method, otherwise the value will not be recorded in the session.

    Something like:
    protected function isValidSortColumn($column)
    {
    return array_key_exists($column, $this->varHolder->get('configuration')->getFieldsDefault());
    }

    ReplyDelete
  2. Wow, that's true, I've absolutely forgot it. Thank you so much!

    ReplyDelete
  3. I think you also should override this:

    action.class.php:

    protected function isValidSortColumn($column)
    {
    return Doctrine_Core::getTable('Video')->hasColumn($column);
    }

    in the template:
    generator/sfDoctrineModule/THEME_NAME/parts/sortingAction.php

    to return `true` in case of "virtual" columns

    ReplyDelete
  4. oh... haven't noticed the comment of Eduardo :) nevermind :)

    ReplyDelete