Filtering data (the crazy way)
Let’s be honest: usual way of searching is crap.
Nobody wants a bunch of fields like id, name, email and so on. Actually all the users want is this.
Yeah, one field. And maybe some extra features, like in:spam (gmail webinterface).
And you know what? This can be done extremely easy using named scopes.
First of all, we create ActiveQuery class for our scopes. For those who missed that - yes, scopes are in AQ now, not in AR.
Let’s do it:
<?php
namespace app\models;
class ActiveQuery extends \yii\db\ActiveQuery
{
public $filterIntercepted = false; // kinda flag showing that we've found a match
public function filtered($q)
{
$model = $this->modelClass;
if ($q) {
if ($q == '#disabled') {
// it's like google's in:spam query
$this->andWhere([$model::tableName() . '.is_disabled' => 1]);
$this->filterIntercepted = true;
} elseif ($q == '#enabled') {
$this->andWhere([$model::tableName() . '.is_disabled' => 0]);
$this->filterIntercepted = true;
} elseif (preg_match('/^[a-f0-9]{13}$/', $q)) {
// поиск по ID
$this->andWhere($model::tableName() . '.id = :q', [':q' => $q]);
$this->filterIntercepted = true;
}
}
return $this;
}
// some extra scopes
public function disabled()
{
$model = $this->modelClass;
$this->andWhere([$model::tableName() . '.is_disabled' => 1]);
return $this;
}
}
(I’m using $model::tableName() to prevent possible ‘field is ambigous’ error in case of joining relations)
Ok, so here I’ve created “filtered” scope that takes one parameter $q and tests it against some conditions.
$q looks like ID?.. If yes, then I set filterIntercepted flag to denote that filter is already ‘working’.
It doesn’t make much sense until I create scopes for actual models:
<?php
namespace app\models;
class ClientQuery extends \app\models\ActiveQuery
{
public function filtered($q)
{
$query = parent::filtered($q); // here we test if $q is matching some basic conditions
if (!$this->filterIntercepted) {
// no match, let's test for something else
$model = $this->modelClass;
if (preg_match('/^\d+$/', $q)) {
// looks like phone number?
$query->andWhere([$model::tableName() . '.phone' => $q]);
} elseif (strpos($q, '@') !== false) {
// looks like email?
$query->andWhere([$model::tableName() . '.email' => $q]);
} else {
// maybe it's a name
$query->andWhere($model::tableName() . '.name LIKE :q', [':q' => '%' . $q . '%']);
}
}
return $query;
}
}
Now we can use it in our actionIndex for example:
public function actionIndex()
{
$items = Client::find()
->joinWith('something') // remember the disambiguation?
->filtered(Yii::$app->request->getQueryParam('q'))
->all();
...
}
Bingo.
PS. Guys, srsly, this is crazy, so use it at your own risk.