There is a serch form on the mainpage of a realestate agency. The data about objects is stored in the table "realty" that uses relations. For example, there are related tables category (residential, commercial, land plots), deal (buy, sell, rent), object_type (apartment, house, office).
Then different categories have different properties and and there are three bootstrap tabs in the search form: residential, commercial, land plots. Under each tab there are selects and input fields that are specific for the choosen tab.
In the most cases, the examples of using Search model are given within a gridview.
Is it possible to adapt Search model logic so that it could return the array of results from the table 'realty' based on the values indicated in the search form on the mainpage ?
Yes, of course you can. you have several options:
Solution 1 - worst solution but the answer to your question
modify the search function of the model (or create a new function). The search functions usually looks like this
public function search($params)
{
$query = Bookmark::find()->where('status <> "deleted"');
$dataProvider = new ActiveDataProvider([
'query' => $query,
'pagination' => [
'pageSize' => Yii::$app->session->get(get_parent_class($this) . 'Pagination'),
],
]);
if (!($this->load($params) && $this->validate())) {
return $dataProvider;
}
$query->andFilterWhere([
'status' => $this->status,
'id' => $this->id,
'reminder' => $this->reminder,
'order' => $this->order,
'update_time' => $this->update_time,
'update_by' => $this->update_by,
'create_time' => $this->create_time,
'create_by' => Yii::$app->user->id,
]);
$query->andFilterWhere(['like', 'name', $this->name])
->andFilterWhere(['like', 'url', $this->url]);
return $dataProvider;
}
You can change it to something like
public function search($params)
{
$query = Bookmark::find()->where('status <> "deleted"');
if (!($this->load($params) && $this->validate())) {
THROW AN ERROR SOMEHOW HERE
}
$query->andFilterWhere([
'status' => $this->status,
'id' => $this->id,
'reminder' => $this->reminder,
'order' => $this->order,
'update_time' => $this->update_time,
'update_by' => $this->update_by,
'create_time' => $this->create_time,
'create_by' => Yii::$app->user->id,
]);
$query->andFilterWhere(['like', 'name', $this->name])
->andFilterWhere(['like', 'url', $this->url]);
return $query->all();
}
however this will return to you all the records because ActiveDataProvider takes care of the pagination based on the query given.
Solution 2, a better solution
read the first example here http://www.yiiframework.com/doc-2.0/yii-data-activedataprovider.html . You can call ->getModels() on an ActiveDataProvider to get the actual records. No changes needed to the search function. Do whatever you want with the array.
Solution 3 and what I use all the time
Use ActiveDataProvider with a ListView. The list view allows you to create the list of records however you want, it does not have to be a table. I personally do this in many places and it works quite well. I sometimes transform arrays to an ArrayDataProvider just to use it. More about data providers here: http://www.yiiframework.com/doc-2.0/guide-output-data-providers.html
Related
'm working on a global site search ( autocomplete ), right now it have almost all entries ( that have uri defined ) for results, it uses js fetch and Craft element api in a json file that filter results based on a query string. Works great!
I was wondering if it's possible to add to the query categories too, i don't know if this is possible buy maybe it's a way to merge them, here is my json api code, thanks!
'search.json' => function() {
$qParam = Craft::$app->request->getParam('q');
return [
'elementType' => Entry::class,
'paginate' => false,
'criteria' => [
'uri'=> ':notempty:',
'search' => $qParam,
'orderBy' => 'score',
'limit' => 20,
],
'cache' => false,
'transformer' => function(Entry $item) {
return [
'title' => $item->title,
'url' => $item->url,
];
},
];
}
I have products table and productlines table. Product has relationship with the productlines. I want to have a single search box and search the fields from product table and fields from productlines table.
My ProductSearch Model
public function search($params)
{
$query = Product::find()->where(['product_id' => $this->getProductID()]);
// add conditions that should always apply here
$query->joinWith('productlines');
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'product_id' => $this->product_id,
'product_region' => $this->product_region,
'product_created' => $this->product_created,
'product_lastchecked' => $this->product_lastchecked,
'sdsref_id' => $this->sdsref_id,
]);
// var_dump($this->getProductID()); exit();
$query->andFilterWhere(['like', 'product_name', $this->product_name])
->andFilterWhere(['like','product_id', $this->product_id])
->orFilterWhere(['like', 'product_catalog', $this->code])
->andFilterWhere(['=', 'product_aka', $this->product_aka])
->orFilterWhere(['like', 'internal_code' , $this->code]);
return $dataProvider;
}
When I do this I get the error:
SQLSTATE[23000]: Integrity constraint violation: 1052 Column 'product_id' in where clause is ambiguous
The SQL being executed was: SELECT COUNT(*) FROM `sim_product` LEFT JOIN `sim_productlines` ON `sim_product`.`product_id` = `sim_productlines`.`product_id` WHERE ((`product_id` IN ('2', '3')) OR (`product_catalog` LIKE '%A%')) OR (`internal_code` LIKE '%A%')
Can anyone assist me where am I going wrong and what can be the possible solution.
Thanks
Yii2 allows you to prefix column names within ActiveQuery such as:
$query->andFilterWhere(['like', 'product.product_name', $this->product_name])
->andFilterWhere(['like','productline.product_id', $this->product_id])
->orFilterWhere(['like', 'productline.product_catalog', $this->code])
->andFilterWhere(['=', 'product.product_aka', $this->product_aka])
->orFilterWhere(['like', 'product.internal_code' , $this->code]);
Source: http://www.yiiframework.com/doc-2.0/guide-db-active-record.html#relational-data
See the section "Joining with relations"
I query my model like so
$projects = Project::where('status', '!=', 'Completed')->get();
This will return me something like this
#attributes: array:16 [▼
"id" => "7"
"user_id" => "32"
"contactEmail" => "sdfsdf#dsfsdf.com"
"deploymentDate" => "2016-07-29"
"status" => "Starting"
"deleted_at" => null
"created_at" => "2016-07-12 14:12:32"
"updated_at" => "2016-07-15 09:47:34"
]
I then pass this model to generate an Excel file
Excel::create('projects', function($excel) use($projects) {
$excel->sheet('Sheet 1', function($sheet) use($projects) {
$sheet->fromArray($projects);
});
})->export('xls');
Everything works fine, and the Excel is generated. One problem I have though is that the excel file shows user_id being 32. Instead of displaying the user_id, I want to display the userName which is
part of my Users table.
How can I join these two tables to get the name instead of the id? All relationships are set up correctly.
Thanks
Try this one,
$projects = Project::select('product.*','users.name AS user_name')
->leftjoin('users','product.user_id','=','users.id')
->where('status', '!=', 'Completed')->get();
Using this code you will be able to get user_name for more relationship please refer this
I'm using the CakePHP Search Plugin, and there's something I can't get to work. I would like to have a search field (let's call it age) where a user can input a value (like 24, for example) and get as a result all the elements with a value in the age field of the table who have age 24 or higher. How can I do that?
So far I can perform normal searches, like by name or by an exact value (if you want age 24 exactly then it works fine).
Here's the code related with the Search Plugin that I have so far:
- model:
class Person extends AppModel{
var $name = 'Person';
public $actsAs = array('Searchable');
public $filterArgs = array(
array('name' => 'name', 'type' => 'like'),
array('name' => 'age', 'type' => 'value'),
);
}
- controller:
class PersonsController extends AppController {
var $name = 'Persons';
var $components = array('Search.Prg');
public $presetVars = array(
array('field' => 'name', 'type' => 'value'),
array('field' => 'age', 'type' => 'value'),
);
function index() {
$this->set('persons', $this->Person->find('all'));
}
/* ---------------- SEARCH PLUGIN --------------*/
public function find() {
$this->Prg->commonProcess();
$this->paginate['conditions'] = this->Game->parseCriteria($this->passedArgs);
$this->set('persons', $this->paginate());
}
}
- find.ctp:
//apart from the code below, I have all the code to show the results
echo $form->create('Game', array(
'url' => array_merge(array('action' => 'find'), $this->params['pass'])
));
echo $form->input('name', array('div' => false));
echo $form->input('age', array('div' => false));
echo $form->submit(__('Search', true), array('div' => false));
echo $form->end();
To sum up: with the code above, I can perfomr a search of an exact value for the age field. How do I change that to perform a search of 'age >= value entered'?
THank you so much in advance!
So this is how I solved it (or at least it seems to work with all the test I've done so far):
Changed the 'age' line by the following line in the filterArgs array in the model:
array('name' => 'age', 'type' => 'query', 'method' => 'findByAge', 'field' => 'Person.age'),
Added the following method in the same model:
public function findByAge($data = array()) {
$query = array(
'Person.age >=' => $data['age'],
);
return $query;
}
That's it! With the 'query' type, any complex query can be added to the main query using a method. Thanks to satyrwilder for pointing in the right direction!
From your presetVars -
array('field' => 'age', 'type' => 'value'),
-- filters down to your find() method from your commonProcess() method, which I presume constructs a generalized find like conditions=>array($key=>$val)?
Cake sees array key=>val pair assignments as an indication to generate a "WHERE $key=$val" condition.
To do a fuzzier pull, wherever you construct that generic find() - add a method or something just to roll those two into
'conditions'=>array("name LIKE '%{$name}%'", "{$age}>={$val}")
or whatever you've got going on. String the arguments together for the desired effect on your query and I'm sure you can extrapolate further ways to abstract that query, if needed.
If you need a more complex solution, Cake provides elegant built-in, extremely powerful subquery generation. It seems like you're working awfully hard to achieve a lot of built-in functionality, but then again I don't know how complicated your situation is.
http://book.cakephp.org/view/1030/Complex-Find-Conditions
HTH
I know this has been answered but there is another method that may be more suitable to this situation.
From the documentation
'expression' type: useful if you want to add condition that will generate by some method, and condition field contain several parameter like in previos sample used for 'range'. Field here contains 'Article.views BETWEEN ? AND ?' and Article::makeRangeCondition returns array of two values.
Full Article Here
I had to accomplish the same thing you did but with 5 different fields. I did not want to have to write a query for each one. Here is how I accomplished the same thing with the expression type. This is in the filterArgs array.
array(
'name'=>'bathrooms',
'type'=>'expression',
'method'=>'returnNumber',
'field'=>'House.bathrooms >= ?'
)
Then I created this function in the model that simply returns the number the user submitted. When called, returnNumber will be passed two arrays.
Looks something like this.
Array
(
[0] => Array
(
[bathrooms] => 6
[bedrooms] => 5
[acres] => 12
[city] => Greenville
[year] => 2009
)
[1] => Array
(
[name] => bathrooms
[type] => expression
[method] => returnNumber
[field] => House.bathrooms >= ?
)
)
Now for the actual function. You can use the debug statement to see what you are actually passing the function.
function returnNumber($arg, $name){
// debug(func_get_args());
// exit;
return $arg[$name['name']];
}
I have a CGridView widget for Lesson model
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'lesson-grid',
'dataProvider'=>$model->search(),
'filter'=>$model,
... and Lesson has relation to the User model:
'user' => array(self::BELONGS_TO, 'User', 'user_id'),
... and the CGridView has a column with user's lastname from the BELONGS_TO model described above
'columns'=>array(
...
array(
'name' => 'user',
'header'=>'Teacher',
'value' => '$data->user->lastname',
),
So, I can't symply search with CGridView in this column, but I need it.
How to search in '$data->user->secondname' with CGridView?
I think that I should extend search method in Lesson model, but how?
Now it looks like this:
public function search()
{
// Warning: Please modify the following code to remove attributes that
// should not be searched.
$criteria=new CDbCriteria;
$criteria->compare('id',$this->id);
$criteria->compare('student',$this->student,true);
$criteria->compare('comment',$this->comment,true);
return new CActiveDataProvider(get_class($this), array(
'criteria'=>$criteria,
));
}
This should work, add it to your search criteria in the search() method:
$criteria->with[]='user';
$criteria->addSearchCondition("user.secondname",$this->user_id);
This is what I do though:
if(!intval($this->user_id) && is_string($this->user_id) && strlen($this->user_id) > 0) {
$criteria->with[]='user';
$criteria->addSearchCondition("user.secondname",$this->user_id);
} else
$criteria->compare('t.user_id',$this->user_id);
And here is the CGridView definition:
'columns'=>array(
...
array(
'name' => 'user_id',
'header'=>'User',
'sortable'=>false, // since it would still be sorting based on ID
// 'value' => '$data->user->lastname', // basic version
'value'=>'CHtml::link((isset($data->user))?$data->user->username:$data->user_id,array("user/view","id"=>$data->user_id))', // link version
),
It's a fun little trick: if the search term is a string and NOT an intval(), it searches for the user by their secondname, via the "user" relation. But if you enter a user_id, it will find the user by their user_id - the default search() functionality.
NOTE: This will enable filtering, but it will still sort based on ID. You will need to implement something additional to get the sorting to work.
There are surely other ways to do this, but this is how I do it. I suspect there is a "right" to do this using the relation, but my technique works solid.