Laravel - Cache results of relationship tables using Eloquent queries - php

I have a simple relationship Post x Files in my app (one post has many files and one file is associated to only one post). To make things more agile, I store the posts on the cache whenever it is not changed so I don't need to query the database again. The problem that I noticed is that only the Posts are being stored in the cache and not the files (and I suppose the problem is because the way I query them). My code is:
class Post extends Model
{
public function files(){
return $this->hasMany('Ibbr\File');
}
}
The function for obtaining the posts:
public static function pegaPostsBoard($nomeBoard)
{
$chave = 'posts_board';
if(Cache::has($chave))
return Cache::get($chave);
$posts = Post::orderBy('updated_at', 'desc')->where('board', $nomeBoard)->where('lead_id', null)->paginate(10);
Cache::forever($chave, $posts); //I "forget" the cache whenever the post is changed
return $posts;
}
I also tried adding ->join('files', 'posts.id', '=', 'files.post_id') before adding it to the cache but it didn't work. How did I noticed that the files are not being cached? Well, I reset the database so it cleans all the rows and I noticed that If I F5 the page the posts are still there (because they are cached) but not their files. So my question is how do I query it in such a way that the files are also stored?

Use with to append relationship to query results
$posts = Post::with('files')
->orderBy('updated_at', 'desc')
->where('board', $nomeBoard)
->where('lead_id', null)
->paginate(10);

Related

Laravel Get data One to Many Relationship with conditions

how to display data one too many relationships with conditions into view.
I have a blog post that has a comment, but this comment has a condition that published or not.
here my Post Model
...
public function comments()
{
return $this->hasMany(Comment::class);
}
...
on my code above I can simply use $post->comments to show all the comments.
like I said before, I need only show comments with published is true
but how to add conditions?...
You can achieve this by getting the post with it's published comments;
$post = Post::where('id', $id)->with('comments', function ($q) {
$q->where('published', true);
})->first();
Then when in the view you call $post->comments, you will only get the published one.
Alternatively, if you really want to you can update your Model to have a published comments relationship, but this is less advised.
public function publishedComments()
{
return $this->hasMany(Comment::class)->where('published', true);
}
You can get only published comments by using
$this->post->comments()->where('published', true)->get()
Check the Documentation
Of course, since all relationships also serve as query builders, you can add further constraints to which comments are retrieved by calling the comments method and continuing to chain conditions onto the query: $comment = App\Post::find(1)->comments()->where('title', 'foo')->first();

Calling Laravel relationship with aggregate

If I have a Laravel 5.5 model called User that hasMany Posts and each Post hasMany Hits, is there an aggregate function I can call to get the total number of Hits for a User across all Posts, where the Hit was created in the last week?
It seems like there may be a clever way to do it besides doing something like
$hits = $user->posts()->hits()
and then looping over those hits to check created date.
In this case it seems like raw sql would be better, but I figured there may be an Eloquent way to handle a situation like this.
I think the right solution is just to use a HasManyThrough relationship to grab all the Hit rows, joined through the posts table.
So it'd look like this on the User model (roughly):
return $this->hasManyThrough(
Hit::class,
Post::class
// if you have non-standard key names you can specify them here-- see docs
);
Then when you have your User model you can just call $user->hits to get a collection of all the associated hits through all the user's Posts
You can add the code below to your Post model.
protected static function boot()
{
parent::boot();
static::addGlobalScope('hitCount', function ($builder) {
$builder->withCount('hits');
});
}
It automatically provides a field hits_count whenever you fetch a post.
$post = Post::first();
$hits = $post->hits_count; //Count hits that belongs to this post
You can read the documentation here to customize it to your need.
Set HasManyThrough relation in the User model:
public function hits()
{
return $this->hasManyThrough('App\Models\Hits','App\Models\Posts','user_id','post_id','id');
}
then you can do this:
$reults = $user->hits()->where('hits_table_name.created_at', '>=', Carbon::today()->subWeek())->count();
HasManyThrough Link
Use DB::enableQueryLog(); and DB::getQueryLog(); to see if executed SQL Query is correct;

How to access related record in CakePHP with mysql?

I have a database of two tables. One with blog posts and one with users, related by a user_id field in the post table. On my index page I have a table of the posts and I want to add the author to that however I want to display the user's name rather than their ID. I am trying to add an author field to my post objects like this in PostsController:
public function index() {
$this->set('posts', $this->Post->find('all'));
foreach ($this as $post){
$post['Post']['author'] = $this->User->findById($post['Post']['user_id']);
}
}
however this brings the error that I am calling findById on null. I am very new to php so I think my understanding of how to use the loop may be incorrect. Perhaps there is a better way which does not require the loop?
Controllers in CakePHP, by default, only load their own models. If you need an additional model at some point, you need to load it in manually.
That won't solve your problem though, because you're setting the result for the find() action straight into the view. You'll want to wait with that until you add the users to it. Oh, and you usually can't iterate through $this with foreach, unless your class implements an Iterator-like interface (which controllers should never have a reason to do)
public function index() {
// first load in the User model
$this->loadModel('User');
// store the posts in a local variable first
$posts = $this->Post->find('all');
// loop through the local variable, also keep the index so we can reference
// the post we're modifying
foreach ($posts as $index => $post) {
$post['Post']['author'] = $this->User->findById($post['Post']['user_id']);
// write the modified $post back into the $posts array
$posts[$index] = $post;
}
// **now** you can make $posts available to your view
$this->set('posts', $posts);
}
Once you have this sorted out, read up on linking models together. There's a way to set up your Post model so that it will automatically fill $post['Post']['author'] with the corresponding User, without you having to do that manually.
Better you specify the relation in model.
In Post Model initialize the relation between post and user
public $hasOne = 'User';
Now in controller use Contain() to get linked models data
$posts = $this->Post->find('all')->contain(['User']);
$this->set('posts', $posts);
You will get User object with each post record which you can use to get user name, you do not need to write a separate query to fetch the user name.

