I recently continued to use my particular relationship that embeds some properties. This time I wanted to add some filters to my student request using the pre-generated filter used in the backend.

Here again, is our example:

N to N relationship with properties
N to N relationship with properties

And the pre-coded filter is here:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
{
}
}
[/php]

Here the filter already includes fields for every field on Student objects (id and name). It is a regular form, so you can override the predefined widgets and validator or even unset some of them. Let’s unset the id:

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure(){
unset($this[‘id’]);
}
}
[/php]

Now we can use our filter like this in our controller:
[php]
// app/frontend/module/student/actions/actions.class.php
class studentActions extends sfActions {
public function executeIndex(sfWebRequest $request) {
//create a default query
$query = Doctrine::getTable(‘Student’)->getQuery();

//obtain the filter values that could have been submited
$filterValues = $request->getParameter(‘student_filters’);
//create the StudentFormFilter using the values, and giving it the query
$this->formFilter = new StudentFormFilter($filterValues);
$this->formFilter->setQuery($query);
//if the form have been submited
if($filterValues){
//bind the values
$this->formFilter->bind($filterValues);
//if valid, specialise the request using the form
if($this->formFilter->isValid())
$query = $this->formFilter->getQuery();
}

//finally get the students
$this->students = $query->execute();
}
}
[/php]

This code could seem a little weird but it is finally all you need to know in order to make a basic use of the auto generated filters. Thus, your template can look like this:

[php]
<?php echo form_tag(url_for(‘student_index’)) ; ?>
<table>
<?php echo $formFilter ; ?>
</table>
<input type="submit"/>
<?php echo ‘</form>’ ?>
// …
<ul>
<?php foreach($students as $student): ?>
<li><?php echo $student->getName(); ?></li>
<?php endforeach; ?>
</ul>
[/php]

Ok, everything is working quite well. But, let’s getting our filter trickier. I want to add a filter that permits me to select students that have a specific enrolment date.

First of all, we need to add a widget and a validator:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
unset($this[‘id’]);

$this->setWidget(‘EnrolmentDate’, new sfWidgetFormFilterDate( array(
‘from_date’ => new sfWidgetFormDate(),
‘to_date’ => new sfWidgetFormDate(),
‘with_empty’ => false)));
$this->setValidator(‘EnrolmentDate’, new sfValidatorDateRange(array(
‘required’ => false,
‘from_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 00:00:00’)
),
‘to_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 23:59:59’)))));
}
}
[/php]

Here, our form will display properly, but the getQuery method used in the will simply ignore our new EnrolmentDate field.

Now the things are getting really uncommon. Symfony is not really built for this kind of behavior, and we will need to override some behavior in our StudentFormFilter to get thing to work. In order to do that I read the sfFormFilterDOctrine.class.php file and more specifically the doBuildQuery function where the request is built. Some of the things I’ll do are a bit ugly but it is the price to pay if we want not to spread our modifications into the core framework, thus I managed to keep everything in the StudentFormFilter.class.php file. This way our ugliness remains contained into a single file and does not impact the framework. So let’s go for it!

First of all we need to add the EnrolmentDate as a new field of the form, to do that we need to override the getFields function:
[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function getFields() {
return array_merge(parrent::getFields(), array(‘EnrolmentDate’=>’EnrolmentDate’));
}
}
[/php]

The very important thing here is to know that I added a field EnrolmentDate of type EnrolmentDate. It will be crucial later.

Also, the doBuildQuery method will get the Student table and check for the presence of the field EnrolmentDate. The workaround here is to make a stub table and override the getTable function in order to return this stub table.

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function getTable() {
return new StudentStubTable();
}
}

// my stub table
class StudentStubTable extends StudentTable{
public function hasField($field) {
if($field == ‘EnrolmentDate’)
return true;
else
return parent::hasField($field);
}
public function getFieldName($field){
if($field == ‘EnrolmentDate’)
return ‘EnrolmentDate’;
else
return parent::getFieldName($field);
}
}
[/php]

