Yii 1.1: Using filters with CGridView and CArrayDataProvider

31 followers

Using filters on CGridView with CActiveDataProvider is easy, but with CArrayDataProvider it is a bit tricky.

To use the filters you have to create a separate model class. I used some code from the following forum topic: link

Note: Normally, when you have an array of CModels, you should use CActiveDataProvider instead!

Here is some example code:

Model

Copy and include this class into your application.

<?php
 
/**
 * Filterform to use filters in combination with CArrayDataProvider and CGridView
 * @see http://www.yiiframework.com/wiki/232/using-filters-with-cgridview-and-carraydataprovider/
 */
class FiltersForm extends CFormModel
{
    /**
     * @var array filters, key => filter string
     */
    public $filters = array();
 
    /**
     * Override magic getter for filters
     * @param string $name
     */
    public function __get($name)
    {
        if (!array_key_exists($name, $this->filters)) {
            $this->filters[$name] = '';
        }
        return $this->filters[$name];
    }
 
    /**
     * Override magic setter for filters
     * @param string $name
     * @param mixed $value
     */
    public function __set($name, $value)
    {
        $this->filters[$name] = $value;
    }
 
    /**
     * Filter input array by key value pairs
     * @param array $data rawData
     * @return array filtered data array
     */
    public function filter(array $data)
    {
        foreach ($data AS $rowIndex => $row) {
            foreach ($this->filters AS $key => $searchValue) {
                if (!is_null($searchValue) AND $searchValue !== '') {
                    $compareValue = null;
 
                    if ($row instanceof CModel) {
                        if (isset($row->$key) == false) {
                            throw new CException("Property " . get_class($row) . "::{$key} does not exist!");
                        }
                        $compareValue = $row->$key;
                    } elseif (is_array($row)) {
                        if (!array_key_exists($key, $row)) {
                            throw new CException("Key {$key} does not exist in array!");
                        }
                        $compareValue = $row[$key];
                    } else {
                        throw new CException("Data in CArrayDataProvider must be an array of arrays or an array of CModels!");
                    }
 
                    if (stripos($compareValue, $searchValue) === false) {
                        unset($data[$rowIndex]);
                    }
                }
            }
        }
        return $data;
    }
 
}

(By @KonApaz) If you want to apply full comparisons (>, <, >=, <=, =) use this

if (substr($searchValue, 0, 2) == '<=') {
    if ($compareValue > substr($searchValue, 2)) {
        unset($data[$rowIndex]);
    }
} else if (substr($searchValue, 0, 2) == '>=') {
    if ($compareValue < substr($searchValue, 2)) {
        unset($data[$rowIndex]);
    }
} else if ($searchValue[0] == '<') {
    if ($compareValue >= substr($searchValue, 1)) {
        unset($data[$rowIndex]);
    }
} else if ($searchValue[0] == '>') {
    if ($compareValue <= substr($searchValue, 1)) {
        unset($data[$rowIndex]);
    }
} else if (stripos($compareValue, $searchValue) === false) {
    unset($data[$rowIndex]);
}

Controller

// Create filter model and set properties
$filtersForm=new FiltersForm;
if (isset($_GET['FiltersForm']))
    $filtersForm->filters=$_GET['FiltersForm'];
 
// Get rawData and create dataProvider
$rawData=User::model()->findAll();
$filteredData=$filtersForm->filter($rawData);
$dataProvider=new CArrayDataProvider($filteredData);
 
// Render
$this->render('index', array(
    'filtersForm' => $filtersForm,
    'dataProvider' => $dataProvider,
));

View

$columns = array(
    array(
        'header'=>CHtml::encode('Name'),
        'name'=>'username',
    ),
    array(
        'header'=>CHtml::encode('Organisation'),
        'name'=>'organisation',
    ),
);
 
$this->widget('zii.widgets.grid.CGridView', array(
    'id'=>'area-grid',
    'dataProvider'=>$dataProvider,
    'columns'=>$columns,
    'filter'=>$filtersForm,
));

Total 20 comments

#16877 report it
marcovtwout at 2014/04/07 03:30am
Re: i would also add strong to the found objects

Please, don't do it like that. Add highlighting at output time, not at filter time.

If you really want to add highlighting while processing the filter, at least store the results in a different array. And don't forget CHtml::encode..

#16859 report it
haimwd at 2014/04/04 11:55am
i would also add strong to the found objects

add this code to make the found string be bold:

if (stripos($compareValue, $searchValue) === false) {
                        unset($data[$rowIndex]);
                    } else {
                        $data[$rowIndex][$key] = str_ireplace($searchValue,'<strong>'.$searchValue.'</strong>',$data[$rowIndex][$key]);
                    }
#15276 report it
marcovtwout at 2013/10/22 09:05am
Re: RE: Re: RE: Re: is_null

FiltersForm is currently not doing any validation, so you cannot apply any rules. Also, null is a perfectly valid value, it does not have to be rejected.

One possible use case where someone edits your controller action:

if (isset($_GET['FiltersForm']))
    $filtersForm->filters=$_GET['FiltersForm'];
 
// programmer Bob making sure that adminNote cannot be freely searched
if (/*not admin*/)
    $filtersForm->filters['adminNote'] = null;

Ofcourse I agree setting and validating rules is the recommended way of validating search values.

This discussion is becoming too long for the small point I was trying to make, so I'm gonna leave it at this. Conclusion: defensive programming..

