Best practices to update a collection [duplicate] - php

I'm trying to make a notification system with laravel. My idea was to get data and update instantly the "is_delivered" flag.
This is the code:
Model:
public function scopeGetForView($query)
{
$query->orderBy('created_at','DESC');
$return = $query->get();
if($return->count() > 0) {
$query->update(array("is_delivered" => 1));
}
return $return;
}
Controller:
$notifications = Auth::user()->notifications()->limit(10)->offset(10)->getForView();
Well, this would work fine without the offset because MySQL does only support limit (without offset) when updating.
But how can I update the whole collection without looping through it? Looping with instant updating would lead to many queries. The other way I can think of would be to create an array with IDs and update them with whereIn(). Is this the only way doing it?

You can run an update on the whole collection:
DB::table('table_name')->whereIn('id', $collection->modelKeys())->update(['is_delivered' => 1]);

Related

How can I filter a negative value from a query made with eloquent and then paginate it?

I was trying to get the remaining time for movie delivery in a rental system made with laravel.
I built the following query using eloquent and it returned the result I wanted:
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->orderBy('Days','asc')
->paginate(6);
but there is a problem when these rentals go over the delivery deadline it returns negative values, so I would like the query to return only the rentals that have the remaining days greater than zero and then paginate those results.
I create this statement and using map() filter only the positives that are returned in a collection but the problem is that I can't paginate them.
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->get()->map(function($alquiler){
return ($alquiler->Days >= 0) ? $alquiler : null;
});
$resting_time = $resting_time->filter()->sortBy('Days');
This is the returning collection:
But this type of collection cannot be paginated.
Any idea how to fix it, or maybe an easier way to do it? Sorry if something doesn't make sense, I'm just starting in laravel.
In second case its not working,because you work with:
\Illuminate\Support\Collection::class
in first case, you work with :
\Illuminate\Database\Eloquent\Collection::class
To make it work , you can try to do next thing:
take a
\Illuminate\Support\Collection::class
and return it paginated via
Illuminate\Pagination\Paginator::class
so the end result will look like this:
$resting_time = DB::table('alquiler')
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->select('socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days"))
->get()->map(function($alquiler){
return ($alquiler->Days >= 0) ? $alquiler : null;
});
$resting_time = $resting_time->filter()->sortBy('Days');
return new Illuminate\Pagination\Paginator($resting_time, 6);
However, i would recommend to prepare data from SQL side, neither doing all of the manipulations from collection perspective.
Most of the answers already provided will work, but will return a collection instead of a paginated resource. The trick is to use the tap helper method before map'ping, to return the same object you modified.
return tap(Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->with('socio', 'pelicula')
->paginate(20))
->map(function ($model) {
return ($model->Days >= 0) ? $model : null;
});
or you can do this way too:
return Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->with('socio', 'pelicula')
->paginate(20))
->map(function ($model) {
if($alquiler->Days >= 0) {
return $model;
}
});
I tried both methods and it didn't work at least the way I wanted, so I did a little more research and put this together:
public function getRestingTime(){
$resting_time = Alquiler::select(['socio.soc_nombre','pelicula.pel_nombre','alquiler.created_at', DB::raw("DATEDIFF(alq_fecha_hasta,NOW()) AS Days")])
->whereRaw('DATEDIFF(alq_fecha_hasta,NOW()) >= ?', [0])
->join('socio','alquiler.soc_id','=','socio.id')
->join('pelicula','alquiler.pel_id','=','pelicula.id')
->orderBy('Days','asc')->paginate(6);
return $resting_time;
}
I hope it helps someone, thanks likewise to the people who responded cleared my mind a bit and gave me new things to try.

Eloquent return wrong primary key result with Datatables

I have a problem with an eloquent result using Datatables component.
This is my code:
$licences = Licence::with(['purchase.company', 'catalog_package.catalog', 'catalog_package.package', 'createdContact']);
return Datatables::of($licences)
->addColumn('actions', function (Licence $licence) {
$ret = '<i class="fa fa-list-ul"></i>';
return $ret;
})
->rawColumns(['actions', 'end_date'])
->make();
The result of $licence->id is wrong: I expect to have 1 but I get 2.
I think that it is confused with the relationships?
I tried to run dd($licence) inside addColumn and there I found that all the values are correct except for id.
Do you know why?
Thanks in advance
According to Yajra Documentation you should add ->select('licences.*') statement to your eloquent query when using eager load "to avoid weird issues where id from related model replaces the id of the main model".
$licences = Licence::with(['purchase.company', 'catalog_package.catalog', 'catalog_package.package', 'createdContact'])->select('licences.*');
https://datatables.yajrabox.com/eloquent/relationships#:~:text=It%20is%20advised%20that%20you%20include%20select(%27table.*%27)

RESTful API - Array to Collection Laravel 5