As you can see, two methods need to be overridden in my StudentStubTable class. Those two functions are used by the doBuildQuery function. Here i make the field EnrolmentDate exist and create his fieldName in the table is EnrolmentDate.

From here, all we need to know is that the doBuildQuery function will look for an addEnrolmentDateQuery function in our StudentFormFilter class. This function is called based on the type of the fields that I defined earlier in the getFields method. This call is made by the concatenation of ‘add’ + type of the field + ‘Query’.

So, let’s code the function and make it do our join with the Enrolment table.

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
// …

public function addEnrolmentDateQuery(Doctrine_Query $query, $field, $values) {
//add our join part
$query->leftJoin(sprintf(‘%s.Enrolment enrolment’,$query->getRootAlias()));

//set the where part depending on the value
if (isset($values[‘is_empty’]) && $values[‘is_empty’])
{
$query->addWhere(‘enrolment.EnrolmentDate IS NULL’);
}
else
{
if (null !== $values[‘from’] && null !== $values[‘to’])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values[‘from’]);
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values[‘to’]);
}
else if (null !== $values[‘from’])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values[‘from’]);
}
else if (null !== $values[‘to’])
{
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values[‘to’]);
}
}
}
}
[/php]

In this method, we retrieve the query as a parameter and we just have to build our method based on the values we receive. Everything here is about building a proper DQL query.

Now we have a nice filter that can filter on our property embedded into a relationship.

Here is the final code:

[php]
// lib/filter/doctrine/StudentFormFilter.class.php
class StudentFormFilter extends BaseStudentFormFilter
{
public function configure()
unset($this[‘id’]);

$this->setWidget(‘EnrolmentDate’, new sfWidgetFormFilterDate( array(
‘from_date’ => new sfWidgetFormDate(),
‘to_date’ => new sfWidgetFormDate(),
‘with_empty’ => false)));
$this->setValidator(‘EnrolmentDate’, new sfValidatorDateRange(array(
‘required’ => false,
‘from_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 00:00:00’)
),
‘to_date’ => new sfValidatorDateTime(array(
‘required’ => false,
‘datetime_output’ => ‘Y-m-d 23:59:59’)))));
}

public function addEnrolmentDateQuery(Doctrine_Query $query, $field, $values) {
//add our join part
$query->leftJoin(sprintf(‘%s.Enrolment enrolment’,$query->getRootAlias()));

//set the where part depending on the value
if (isset($values[‘is_empty’]) && $values[‘is_empty’])
{
$query->addWhere(‘enrolment.EnrolmentDate IS NULL’);
}
else
{
if (null !== $values[‘from’] && null !== $values[‘to’])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values[‘from’]);
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values[‘to’]);
}
else if (null !== $values[‘from’])
{
$query->andWhere(‘enrolment.EnrolmentDate >= ?’, $values[‘from’]);
}
else if (null !== $values[‘to’])
{
$query->andWhere(‘enrolment.EnrolmentDate <= ?’, $values[‘to’]);
}
}
}

public function getFields() {
return array_merge(parrent::getFields(), array(‘EnrolmentDate’=>’EnrolmentDate’));
}

public function getTable() {
return new StudentStubTable();
}
}

// my stub table
class StudentStubTable extends StudentTable{
public function hasField($field) {
if($field == ‘EnrolmentDate’)
return true;
else
return parent::hasField($field);
}
public function getFieldName($field){
if($field == ‘EnrolmentDate’)
return ‘EnrolmentDate’;
else
return parent::getFieldName($field);
}
}
[/php]

If you have any remarks about my implementation (I am sure you have) don’t hesitate to tell me about, I think this code can still be improved.

