Yii2 search by foreign table field and total count - php

Im trying to search by a foreign table as follows:
2 tables:
people:
id
name
...
url:
id
peopleID
url
People.php model:
public function getUrls()
{
return $this->hasMany(Urls::className(), ['peopleID' => 'id'])->select(['url']);
}
PeopleSearch.php model:
...
$query->joinWith(['urls']);
...
$query
->andFilterWhere(['or',
['like', 'name', $this->name],
...
['like', 'url', $this->name]]
);
This works to search value entered in "name" field in several fields including foreign url one but in my view i enter a manual pagination by using something like:
$dataProvider->prepare();
if ($dataProvider->totalCount > 0)
echo Yii::t('app', 'Showing').": <b> ".($dataProvider->pagination->page*$dataProvider->pagination->pageSize+1)."-".($dataProvider->pagination->page*$dataProvider->pagination->pageSize+$dataProvider->count)."</b> ".Yii::t('app', 'of')." <b>".$dataProvider->totalCount."</b> ".Yii::t('app', 'items');
else echo Yii::t('app', 'No results found.');
echo LinkPager::widget(['pagination' => $dataProvider->pagination])
And $dataProvider->totalCount gives me the total amount of records from joined table but not the total records from people one. For instance if i have 2 records in people table and each one has 20 urls in "url" table index.php view shows "showing 1-2 of 40 items" instead of "showing 1-2 of 2 items"
Also LinkPager::widget shows a wrong number of total pages
Note that $dataProvider is passed from the controller to the view with
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
What could I do for pagination to perform the way i want?
Thank you in advance,

In People.php model my recommendation is to remove the ->select(['url']):
public function getUrls()
{
return $this->hasMany(Urls::className(), ['peopleID' => 'id']);
}
This way you can still manipulate those urls if needed.
In PeopleSearch.php model:
...
// $query->joinWith(['urls']);
// This line is the one that makes it so you get 20 results instead of 2, because you actually get one result for each url related to the people returned by the query.
$query->with(['urls']);
// This last line makes sure the model class populates the relation using only one query.
// Two queries will end up being executed to populate both the people and url models,
// however you will get the right amount for $dataProvider->totalCount.
...
if(strlen($this->url) > 0) {
$urlsPeopleIDs = \app\models\Url::find()
->select('peopleID')
->asArray()
->where(['like', 'url', $this->url])
->all();
$query->andWhere(['id' => $urlsPeopleIDs]);
}
// This way you will only filter by url when you receive a url string with lenght > 0.
// If you haven't already, you will need to create a public property called 'url' in your PeopleSearch.php model and add a 'string' or 'safe' rule so you can actually load it's value from post.

Related

Laravel : How to exclude duplicate records when inserting a group of data into mysql?

I am using Laravel 5.5,I want to exclude duplicate records when inserting a group of data into mysql.
For example,there is a table students,it has these fields:
id
name
gender
Now I will insert a group of data into students,if not mind duplicate records,I can do it like this:
public function insert()
{
$newStudents=[
['name'=>'Jim','gender'=>'boy'],
['name'=>'Lucy','gender'=>'girl'],
['name'=>'Jack','gender'=>'boy'],
['name'=>'Alice','gender'=>'girl']
];
DB::table('students')->insert($newStudents);
}
Now I don't want to insert duplicate records.(The duplicate is : both name and gender have the same values,not one field has the same value).
what should I do?
You could use the collection helper unique. See code below:
$newStudents=collect([
['name'=>'Jim','gender'=>'boy'],
['name'=>'Lucy','gender'=>'girl'],
['name'=>'Jack','gender'=>'boy'],
['name'=>'Alice','gender'=>'girl'],
['name'=>'Alice','gender'=>'girl']
])->unique(function ($student) {
return $student['name'].$student['gender'];
})->toArray();
DB::table('students')->insert($newStudents);
The above code will only insert unique records, even though there is a duplicate record there.
For more information, see here:https://laravel.com/docs/5.4/collections#method-unique
You could create an unique index for the name and gender in the database. However, then when you try to save them you will get a MySQL error I'm guessing. So you could use the unique validation with where.
Validator::make($student, [
'gender' => [
'required',
Rule::in(['boy', 'girl']),
],
'name' => Rule::unique('students')->where(function ($query) use ($student) {
$query->where('gender', $student['gender']);
})
]);
Then you can run your collection through and filter out the ones that aren't valid like in #pseudoanime's answer.

