Optimise AJAX - Laravel function - php

I'm struggling a bit with some formulas, the idea is to have just one formula/function for everything, so it'll be easy to maintain and will be robust.
The problem is trying to combine AJAX calls and laravel functions.
From one side I have a AJAX Datatables controller (the calls need to be in this format):
public function userData(Request $request)
{
$event = User::select(
'users.*',
DB::raw('IFNULL(b.balance,0) as balance'),
)
->leftJoin(DB::raw('(SELECT seller_id, SUM(total) as balance FROM transactions WHERE concept IN ("TPV") AND status = "ok" GROUP by buyer_id)as b'), 'b.seller_id', '=', 'users.id')
->get();
return $this->formatView($request, $event, 'user');
}
Then, the formula I use for the rest of the web is in a Function inside a Model:
public function Balance($seller_id = false){
return Transaction::emitted()
->where('event_id', $this->id)
->where('seller_id', $this->seller_id)
->whereIn('concept', ['TPV'])
->where('status', 'ok')
->sum('total');
}
The question is: Do you have an idea of how to use just one formula/function for everything?

Try this in your controller method
Do common calculations in separate method and call it here. Then change format of response in below sections
if ($request->expectsJson()){
//send response to ajax here in json format. note that you should set ajax dataType:'json'
}
//send response for web here.

Related

Getting the latest of relations in Laravel

I have this schema:
Ticket
----------
id, description
TicketStatus
-----------
id, name
Ticket_TicketStatus
-----------
id, ticket_id, status_id, latest (bool)
on Ticket model
public function statuses()
{
return $this->belongsToMany(TicketStatus::class, 'ticket_ticket_status', 'ticket_id', 'status_id');
}
public function getCurrentStatusAttribute()
{
return $this->statuses()->wherePivot('latest', 1)->first();
}
public function getStatusAttribute()
{
return $this->current_status->name;
}
public function scopeOpen($query)
{
return $query->whereHas('statuses', function (Builder $sub_query) {
$sub_query->where('latest', true)->where('is_open', true);
});
}
public function scopeClosed($query)
{
return $query->whereHas('statuses', function (Builder $sub_query) {
$sub_query->where('latest', true)->where('is_open', true);
});
}
What I'm trying to accomplish is to get all the tickets of a certain status that are the latest ones.
So I do something like this:
$not_mine_open_tickets = Ticket::open()
->orderBy('updated_at', 'desc')
->get();
But this is taking a lot of time to be executed on my database.
Anyone knows what's wrong?
But this is taking a lot of time to be executed on my database. Anyone knows what's wrong?
It could be many things, and it depends on a lot of factors.
What DB engine are you using?
How many rows are there?
What indexes are set up?
What is the current load of the database?
I'd start by looking at what the execution plan of your queries are and see what the estimated time to query is and what (if any) indexes are being used.
To get the query that would be executed, you can dump this out in your code:
dump(Ticket::open()
->orderBy('updated_at', 'desc')
->toSql());
dump(Ticket::open()
->orderBy('updated_at', 'desc')
->getBindings());
I'd then look to run this through some database software and look at the execution plan.
If you do some more research for your specific database engine you can find specific advice for next steps.

Can multiple queries be applied to a model in Laravel?

I'm currently trying to get data from the DB with a Laravel controller through a model Maintain. Is there a way to query the DB based on the request data made by an axios post from the front end, this includes both variables sub and zone, I have the ability to get the data from using sub OR zone but not both, is there a way to construct a multi where query if $request contains the necessary variables and a standard request if they are null or ""?
public function all(Request $request){
$query = [];
if($request->sub != ""){
array_push($query, ['subsystem', '=', $request->sub]);
}
if($request->zone != ""){
array_push($query, ['zone', '=', $request->zone]);
}
if(count($query) > 0){
return Maintain::all()->where($query);
}
else{
return Maintain::all();
}
}
Currently This returns with an error ErrorException: array_key_exists(): The first argument should be either a string or an integer in file but I've been using the Laravel reference and it doesn't seem to be working. I used Postman to get the readout of $query and it contains the following:
[
['sub', '=', 'Subsystem 1'],
['zone', '=', 'Zone 1']
]
Any help would be appreciated.
Try like this
public function all(Request $request){
$result = Maintain::when($request->zone, function ($q) use($request){
$q->where('zone', $request->zone);
})
->when($request->sub, function ($qw) use($request){
$qw->where('subsystem', $request->sub);
})
->get();
return($result);
}
when() method look like if-else
Edited: Let we know if you get an error: Happy coding

how to chunk a join query in laravel

Somewhere in my Laravel application, the following query is likely to return a very large results set:
$data = $query->join('accommodation_rooms', 'accommodations.id', '=', 'accommodation_rooms.accommodation_id')
->join('discounts', 'accommodation_rooms.id', '=', 'discounts.accommodation_room_id')
->select('accommodation_rooms.id')
->orderBy('discounts.amount', 'desc')
->select('discounts.amount', 'accommodations.*')
->groupBy('discounts.amount', 'accommodation_rooms.id');
return $data;
I wondered how I could load a part of the data faster but only load the rest later maybe using some pagination mechanism or something.
Given the data is sent from an API, I want to know how I could chunk this data.
Thank you.
You can use paginate method for this purpose
$data = $query->join('accommodation_rooms', 'accommodations.id', '=', 'accommodation_rooms.accommodation_id')
->join('discounts', 'accommodation_rooms.id', '=', 'discounts.accommodation_room_id')
->select('accommodation_rooms.id')
->orderBy('discounts.amount', 'desc')
->select('discounts.amount', 'accommodations.*')
->groupBy('discounts.amount', 'accommodation_rooms.id')->paginate(15);
You can change the numbers of record to fetch in given parameter to paginate function e.g 15.

