Laravel count column of related model - laravel

I want to count the number of upvotes and downvotes from ScripRating for a certain Script.
Script.php:
public function ratings()
{
return $this->hasMany('ScriptRating');
}
ScriptRating.php:
public function script()
{
return $this->belongsTo('Script');
}
The script_rating database table:
id (primary, increments)
script_id(integer)
rating(integer) <-- Can be either 1 (upvote) or -1 (downvote)
To retrieve a script and display the ratings:
$script = Script::where('title', '=', $title)->get();
{{ $script->ratings }}
This works fine, it returns an array: [{"id":1,"script_id":1,"rating":1}]. But at this point I'm stuck. How could I count the total upvotes and downvotes for a certain script?
I also have one more small question what I'm finding confusing. This does the same as the code above:
$script = Script::where('title', '=', $title)->with('ratings')->get();
{{ $script->ratings }}
What is the difference between these two methods and which one should I use?
Thanks in advance!
Edit
I made three scopes:
public function scopeTotalRating($query, $scriptId) {
return $query->where('script_id', $scriptId)->get()->sum('rating');
}
public function scopeThumbsUp($query, $scriptId) {
return $query->where('script_id', $scriptId)->having('rating', '=', 1)->get()->sum('rating');
}
public function scopeThumbsDown($query, $scriptId) {
return $query->where('script_id', $scriptId)->having('rating', '=', -1)->get()->sum('rating');
}
And display them as following:
{{ ScriptRating::thumbsUp($script->id) }}

You can use
{{ $script->ratings->count() }}
This will display the number total ratings a script has.
However what you're interested in doing is grouping the ratings into upvotes and downvotes, so you'll need to query your relationship by a group by clause.
Script::where('title', '=', $title)->with([
'ratings' => function($query) {
$query->groupBy('rating');
})
])->get();
I believe the collection returned should be now grouped by 1 and -1. Let me know of the results!
EDIT: You can also take a look here at the documentation on querying relationships:
http://laravel.com/docs/4.2/eloquent#querying-relations
EDIT for response:
The simplest way to do this without using group by would be separate queries:
$script = Script::where('title', $title)->first();
if ($script) {
$upvotes = ScriptRating::where('script_id', $script->id)->having('rating', '>', 0)->get()->count();
$downvotes = ScriptRating::where('script_id', $script->id)->having('rating', '<', 0)->get()->count();
}
Also the difference between your scripts mentioned is called eager loading or lazy loading. When you specify ->with() in your query, this is called eager loading. If you don't do this, the query will be ran when you specify $script->ratings
More about eager/lazy loading here:
http://laravel.com/docs/4.2/eloquent#eager-loading
Edit for another response:
You would use the ->whereHas('ratings') function if you want to gather scripts that only have ratings. You can also check the existence of the script having ratings by doing an if statement:
if ($script->ratings->count() > 0) {
// The script has ratings
} else {
// The script does not have ratings
}
If you don't want to keep repeating this code you could always put a function inside your Script.php model by using the following:
public function hasRatings()
{
return $this->ratings->count() > 0;
}
Then you can do:
if ($script->hasRatings())

You can add to the Script model class those 2 functions:
public function ratingsSumRelation()
{
return $this->hasOne('ScriptRating')->selectRaw('script_id, sum(rating) as sum_all')
->groupBy('script_id');
}
public function getRatingSumAttribute()
{
return $this->ratingsSumRelation ?
$this->ratingsSumRelation->sum_all: 0;
}
and now display sum using:
{{ $script->rating_sum }}

Related

Reducing amount of queries, page loading slow [duplicate]