Eager load model using with but giving it another name - Laravel 5.2

Is it possible to use eager loading using the with method but giving it another name? Something like:
->with('documents as product', 'documents.documents as categories')
I have a documents table that can be product or categories, eager loading is working but not that friendly to retrieve the documents by just the document name instead of what it really is.
This feature is currently not supported in any Laravel version. Your best bet is to duplicate your relations and name them according to your needs. E.g.:
class Post extends Model
public function documents() {
return $this->hasMany(Document::class);
}
public function products() {
return $this->hasMany(Document::class)
->where('type', 'product'); // Scope the query, if necessary
}
public function categories() {
return $this->hasMany(Document::class)
->where('type', 'category'); // Would a Document really be of type 'category', though? Looks like a code smell.
}
}
$postsWithAllDocs = Post::with('documents')->get();
$postsWithProductsOnly = Post::with('products')->get(); // Only eager load Documents of type 'product'
On a side note, you mention that a Document can be a product or category, which logically doesn't seem to make much sense. Part of the issue could probably be resolved by rethinking the structure of your database and relations.
Eager loading tells "load also this relationship data", so next you can access subject->relation without further queries
if you want to rename the relationship maybe you should do it renaming the relationshp in the model, not in the eager loading
you can also bypass this by adding virtual attributes:
function getProductAttribute(){
return $this->document;
}
leaving eager loading on original document
resulting in product attribute that is the same as document:
$subject->product === $subject->document
I asked myself the same question, and since I didn't find a satisfying answer online, here is what I did.
I had:
$o->load('X');
but I wanted the $o object to have attribute Y with the value of X relation. Since I already had the Y relation defined for $o, I couldn't rename X to Y and finish the job. So I did
$o['Y'] = $o->X();
I know this is not the best solution, but it works for my case :)
Note: load and with produce exactly the same number of sql queries - you need to choose the one which is more appropriate for your situation.

count() returning soft deleted items in laravel

I have a model Comments that uses soft deleting: it has a one-to-many relationship with my Post model.
My site will have a native mobile app associated with it and I need to send a count of the comments to it when I send the information about a post and for some reason it is returning the count WITH the soft deleted items.
I've got the Post array working and sending the comment count using
protected $appends = array('score','commentcount', 'ups', 'downs');
and
public function getCommentcountAttribute()
{
return DB::table('comments')
->where('post_id',$this->id)
->where('deleted_at','=',NULL)
->count();
}
in my post model. I've also tried
public function getCommentcountAttribute()
{
return $this->comments()->count();
}
and
public function getCommentcountAttribute()
{
return $this->comments()->whereNull('deleted_at')->count();
// also: return $this->comments()->where('deleted_at',NULL)->count();
}
also when defining the relationship I've tried adding ->whereNUll('deleted_at') to both the ->hasMany('Comment') and the ->belongsTo('Post') with no luck.
I've checked the database and ran the SQL I'm expecting Fluent and Eloquent to be generating which is
SELECT * FROM `comments` WHERE post_id=31 and deleted_at=null
(31 being the post I'm using to test). Nothing is working. Let me know if you guys need to see anymore specific functions as I'd rather not post my entire models.
I was able to make it work with ->whereRaw('deleted_at = ?',array(NULL)). That seems pretty hacky to me though. I'd gladly accept a better answer.
You have to enable Soft Deleting in your model.
class Comment extends Eloquent {
protected $softDelete = true;
}
That's it.
And you don't need to include the following where clauses in your queries:
return DB::table('comments')
->where('post_id',$this->id)
//->where('deleted_at','=',NULL) // no needed, Laravel by default will include this condition
->count();
public function getCommentcountAttribute()
{
// remove ->whereNull('deleted_at')
return $this->comments()->count();
}
Change your code to:
return \App\Comments::count();
Soft delete works only on models, not queries:
class Comment extends Eloquent
{
protected $softDelete = true;
}
Whilst this is an old post the following should hopefully be helpful with others if they come across this.
For laravel V5 and above.
Add use SoftDeletes; to your model.
If you are trying to get the count that includes soft deletes use the following:
Model::withTrashed()->'yourquery'
If you do not want soft deleted records included then you can follow the normal convection.
Model::select()->get();

Categories