laravel eloquent sort by relationship

I have 3 models
User
Channel
Reply
model relations
user have belongsToMany('App\Channel');
channel have hasMany('App\Reply', 'channel_id', 'id')->oldest();
let's say i have 2 channels
- channel-1
- channel-2
channel-2 has latest replies than channel-1
now, i want to order the user's channel by its channel's current reply.
just like some chat application.
how can i order the user's channel just like this?
channel-2
channel-1
i already tried some codes. but nothing happen
// User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies'])
->orderBy('replies.created_at'); // error
}
// also
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved')
->with(['replies' => function($qry) {
$qry->latest();
}]);
}
// but i did not get the expected result
EDIT
also, i tried this. yes i did get the expected result but it would not load all channel if there's no reply.
public function channels()
{
return $this->belongsToMany('App\Channel')
->withPivot('is_approved')
->join('replies', 'replies.channel_id', '=', 'channels.id')
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'ASC');
}
EDIT:
According to my knowledge, eager load with method run 2nd query. That's why you can't achieve what you want with eager loading with method.
I think use join method in combination with relationship method is the solution. The following solution is fully tested and work well.
// In User Model
public function channels()
{
return $this->belongsToMany('App\Channel', 'channel_user')
->withPivot('is_approved');
}
public function sortedChannels($orderBy)
{
return $this->channels()
->join('replies', 'replies.channel_id', '=', 'channel.id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Then you can call $user->sortedChannels('desc') to get the list of channels order by replies created_at attribute.
For condition like channels (which may or may not have replies), just use leftJoin method.
public function sortedChannels($orderBy)
{
return $this->channels()
->leftJoin('replies', 'channel.id', '=', 'replies.channel_id')
->orderBy('replies.created_at', $orderBy)
->get();
}
Edit:
If you want to add groupBy method to the query, you have to pay special attention to your orderBy clause. Because in Sql nature, Group By clause run first before Order By clause. See detail this problem at this stackoverflow question.
So if you add groupBy method, you have to use orderByRaw method and should be implemented like the following.
return $this->channels()
->leftJoin('replies', 'channels.id', '=', 'replies.channel_id')
->groupBy(['channels.id'])
->orderByRaw('max(replies.created_at) desc')
->get();
Inside your channel class you need to create this hasOne relation (you channel hasMany replies, but it hasOne latest reply):
public function latestReply()
{
return $this->hasOne(\App\Reply)->latest();
}
You can now get all channels ordered by latest reply like this:
Channel::with('latestReply')->get()->sortByDesc('latestReply.created_at');
To get all channels from the user ordered by latest reply you would need that method:
public function getChannelsOrderdByLatestReply()
{
return $this->channels()->with('latestReply')->get()->sortByDesc('latestReply.created_at');
}
where channels() is given by:
public function channels()
{
return $this->belongsToMany('App\Channel');
}
Firstly, you don't have to specify the name of the pivot table if you follow Laravel's naming convention so your code looks a bit cleaner:
public function channels()
{
return $this->belongsToMany('App\Channel') ...
Secondly, you'd have to call join explicitly to achieve the result in one query:
public function channels()
{
return $this->belongsToMany(Channel::class) // a bit more clean
->withPivot('is_approved')
->leftJoin('replies', 'replies.channel_id', '=', 'channels.id') // channels.id
->groupBy('replies.channel_id')
->orderBy('replies.created_at', 'desc');
}
If you have a hasOne() relationship, you can sort all the records by doing:
$results = Channel::with('reply')
->join('replies', 'channels.replay_id', '=', 'replies.id')
->orderBy('replies.created_at', 'desc')
->paginate(10);
This sorts all the channels records by the newest replies (assuming you have only one reply per channel.) This is not your case, but someone may be looking for something like this (as I was.)

Undefined Variable Multiple Query Scopes Laravel

This work perfect:
public function scopeHBO($query)
{
return $query ->where('network', '=', "hbo");
}
Call in Controller: It Works!
$events = Schedule::HBO()->orderBy('searchdate')->get();
When I add another Query Scope like so:
public function scopeHBO($query)
{
return $query
->where('network', '=', "hbo")
->where('searchdate', '>=', 'NOW()');
}
OR:
public function scopeDate($query)
{
return $query->where('searchdate', '>= ', 'NOW()');
}
Then call in the controller:
$events = Schedule::HBO()->Date()->orderBy('searchdate')->get();
I get an error: Undefined variable: event. I tried with with Raw MySql in the same model and it works. Whenever i add a query scope, does not matter what it is.. i get that same error Undefined variable: event.
NOW() is a function, so you need to use a raw query:
where('searchdate', '>=', DB::raw('NOW()'))
Then you can use the scopes. (Do note that I think scopeDate must be called as date(), not Date() - not 100 % sure on that though.)
This sounds less like a generic problem with Laravel, and more like a problem with you specific application.
My guess (which is a wild guess), is that adding that second where clause in your scope method
return $query
->where('network', '=', "hbo")
->where('searchdate', '>=', 'NOW()');
ended up creating a SQL query that returned 0 rows. Then, somewhere in your other code you're doing something like
foreach($events as $event)
{
//...
}
//referencing final $event outside of loop
if($event) { ... }
As I said, this is a wild guess, but the problem doesn't seem to be your query code, the problem seems to be the rest of your code that relies on the query returning a certain number of, or certain specific, rows/objects.

Categories