This question already has answers here:
Laravel sorting last record
(5 answers)
Closed 2 years ago.
I'm trying to minimize the amount of queries I'm using by "eager loading" a threads' replies pagination. But I'm a little unsure how to do it. Below is the code:
$unpinnedThreads = Thread::all()->sortByDesc(function($thread) {
$replies = $thread->replies->sortByDesc('created_at');
$lastTouchedPost = Carbon::minValue();
if (!empty($replies->toArray())) {
$lastTouchedPost = $lastTouchedPost->max($replies->first()->created_at);
}
return $lastTouchedPost->max($thread->created_at);
});
View:
#foreach($thread->replies->paginate(15)->setPath($thread->path())->getUrlRange(ceil($thread->replies->count()
/ 15 - 2), ceil($thread->replies->count() / 15)) as $key => $pagination)
#if($key > 1)
{{ $key }}
#endif
#endforeach
So far I have alot of queries in the debug bar that looks like this:
select * from `replies` where `replies`.`thread_id` = ? and `replies`.`thread_id` is not null
I was wondering how I could reduce the number of queries.
When I code: Thread::with('replies')->sortByDesc->, it throws calls to undefined method. However when I code ->with('replies'), it throws an error that says:
Method Illuminate\Database\Eloquent\Collection::with does not exist.
Can anyone please help me?
Thank you.
Edit::
I've deduced the issue is in my method:
$unpinnedThreads = Thread::all()->sortByDesc(function($thread) {
$replies = $thread->replies->sortByDesc('created_at');
$lastTouchedPost = Carbon::minValue();
if (!empty($replies->toArray())) {
$lastTouchedPost = $lastTouchedPost->max($replies->first()->created_at);
}
return $lastTouchedPost->max($thread->created_at);
});
Does anyone know how I can optimize the method?
Answer found here: Laravel sorting last record
"One solution I would suggest is to have the replied touch the topics. https://laravel.com/docs/5.3/eloquent-relationships#touching-parent-timestamps
This way you can always order by the Topic's updated_at because whenever a reply is created/edited it will update the Topic as well.
To achieve this you would just need to add:
protected $touches = ['topic'];
The above is assuming that the method name for the topics relationship in the replies model is topic().
Hope this helps!"
That is really strange. It appears that you are calling with() in the model Thread::with('replies'), but the error is saying that you are calling it on a collection. The collection object does not have the with() method.
The bellow code should work:
$collection = Thread::with(['replies'])->get()->sortByDesc(function($thread)
{
if (count($thread->replies))
{
return $thread->replies->sortBy('created_at')->last()->created_at;
}
else
{
return $thread->created_at;
}
});
If you are not using the defaults, created_at must be configured:
protected $casts = [
'created_at' => 'date',
]
Don't forget to create the relationship inside your Thread model:
public function replies()
{
return $this->hasMany(Reply::class, 'thread_id', 'id');
}

My recursive function in laravel does not call itself

I am writing a recursive function to call the child record from the parent record. It seems not to be working. i am getting this error; "Trying to get property 'refid' of non-object". Where am i getting it wrong. Please any idea? below is the code Snippet.
the function controller
public function DisplayDetail($id)
{
$displayDetail = DB::table('tblmembers')
->where('refid',$id)
->get();
return $this->DisplayDetail($displayDetail->refid);
}
main controller where the function is called
public function dashboard()
{
$profile = DB::table('tblmembers')->where('username',$userid)->first();
$data['userdetail'] = $this->DisplayDetail($profile->memid);
return view('main.userArea',$data);
}
the blade where the record fetched is displayed
#foreach($userdetail as $userd)
{{ $userd->memid }}
#endforeach
my sample data
refid | memid
-------------------
12345 | 123456
123456 | 1234567
123456 | 1234568
123456 | 1234569
1234567 | 1234570
from the above table; refid: 123456 brought memid: 1234567,1234568,1234569. then refid: 1234567 brought memid: 12345670
i want to display all the memid after login in as a user with memid 123456
You are doing one thing wrong in your function DisplayDetail. Here the the correction in your function
If you want to get single item then here is the correct code.
public function displayDetail($id)
{
$displayDetail = DB::table('tblmembers')
->where('refid',$id)
->first();
if($displayDetail) {
$displayDetail['userdetail'] = $this->displayDetail($displayDetail->refid);
}
return $displayDetail;
}
And dashboard function will be look like this
public function dashboard()
{
$profile=DB::table('tblmembers')->where('username',$userid)->first();
$userDetail = $this->DisplayDetail($profile->memid);
return view('main.userArea',[
'userdetail' => $userDetail
]);
}
This is the correct code. Try this and let me know if you have another query on this.
The error:
Trying to get property 'refid' of non-object
is occuring because your database query is using ->get(), which returns a Collection, rather than an object. You cannot get the property ->refid on a Collection, you can only get it from an object that resides in the collection.
As Lakhwinder Singh shows in his code, you need to use ->first(), as this will return one object. I would suggest using ->firstOrFail(), that way you will either get back an object which matches your ID, or it will fail if it cannot find it.
If you do:
$displayDetail = DB::table('tblmembers')
->where('refid',$id)
->firstOrFail();
You will now be able to call:
$displayDetail->refid
You can use that in your function call to displayDetail.
try this function
public function DisplayDetail($id,$data=[])
{
$displayDetail = DB::table('tblmembers')
->where('refid',$id)
->get();
if($displayDetail && isset($displayDetail->refid))// your condition for last child
{
$data = $this->DisplayDetail($displayDetail->refid,$data);
}
$data[] = array('refid' =>$displayDetail->id ,
'memid' => $displayDetail->secondid );
return $data;
}
i'll explain you later first modify according to your requirement and run

The Response content must be a string or object implementing __toString(), "object" given