Laravel 4 Populate a Dropdown list with 2 Database Columns?

echo Form::select(
'campaign_user_id',
User::find(1)->profile->lists('first_name', 'id'),
Input::get('campaign_user_id', $campaign->user_id),
array(
"placeholder" => "US",
'class' => 'form-control',
'disabled' => 'disabled'
)
)
The code above is used to build a Dropdown selection list. It fetches a list of Users from the database. On line 3 you can see User::find(1)->profile->lists('first_name', 'id')
It is getting the first_name column, I need to somehow get first and last name columns though and combine them in this list. So that list value is still user ID and Full name is shown.
Any idea how to make this work with 2 DB columns first_name and last_name? Or another method to reach my end goal?
// In your model
class User extends Eloquent
{
public function getFullNameAttribute()
{
return $this->attributes['first_name'] . ' ' . $this->attributes['last_name'];
}
}
then grab like so:
User::find(1)->profile->lists('full_name', 'id');
OR
User::find(1)->profile->select(DB::raw('concat (first_name," ",last_name) as full_name,id'))->lists('full_name', 'id');
I prefer the first one :)

Yii DataProvider change the attributes of a column in each result

I'm using Yii's Dataprovider to output a bunch of users based on the column "points";
It works fine now but I have to add a feature so if the user is online, he gets an extra 300 points.
Say Jack has 100 points, Richmond has 300 points, However Jack is online, so Jack should rank higher than Richmond.
Here is my solution now:
$user=new Rank('search');
$user->unsetAttributes();
$user->category_id = $cid;
$dataProvider = $user->search();
$iterator = new CDataProviderIterator($dataProvider);
foreach($iterator as $data) {
//check if online ,update points
}
However, this CDataProviderIterator seems change my pagination directly to the last page and I can't even switch page anymore. What should I do?
Thank you very much!
Here is the listview:
$this->widget('zii.widgets.CListView', array(
'id'=>'userslist',
'dataProvider'=>$dataProvider,
'itemView'=>'_find',
'ajaxUpdate'=>false,
'template'=>'{items}<div class="clear"></div><div style="margin-right:10px;"><br /><br />{pager}</div>',
'pagerCssClass'=>'right',
'sortableAttributes'=>array(
// 'create_time'=>'Time',
),
));
Updated codes in Rank.php model
$criteria->with = array('user');
$criteria->select = '*, (IF(user.lastaction > CURRENT_TIMESTAMP() - 1800, points+300, points)) as real_points';
$criteria->order = 'real_points DESC';
However, it throws me error:
Active record "Rank" is trying to select an invalid column "(IF(user.lastaction > CURRENT_TIMESTAMP() - 1800". Note, the column must exist in the table or be an expression with alias.
CDataProviderIterator iterates every dataprovider value, and stops at the end. I don't know all about this classes, but think the reason is in some internal iterator, that stops at the end of dataprovider after your foreach.
Iterators are used when you need not load all data (for large amounts of data) but need to process each row.
To solve your problem, just process data in your view "_find". Add points there if online.
Or if you want place this logic only in the model (following MVC :) ), add method to your model:
public function getRealPoints() {
return ($this->online) ? ($this->points + 300) : $this->points;
}
And you can use $user->realPoints to get points according to user online status
update: To order your list by "realPoints" you need to get it in your SQL.
So use your code:
$user=new Rank('search');
$user->unsetAttributes();
$user->category_id = $cid;
$dataProvider = $user->search();
and modify $user->search() function, by adding:
$criteria->select = '*, (IF(online='1', points+300, points)) as real_points';
$criteria->order = 'real_points DESC';
where online and points - your table columns.