2 thoughts on “How to use filters on custom fields with symfony 1.4

  1. Hi Michel,

    I tried to use your snippet, but I couldn’t use it. I got a symfony constructor error each time when I wanted to use like this return new StudentStubTable();

    So I search for another solution. I found this:
    http://symfony-world.blogspot.com/2010/01/custom-admin-generator-filter-example.html

    I’m using the sfGuardUser plugin and I have many extended user accounts to this.
    One of them the Racer class.
    I wanted to use the sfGuardUser first_name and last_name on Racer’s backend filter. I could solve it by the above snippet:

    class RacerFormFilter extends BaseRacerFormFilter
    {
    public function configure()
    {
    $this->setWidget(‘first_name’, new sfWidgetFormFilterInput(array(‘with_empty’ => false)));
    $this->setWidget(‘middle_name’, new sfWidgetFormFilterInput(array(‘with_empty’ => false)));
    $this->setWidget(‘last_name’, new sfWidgetFormFilterInput(array(‘with_empty’ => false)));

    $this->setValidator(‘first_name’, new sfValidatorPass());
    $this->setValidator(‘last_name’, new sfValidatorPass());

    } // configure

    /**
    * add the firstname and lastname as a new field of the form, to do that we need to override the getFields function
    *
    * @see lib/filter/doctrine/base/BaseRacerFormFilter::getFields()
    */
    public function getFields()
    {
    return array_merge(parent::getFields(), array(‘first_name’ => ‘first_name’, ‘last_name’ => ‘last_name’));
    } // getFields

    /**
    * will handle the database request of firstname
    *
    * @param $query
    * @param $field
    * @param $value
    */
    public function addFirstNameColumnQuery($query, $field, $value)
    {
    Doctrine::getTable(‘Racer’)->applyFirstNameFilter($query, $value);
    } // addFirstNameColumnQuery

    /**
    * will handle the database request of lastname
    *
    * @param $query
    * @param $field
    * @param $value
    */
    public function addLastNameColumnQuery($query, $field, $value)
    {
    Doctrine::getTable(‘Racer’)->applyLastNameFilter($query, $value);
    } // addLastNameColumnQuery

    } // RacerUserTable Class

    class RacerTable extends Doctrine_Table
    {
    //..
    /**
    * adds a join between the Racer and the sfGuardUser tables and automatically creates the sfGuardUser object related to each Racer.
    *
    * @param $q
    */
    public function retrieveBackendUserlist(Doctrine_Query $q)
    {
    $rootAlias = $q->getRootAlias();

    $q->leftJoin($rootAlias.’.sfGuardUser u’);
    $q->leftJoin($rootAlias.’.Club c’);

    return $q;
    }

    /**
    * Applies the firstname attribute to a given query retrieving racers.
    *
    * @param Doctrine_Query $query – query to have first_name attribute applied.
    * @param string $value – part of the name
    */
    public function applyFirstNameFilter($query, $value)
    {
    if (!empty($value[‘text’])) {
    $query->where(“u.first_name LIKE ‘”.$value[‘text’].”‘”);
    }

    return $query;
    }

    /**
    * Applies the lastname attribute to a given query retrieving racers.
    *
    * @param Doctrine_Query $query – query to have last_name attribute applied.
    * @param string $value – part of the name
    */
    public function applyLastNameFilter($query, $value)
    {
    if (!empty($value[‘text’])) {
    $query->where(“u.last_name LIKE ‘”.$value[‘text’].”‘”);
    }

    return $query;
    }

    } // RacerTable

    to generator.yml
    generator:
    //..
    config:
    //..
    list:
    table_method: retrieveBackendUserlist
    display: [sfGuardUser, Club, sex, age]
    filter:
    display: [last_name, middle_name, first_name, club_id, sex, age]

  2. Hi, good post, in principle help me. But exists a function called addXXColumnQuery(),
    that do the work fine, and without the tricky part of the class. This post:
    http://oldforum.symfony-project.org/index.php/m/92136/
    tells an idea of how use that function, personally I used it and do the work well. The only thing that don’t work, is fill the custom value field, but I am using a dependence plugin, so probably there is the problem.

    Regards!

    Sorry for my english :s

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s