how to update specific record in cache in Laravel? - php

I am working on a project but I am not quite sure how to update a single record stored in the cache, for example:
PostController.php
class PostController extends Controller
public function index(){
$page = request('page', 1);
$posts = Cache::rememberForever('posts_page_'.$page, function(){
return Posts::query()
->with('category')
->latest()
->paginate(30);
});
}
...
...
}
Here is the store method:
public function store(PostCreateRequest $request)
{
$post = Post::create([
'title' => $request->title,
'body' => $request->body,
...
...
'category_id' => $request->category_id
]);
....
....
Cache::flush();
return redirect()->route('posts.index')
->with('success', 'post created');
}
I need to do Cache::flush(); to clean all the posts cache, because I have this keys in the cache: posts_page_1, posts_page_2, posts_page_3, etc, category_name1_page_1, category_name1_page_2, category_name2_page_1, etc..
But I am adding only one post, the same occurs when I update only one post, I have to clean up all the cache, what would happen if I had thousands or millions records? that Cache::flush() would take to much time, I would like to know how to add or update one specific record in the cache, I am using currently the file driver for caching. Thanks.

The problem here is that your are not storing many posts your are storing the result of a query, so any time you use the index() method you are retrieving a cached response.
First you can face this "issue" by avoiding the use of rememberForever().
Why? Because every time you have to manually clear the cache, if you forgot items you can easily end with many MB of data forgotten in the cache.
Maybe changing your method to:
$seconds = 300; // A default clearing time
Cache::remember('posts_page_'.$page, $seconds, function(){
return Posts::query()
->with('category')
->latest()
->paginate(30);
});
Also in the docs says many ways to clear cache. Maybe use:
Cache::forget('posts_page_'.$page);

Related

Laravel - sort array from other class by SQL table column