Yii relations using variables of the active model

I have two rows in one of my tables which look like:
id product_id target_product_id description type
1 206587 456 sdfgdfgdfg 0
2 456 206587 fgdgfhghfgfdsgfdghfghfsd 0
When viewing the model for the row with id 1 I wish to get the second row based on where the product_id and the target_product_id are inversed. So I made a relation of:
'linked_product_relation' => array(self::HAS_ONE, 'Accessory', '',
'on'=>'linked_product_relation.target_product_id = product_id
AND link_product_relation.product_id = target_product_id')
However, it seems to only ever return null. I have checked that link_product_relation links to the table, and I get no SQL error, just a null return. If I use the relation with only link_product_relation.product_id = product_id though I do actually get a response, but only the row I am currently looking at. I seem to be missing something.
How can I get my desired output?
Edit
When I add a function to replace the relation:
function getTwinned(){
$a=Accessory::model()->findByAttributes(array('target_product_id'=>$this->product_id, 'product_id'=>$this->target_product_id));
if($a===null)
return null;
else
return $a;
}
It works perfectly.
You did not specify a foreign key ('' in your code). Try something like this:
'linked' => array(self::BELONGS_TO, 'Accessory', array(
'target_product_id'=>'product_id',
'product_id' => 'target_product_id',
)),
For more information also read the manual on this topic here and here.

Generating Links Within a HTML Table Using Query Results. (Codeigniter)

I’m trying to display two columns in my table view, one being a title of the document, the next being a description of the document. I have a column in the particular table which I am selecting named “filename” that stores the name of the uploaded document which is associated with it’s title and description.
I’m curious as to how I would manage to display only the title and description, while setting the data contained in the “filename” column as the hyperlink value of the title? (Basically, I’m wanting them to be able to download a document once they click on it’s name)
I’m fairly certain that I can pull this off by manually by skipping the table generator and doing a “foreach” in the view to print out all the data from the resultset, but I’m open to suggestions, as this would make for sloppy code. Here’s a snippet of my controller below.
<?php
class blah extends CI_Controller {
public function troubleshooting() {
$this->load->library('pagination');
$this->load->library('table');
$config['base_url'] = 'http://somewebsite.com/troubleshooting';
$config['total_rows'] = $this->db->get('document')->num_rows();
$config['per_page'] = 10;
$config['num_links'] = 10;
$this->pagination->initialize($config);
$data['records'] = $this->db->get('document', $config['per_page'],$this->uri->segment(3));
$this->db->select('doc_title, filename, description, category_id, product_id');
$this->db->where('category_id = 1');
$this->db->where('product_id = 1');
$this->db->order_by('doc_title', 'asc');
$this->load->view('blah/troubleshooting.php', $data);
}
}
You can create the link in your query with a select->(CONCAT(...), FALSE) query.
http://codeigniter.com/forums/viewthread/105687/#531878
** UPDATE **
Well, your table helper is going to receive an array - and your db query is going to return an array. So, you have to create your query in a way that will create your array the way you need it for the table helper.
You need something like this:
$records = [
'doc_title' => 'My Title',
'filename' => 'filename.php',
'description' => 'My description text here.',
'category_id' => 5,
'product_id' => 56
]
Your query might look like this:
$this->db->select('doc_title');
$this->db->select('CONCAT("<a href=path/to/'.filename.'>".'filename'."</a>")', FALSE);
$this->db->select('description, category_id, product_id');
$this->db->where('category_id = 1');
$this->db->where('product_id = 1');
$this->db->order_by('doc_title', 'asc');
$records = $this->db->get('document', $config['per_page'],$this->uri->segment(3));
The trick is your CONCAT function will have some 'quote' issues. Without actually testing this myself, I'm not sure I've got the quotes right. Take a look at some docs there and that should help: http://dev.mysql.com/doc/refman/5.0/en/string-functions.html#function_concat

Categories