I'm having issues with an array returned from DB::select(). I'm heavily using skip and take on Collections of eloquent models in my API. Unfortunately, DB::select returns an array, which obviously doesn't work with skip and take's. How would one convert arrays to a collection that can utilise these methods?
I've tried
\Illuminate\Support\Collection::make(DB::select(...));
Which doesn't quite work as I expected, as it wraps the entire array in a Collection, not the individual results.
Is it possible to convert the return from a DB::select to a 'proper' Collection that can use skip and take methods?
Update
I've also tried:
$query = \Illuminate\Support\Collection::make(DB::table('survey_responses')->join('people', 'people.id',
'=', 'survey_responses.recipient_id')->select('survey_responses.id', 'survey_responses.response',
'survey_responses.score', 'people.name', 'people.email')->get());
Which still tells me:
FatalErrorException in QueryHelper.php line 36:
Call to a member function skip() on array
Cheers
I would try:
$queryResult = DB::table('...')->get();
$collection = collect($queryResult);
If the query result is an array, the collection is filled up with your results. See the official documentation for the collection. Laravel5 Collections
For anyone else that's having this sort of problem in Laravel, I figured out a work around with the following solution:
$query = DB::table('survey_responses')->join('people', 'people.id', '=', 'survey_responses.recipient_id')
->select('survey_responses.id', 'survey_responses.response', 'survey_responses.score', 'people.name', 'people.email');
if(isset($tags)){
foreach($tags as $tag){
$query->orWhere('survey_responses.response', 'like', '%'.$tag.'%');
}
};
// We apply the pagination headers on the complete result set - before any limiting
$headers = \HeaderHelper::generatePaginationHeader($page, $query, 'response', $limit, $tags);
// Now limit and create 'pages' based on passed params
$query->offset(
(isset($page) ? $page - 1 * (isset($limit) ? $limit : env('RESULTS_PER_PAGE', 30)) : 1)
)
->take(
(isset($limit) ? $limit : env('RESULTS_PER_PAGE', 30))
);
Basically, I wasn't aware that you could run the queries almost incrementally, which enabled me to generate pagination chunks before limiting the data returned.

How to chunk results from a custom query with Laravel 5

Following up on this Question: How to chunk results from a custom query in Laravel
I try
DB::connection('mgnt')->select($query)->chunk(200, function($orders) {
foreach ($orders as $order) {
//a bunch of code...
}
});
But I get the following error:
FatalErrorException in MigrationController.php line 98:
Call to a member function chunk() on array
Is chunking possible without having an appropriate Eloquent ORM Model?
I try to chunk because I get a blank page (can't find any errrors in any log) if the query returns too many results.
I think right now it max 50.000 results that I can query at once. Is that maybe due to some restriction or limitation in Laravel?
Well since the query will just return an array of objects you can simply use PHP's array_chunk():
$result = DB::connection('mgnt')->select($query);
foreach(array_chunk($result, 200) as $orders){
foreach($orders as $order){
// a bunch of code...
}
}
Here's what chunk() on an eloquent model does:
$results = $this->forPage($page = 1, $count)->get();
while (count($results) > 0)
{
// On each chunk result set, we will pass them to the callback and then let the
// developer take care of everything within the callback, which allows us to
// keep the memory low for spinning through large result sets for working.
call_user_func($callback, $results);
$page++;
$results = $this->forPage($page, $count)->get();
}
You could try to do something similar (although I think it should be possible to run your query all at once, but I can't help you with that...)
Add a limit to your SQL query LIMIT 200
Increase the offset with every query you run. First 0, second 1 * 200, third 2 * 200
Do that until the result is returned empty (e.g. with a while loop like above)

Laravel 4 - How to turn timestamps off to update an entire collection

I'm trying to do a mass update on an eloquent collection.
So I have my query, which looks a bit like this:
\Responder::with('details')
->where('job_number', $project->job_number)
->where('batch_id', ((int) $batch_id) - 1)
->where('updated_at', '<=', $target_time)
->whereHas('transactions', function($q) {
$q->where('status', 'success');
}, '<', 1)
->whereHas('details', function($q) {
$q->where('email', '<>', '');
});
This query object is stored as $query (because I'm re-using it - the same reason I dont want to switch how I'm doing the query), I am then performing an update on the collection, e.g.
$query->update(array('batch_id' => $batch_id));
This works great except it updates all the 'updated_at' timestamps. Now i like the timestamps, they are used extensively elsewhere, so i cant turn them off all together but I thought I could disable them temporarily but I've tried the following:
$query->timestamps = false;
$query->update(array('email_drop_off_index' => $batch_id));
and I can confirm that doesn't work, is there a way to do this?
Any help much appreciated
timestamps = false should be made on your model, but what you are doing is setting the value on the query builder. That's why it is not being picked up.
timestamps is an instance variable so you can't set it statically, and I don't think there is a built-in way to do it from the query builder. So I suggest try instantiating the model first, then create a new query from it, like this:
$responder = new \Responder;
$responder->timestamps = false;
$query = $responder->newQuery()
->with('details')
->where('job_number', $project->job_number)
...; // the rest of your wheres
$query->update(array('email_drop_off_index' => $batch_id));
Here's a possible solution: subclass your Responder model and turn off timestamps in the subclass.
class MassUpdateResponder extends Responder
{
public $timestamps = false;
}
Then use your new class to do the updates. This seems like a bit of a hack, but it should work.
BTW, doing an update like the following worked for me:
$query->timestamps = false;
$query->value = "new value";
$query->save();
The update() method may be doing something different that's causing it to ignore the value of $timestamps.

Categories