I am calling an array of all the comments of a poll by using the following code:
$poll = Poll::find($id);
return view('pages.poll', ['poll' => $poll, 'comments' => $poll->comments]);
and the links between Comments and Polls are the following:
Comment.php
public function poll() {
return $this->belongsTo(Poll::class, 'poll_id');
}
Poll.php
public function comments() {
return $this->hasMany(Comment::class, 'poll_id');
}
Finally, I would like to sort the array comments coming from $poll->comment by the column likes in the Comment table, something like DB::table('comment')->orderBy('likes')->get();.
Is there any way to do that?
$poll->comments->sortBy('likes')
There's a number of ways you can do this.
Add orderBy('likes') directly to your comments relationship:
Poll.php:
public function comments() {
return $this->hasMany(Comment::class, 'poll_id')->orderBy('likes');
}
Now, any time you access $poll->comments, they will be automatically sorted by the likes column. This is useful if you always want comments in this order (and it can still be overridden using the approaches below)
"Eager Load" comments with the correct order:
In your Controller:
$poll = Poll::with(['comments' => function ($query) {
return $query->orderBy('likes');
})->find($id);
return view('pages.poll', [
'poll' => $poll,
'comments' => $poll->comments
]);
with(['comments' => function ($query) { ... }]) adjusts the subquery used to load comments and applies the ordering for this instance only. Note: Eager Loading for a single record generally isn't necessary, but can be useful as you don't need to define an extra variable, don't need to use load, etc.
Manually Load comments with the correct order:
In your Controller:
$poll = Poll::find($id);
$comments = $poll->comments()->orderBy('likes')->get();
return view('pages.poll', [
'poll' => $poll,
'comments' => $comments
]);
Similar to eager loading, but assigned to its own variable.
Use sortBy('likes'):
In your Controller:
$poll = Poll::find($id);
return view('pages.poll', [
'poll' => $poll,
'comments' => $poll->comments->sortBy('likes')
]);
Similar to the above approaches, but uses PHP's sorting instead of database-level sorting, which can be significantly less efficient depending on the number of rows.
https://laravel.com/docs/9.x/eloquent-relationships#eager-loading
https://laravel.com/docs/9.x/eloquent-relationships#constraining-eager-loads
https://laravel.com/docs/9.x/collections#method-sortby
https://laravel.com/docs/9.x/collections#method-sortbydesc

laravel pick last comment posted related to each post

I have got three tables in laravel like so:
Users, posts, and comments
I'm trying to come up with a query that fetches me all the user's posts, plus the date of last comment with each post.
Approach i've taken that's not working perfectly is:
$posts = User::find($userId)->posts()->with('latestComment')->get();
In my Post model I have:
public function latestComment()
{
return $this->hasOne(Comment::class)->latest();
}
In my findings, i haven't been to see a way to get the date from the lastComment load.
Any pointers welcome,
Thanks
Just discovered one needs to add the foreign key to the select method like so:
return $this->hasOne(Comment::class)->latest()->select('field','foreign_key');
You should use eager loading constraint. Code from the other answers will first load all comments, which you don't want.
$posts = Post::where('user_id', $userId)
->with(['comments' => function($q) {
$q->taletst()->take(1);
}])
->get();
You can use the existing relationship and get the latest comment.
public function comments() {
return $this->hasMany(Comment::class);
}
public function latestComment() {
return $this->comments()->last();
}

Showing orders of user in laravel

I'm trying to give ability on user to see his orders. How can I query the database.. I'm trying something like this but I got empty page.. I mean nothing from database. May be my query ins't correct.
This is my controller
public function viewOrders() {
$user_order = self::$user->user_id;
$orders = Order::where('user_id', '=', $user_order);
return View::make('site.users.orders', [
'orders' => $orders
]);
}
Am I getting correctly user_id here? I'm not sure...
Update: I have this in my User model
public function orders() {
return $this->hasMany('Order', 'user_id', 'user_id');
}
Ok, so based on your route+button+model do it like this
$orders = self::$user->orders()->orderBy('order_id', 'asc')->get();
return View::make('site.users.orders', [
'orders' => $orders
]);
this should work.. You can remove orderBy clause if you don't need it.
If you have Authentication set properly you can do the following.
public function viewOrders(){
$user = Auth::user();
return view('site.users.orders',[
'orders' => $user->orders
]);
}
When you use the relationship without using the calling parentheses you get a collection of models which are queried if they're not already loaded. This is called lazy loading, if you want to load a relationship before accessing it you can use eager loading. In this case, it is not necessary though.

Reducing Query Load Times By Limiting Results on Eloquent Relationship

I have the following scope.
public function scopeWithPosts($query)
{
return $query->with(
['posts' => function($query) {
$query->limit(2); // Line 5
}]
// further eager loads
// e.g. ['posts.*']
);
}
My relationship is as follows:
public function posts()
{
return $this->hasMany('Models\Post', 'user_id', 'user_id');
}
I am attempting to do the above to reduce the load time on my query. Problem is that whenever I attempt limit, it doesn't seem to work.
For example, with Line 5 I get 1 result, and if I remove I get 5 results.
Any ideas where i'm going wrong?
Thanks in advance.
Did you already tested the take method?
$query->take(2);
For even more precise limitation you could use
$query->skip(10)->take(2);

Eloquent model mass update

Please correct me if I am wrong, but I think there is no such thing as mass update in an Eloquent model.
Is there a way to make a mass update on the DB table without issuing a query for every row?
For example, is there a static method, something like
User::updateWhere(
array('age', '<', '18'),
array(
'under_18' => 1
[, ...]
)
);
(yes, it is a silly example but you get the picture...)
Why isn't there such a feature implemented?
Am I the only one who would be very happy if something like this comes up?
I (the developers), wouldn't like to implement it like:
DB::table('users')->where('age', '<', '18')->update(array('under_18' => 1));
because as the project grows, we may require the programmers to change the table name in the future and they cannot search and replace for the table name!
Is there such a static method to perform this operation? And if there is not, can we extend the Illuminate\Database\Eloquent\Model class to accomplish such a thing?
Perhaps this was not possible a few years ago but in recent versions of Laravel you can definitely do:
User::where('age', '<', 18)->update(['under_18' => 1]);
Worth noting that you need the where method before calling update.
For mass update/insert features, it was requested but Taylor Otwell (Laravel author) suggest that users should use Query Builder instead. https://github.com/laravel/framework/issues/1295
Your models should generally extend Illuminate\Database\Eloquent\Model. Then you access the entity iself, for example if you have this:
<?php
Use Illuminate\Database\Eloquent\Model;
class User extends Model {
// table name defaults to "users" anyway, so this definition is only for
// demonstration on how you can set a custom one
protected $table = 'users';
// ... code omited ...
Update #2
You have to resort to query builder. To cover table naming issue, you could get it dynamically via getTable() method. The only limitation of this is that you need your user class initialized before you can use this function. Your query would be as follows:
$userTable = (new User())->getTable();
DB::table($userTable)->where('age', '<', 18)->update(array('under_18' => 1));
This way your table name is controller in User model (as shown in the example above).
Update #1
Other way to do this (not efficient in your situation) would be:
$users = User::where('age', '<', 18)->get();
foreach ($users as $user) {
$user->field = value;
$user->save();
}
This way the table name is kept in users class and your developers don't have to worry about it.
A litle correction to #metamaker answer:
DB::beginTransaction();
// do all your updates here
foreach ($users as $user) {
$new_value = rand(1,10) // use your own criteria
DB::table('users')
->where('id', '=', $user->id)
->update(['status' => $new_value // update your field(s) here
]);
}
// when done commit
DB::commit();
Now you can have 1 milion different updates in one DB transaction
If you need to update all data without any condition, try below code
Model::query()->update(['column1' => 0, 'column2' => 'New']);
Use database transactions to update multiple entities in a bulk. Transaction will be committed when your update function finished, or rolled back if exception occurred somewhere in between.
https://laravel.com/docs/5.4/database#database-transactions
For example, this is how I regenerate materialized path slugs (https://communities.bmc.com/docs/DOC-9902) for articles in a single bulk update:
public function regenerateDescendantsSlugs(Model $parent, $old_parent_slug)
{
$children = $parent->where('full_slug', 'like', "%/$old_parent_slug/%")->get();
\DB::transaction(function () use ($children, $parent, $old_parent_slug) {
/** #var Model $child */
foreach ($children as $child) {
$new_full_slug = $this->regenerateSlug($parent, $child);
$new_full_title = $this->regenerateTitle($parent, $child);
\DB::table($parent->getTable())
->where('full_slug', '=', $child->full_slug)
->update([
'full_slug' => $new_full_slug,
'full_title' => $new_full_title,
]);
}
});
}
Laravel 6.*
We can update mass data on query as follow :
Appointment::where('request_id' , $appointment_request->id)->where('user_id', Auth::user()->id)->where('status', '!=', 'Canceled')->where('id', '!=', $appointment->id)->update(['status' => 'Canceled', 'canceled_by' => Auth::user()->id]);
Another example of working code of the mass query and mass update in same instruction:
Coordinate::whereIn('id',$someCoordIdsArray)->where('status','<>',Order::$ROUTE_OPTIMIZED)
->update(['status'=>Order::$ROUTE_OPTIMIZED]);
From Laravel 8 you can also use upsert which helped me updated multiple rows at once with each rows having different values.
https://laravel.com/docs/8.x/eloquent#upserts
laravel 5.8 you can accomplish mass update like so:
User::where('id', 24)->update (dataAssociativeArray) ;

Categories