#15275 report it
KonApaz at 2013/10/22 08:58am
What validators ? (#15274)

According to models (as usually)

the validator for searching is array('attr1, attr2, attr3', 'safe', 'on' => 'search'),

so no other special validator is required (in most cases)

#15274 report it
KonApaz at 2013/10/22 08:48am
RE: Re: RE: Re: is_null
foreach ($this->filters AS $key => $searchValue)

protects for null $searchValue

how to inject 'null' value without $key ?

form[attr1] = value1 form[attr2] = value1 if (form[attr3] = value1;) is missing the above foreach not includes the attr3 in the iterator. if form[attr3] has no-value will be empty string '' not null

So do you know how can pass null value (not 'null' string) in this case ?

Also if it nessesar to check the values, using rules and validate method are the suitable way.

#15273 report it
marcovtwout at 2013/10/22 08:06am
Re: RE: Re: is_null

When normally submitted through the html form, yes. But what happens if the value is arbitratily set to null? (for example, a programmer trying to override a certain search value). All results will be filtered. So leave it in there, and remember http://en.wikipedia.org/wiki/Defensive_programming

#15272 report it
KonApaz at 2013/10/22 07:55am
RE: Re: is_null

As far as i know (in this part of the code) the $searchValue is always a string (empty or not) So I thing the "is_null" could be ommited

#15271 report it
marcovtwout at 2013/10/22 07:38am
Re: is_null

It's not unexpected that someone would set a search value as null (as in, ignore), so I would definitely leave it in.

#15270 report it
yugene at 2013/10/22 07:25am
@KonApaz

is_null was a precaution not to miss anything, so, yes, !== '' should work fine alone. Actually, I started with this check only first in my code, but then added is_null check.

Comparison -- yes, your first variant was a correct one as well :)

btw, don't know why, but new order of vars is easier for me to catch right away to understand if comparison is built correctly.

#15269 report it
KonApaz at 2013/10/22 07:00am
RE: update #8

In the updated line

!is_null($searchValue) $searchValue !== '')

I agree with $searchValue !== '' instead empty function http://php.net/manual/en/function.empty.php (empty returns false for the number 0)

But, Is there case that is_null($searchValue) return true in this code ?

I have never seen such a case or I can't remember, dο you?

Thanks :)

#15268 report it
KonApaz at 2013/10/22 06:51am
RE: 1560

Hi yugene

In my first update I have the correct inequality http://www.yiiframework.com/wiki/revision/?id=232&r1=5

You have update the same comparison that I wrote in the first time, reversing the order of variables. (Thanks!)

For example substr($searchValue, 2) < $compareValue and $compareValue > substr($searchValue, 2) Is the same thing.

I don't remember why I had agreed with http://www.yiiframework.com/wiki/revision/?id=232&r1=6

Let explain the logic, is a little confusing for a lot of programmers.

if (substr($searchValue, 0, 2) == '<=') {
   if (substr($searchValue, 2) < $compareValue) {
       unset($data[$rowIndex]);
   }
} ...

If: the comparison in text(or whatever) is <=50 (we want the rows of which the specific column is equal or less than 50)

Then: remove the row that ($compareValue > 50 or 50 < $compareValue) that means rows of which the specific column is greater than 50.

So the http://www.yiiframework.com/wiki/revision/?id=232&r1=6 or the currently updated wiki are the correct!

#15260 report it
yugene at 2013/10/22 02:35am
@KonApaz

Full comparison is fixed now - there still were some issues with it. I suppose it was just a typo appeared moving the code from your project - in order of variables names at the second level conditions.

#15256 report it
KonApaz at 2013/10/21 12:51pm
RE: > < >= <= comparisons

Hi marcovtwout you are right!

the direction of inequality were opposed http://www.yiiframework.com/wiki/revision/?id=232&r1=6

I changed it on my project but I forgot to update the wiki :)

#15164 report it
marcovtwout at 2013/10/14 05:52am
Re: change search criteria

If you want to do more advanced comparisons, you could expand the comparison code, for example like this:

if ($searchValue[0] == '<') {
    // implement
} else if ($searchValue[0] == '>') {
    // implement
} else if (stripos($compareValue, $searchValue) === false) {
    unset($data[$rowIndex]);
}
#15079 report it
Sharon Lavie at 2013/10/05 05:15am
works!!

thanks.

#15064 report it
Andre Lopez at 2013/10/03 02:58pm
change search criteria

Hi, How could search by criteria: greater than, less than, or just compare dates "from_date, to_date"

thanks in advance, Regards.

#14862 report it
marcovtwout at 2013/09/16 11:40am
Updated wiki article

Updated the code with info from xtremagix. Indeed, it was only working with array of arrays, not array of CModels.

Note: Normally, when you have an array of CModels, you should use CActiveDataProvider instead!

#14062 report it
Rajeev R at 2013/07/16 06:15am
Worked \m/

Thanks..

#13033 report it
Tikisman at 2013/04/29 06:16pm
Thanks

Awesome post :D

#12864 report it
marcovtwout at 2013/04/16 12:13pm
Re: Little fix to work with comparison operators

Blacklisting possible user input and using eval(), damn scary indeed!

Why don't make a more rigid and secure code using switch with cases for your comparison operators, at the same time teaching readers how to do this properly?

Leave a comment

Please to leave your comment.

Write new article