I have two query results, $query1 and $query2
The content of $query1 is printed as follows:
{"name":"Gerencia Regional Llanos","name":"Lorem ipsum"}
And the $query2 is:
[{"id":1,"name":"Lorem upsum"},{"id":2,"name":"Lorem upsum"}]
When I print $query1 in blade everything is okey, but when I try to print $query2 the server throws the following error:
htmlentities() expects parameter 1 to be string, object given
In blade I call both of them in the same way:
{{ $query1 }}
You can see the example to solve the problem.
There is require to response the requested data in the mentioned format.
$datas = DB::table('user_table')
->get();
// response the desired data
return response()->json(['datas' => $datas], 200);
The contents of your first query is a string. But the contents of your second query is converted to an array - I'm guessing because it's a collection of things rather than a single thing. Hence you can't print it with {{ $x }}, because that calls htmlentities(), which only wants to work with strings.
You can either make sure to just get a single object that can be cast to a string in your second query as well, or you can use a foreach loop to print each item in the collection/array at a time.
Create the following function
function utf8_encode_deep(&$input) {
if (is_string($input)) {
$input = utf8_encode($input);
} else if (is_array($input)) {
foreach ($input as &$value) {
self::utf8_encode_deep($value);
}
unset($value);
} else if (is_object($input)) {
$vars = array_keys(get_object_vars($input));
foreach ($vars as $var) {
self::utf8_encode_deep($input->$var);
}
}
}
Try to do the following
utf8_encode_deep($query);

how to combine results of query and return that result using codeigniter

i am new with codeigniter.
i have used the following code to execute query recursively.
Suppose $q query select 4 id (10,11,20,24)
then for each id showreply function (in foreach) call recursively then how can return the combine result.
$resultq3 = $this->showreply($reply_id);
<?php
public function showreply($reply_id)
{
$q1 =$this->db->select('*')
->from('forum_reply AS fr')
->where('fr.parent_id',$reply_id1)
->order_by('fr.id ')->get();;
foreach($q1->result_array() as $row4)
{
$id = $row4['id'];
$parent_id = $row4['parent_id'];
if($parent_id!=0)
{
$this->showreply($id);
}
}
return $result;
}
?>
I'm not really understanding what it is you're asking here. Maybe showing the showReply function would help, but you already have the combined result and are splitting that out in your foreach so what's the problem? Also why are you assigning reply_id to reply_id1? What is the point of that? Just use $reply_id in your query.
You're also executing an if statement that makes little sense since you can filter out the id's you don't want in the query itself (and are you seriously ever going to have an id that = 0?)
In fact the more I look at this code the more confused I become. Where is $id getting populated for $this->showreply($id)?
<?php
public function showreply($reply_id)
{
$q1 =$this->db->select('*')
->from('forum_reply AS fr')
->where('fr.parent_id',$reply_id)
->where('fr.parent_id !=',0)
->order_by('fr.id ')->get();;
//$i=0;
foreach($q1->result_array() as $row4)
{
$parent_id = $row4['parent_id'];
$this->showreply($id);
}
//below is the actual answer to your question on how to return the combined results.
return $q1->result_array();
}
?>
Okay after rereading your question I think I have a better understanding. If you pass the id's as an array like this:
$reply_id = array(10,11,20,24);
You can then modify your query to use:
$this->db->where_in('fr.parent_id', $reply_id);
That will return the results as one combined result with all 4 ids included.

Passing arguments and conditions to model in codeigniter

I'm adding some models to a project, and was wondering if there is a "best practice" kind of approach to creating models:
Does it make sense to create a function for each specific query?
I was starting to do this, then had the idea of creating a generic function that I could pass parameters to. e.g:
Instead of
function getClients(){
return $this->db->query('SELECT client_id,last FROM Names ORDER BY id DESC');
}
function getClientNames($clid){
return $this->db->query('SELECT * FROM Names WHERE client_id = '.$clid);
}
function getClientName($nameID){
return $this->db->query('SELECT * FROM Names WHERE id ='.$nameID);
}
}
Something like
function getNameData($args,$cond){
if($cond==''){
$q=$this->db->query('SELECT '.$args.' FROM Names');
return $q;
}else{
$q=$this->db->query('SELECT '.$args.' FROM Names WHERE '.$cond);
return $q;
}
}
where I can pass the fields and conditions (if applicable) to the model. Is there a reason the latter example would be a bad idea?
Thanks!
I think it would actually be a better idea to use CI's Active Record to compile the queries.
An example:
function all_clients($select)
{
$this->db->select($select);
return $this->_get_client_data();
}
function single_client($select, $id = "")
{
// validate $id
$this->db->select($select);
$this->db->where("id", $id);
$this->db->limit(1);
return $this->_get_client_data();
}
// Only called by a method above once the query parameters have been set.
private function _get_client_data()
{
$q = $this->db->get("clients");
if($q->num_rows() > 0)
{
return $q->result_array();
}
return FALSE;
}
CI's Active Record makes all the stuff you were wanting to much easier. You can imagine setting up your public functions to conditionally set a number of options before actually calling $this->db->get().
I guess you would call _get_client_data a catch-all (?) and running all your data retrieval through a single method makes stuff like error handling much easier to maintain.
NOTE: Always remember to validate data like this. I know you do, but I'm just repeating it.

Resources