Fetching data from multiple tables in laravel5 - laravel

I have two tables posts and sharedposts. sharedposts table has columns "user_id", "updated_at" and "post_id" where as posts table has 'id', 'title' and 'body'. I know about the relations to link both tables. All i want to do is to display each post ordered by 'updated_at' from both the tables. Please help with both query builder and eloquent way. Thanku

The code is not tested, but you can try this:
Post::whereHas('sharedpost',function($query){
$query->orderBy('updated_at');
})->get()

Maybe a bit different approach if there won't be to much of the updates of the models? This way I suppose the read should be faster (somebody please correct me if I'm wrong here)
Add another field to the table by creating new migration and adding
$table->timestamp('last_updated');
Then using Events update the field whenever the Post or SharedPost is updated by adding this to AppServiceProvider:
public function boot()
{
Post::saving(function ($post) {
$post->last_updated = Carbon::now();
});
}
I'm not sure if this will work though and it would be appreciated if you returned a feedback as I'm lazy to try it out myself at the moment :). Worse case scenario you might need to change the method as:
public function boot()
{
SharedPost::saved(function ($sharedpost) {
$sharedpost->post->last_updated = Carbon::now();
$sharedpost->save();
});
}
or you could check out Observers if you don't want the code in the AppServiceProvider. Haven't used it but it seems straightforward.
Then you could simply Post::orderBy('last_updated')->get();
This way you will have one or two more inputs to your database but I think (and guys please correct me if I'm wrong) the get() should be faster. Keep in mind I'm far from a MySQL expert.
And in the end, you can make it even faster by saving this data to Cache using Redis.

Related

n-n relation in Backpackforlaravel: doesn't delete corresponding items

I'm having troubles with a n-n relationship in my Backpackforlaravel app. I have Registrations and Sessions, the setup in the model looks like this:
Registration:
public function sessions()
{
return $this->belongsToMany(\App\Models\Session::class);
}
Session:
public function registrations()
{
return $this->belongsToMany(\App\Models\Registration::class);
}
So the setup seems to be fine, and I also have a registration_session table in my database.
What I want to achieve is, that whenever a Session gets deleted, I want to delete all the entries in the registration_session table, but also all the Registrations. I thought the deletion of the entries in the registration_session table maybe is a standard, but it didn't delete them when I deleted a session. In order to achieve both, I did the following in the destroy function of my SessionCrudController:
public function destroy($id)
{
$this->crud->hasAccessOrFail('delete');
foreach ($this->crud->getCurrentEntry()->registrations as $registration) {
$registration->delete();
}
DB::table('registration_session')->where('session_id', $id)->delete(); //really required?
return $this->crud->delete($id);
}
I have the feeling that I'm doing things way more complicated than I should, so I would appreciate any recommendations.
Update: this is how I store the data initially in the table...the information is coming from a form, so it's not added in a CRUD panel:
DB::table('registration_session')->insert([
'registration_id' => $reg->id,
'session_id' => $sesId
]);
By default, Backpack or Laravel don't concern themselves with deleting related entries, because they assume your Database layer will handle it. With most DBMSs you can specify such rules, and Laravel makes it easy to do so in their migrations. Take a look at foreign key constraints in the Laravel docs, it's pretty easy to build your migration in such a way that any time one item gets deleted, the related items will to, using "cascade":
$table->foreignId('user_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');

Prevent duplicate queries and N+1 problem in Laravel collection

I'm currently working on a simple Laravel project where I need to get the posts of the users I'm following. With the code below, I can get the posts but I also add a lot of duplicate queries and an N+1 issue on the Authenticated user. So it's becoming sort of a head scratcher. I've looked though other similar scenarios online but I haven't been able to pinpoint what I'm doing wrong. Perhaps there is a better way. Currently, I have on the User model:
public function usersImFollowing()
{
return $this->belongsToMany(User::class, 'follow_user', 'user_id', 'following_id')
->withPivot('is_following', 'is_blocked')
->wherePivot('is_following', true)
->wherePivot('is_blocked', false)
->paginate(3);
}
public function userPosts()
{
return $this->hasMany(Post::class, 'postable_id', 'id')
->where('postable_type', User::class);
}
As you can see, I am using two booleans to determine if a user is following or is blocked. Also, the Post model is a polymorphic model. There are several things I've tried, among them, I tried a hasManyThrough, without using the hasMany Posts relationship above. It got the posts for each user but since I'm using the booleans above, I couldn't use them in the hasManyThrough, it simply got the posts based on the following_id, whether or not the user was following or was blocked became irrelevant.
Then in a separate service class, I tried the methods below (I'm using a separate class to maintain the code easier). They both get the posts for each user but add an N+1 problem and 12 duplicate queries based on 5 posts from 2 users. I will also need to filter the results based on some conditions, so it will probably add more queries. Additionally, I'm using a Laravel resource collection that would pull other items for each post, such as images, comments, etc., so the amount of queries would increase even more. Not sure, perhaps I'm doing too much and there is an easier way:
Either:
$following = $request->user()->usersImFollowing();
$posts = $following->map(function($user){
return $user->userPosts()->get();
})->flatten(1);
return $posts;
Or
$postsfromfollowing = [];
$following = $request->user()->usersImFollowing()->each(function($user) use (&$postsfromfollowing){
array_push($postsfromfollowing,$user->userPosts);
});
$posts = Arr::flatten($postsfromfollowing);
return $posts;
Maybe you could use scopes to do little celanup of code and generated sql.
In User model something like
public function scopeIsFollowedBy(Builder $query, int $followerId) {
return $query->where('following_id', '=', $followerId);
}
And in Post model
public function scopeIsFollowedBy(Builder $query, int $followerId) {
return $query->whereHas('user', function($q) use ($followerId) {
$q->isFollowedBy($followerId);
});
}
You can use it then in coltroller like any other condition like this:
Post::isFollowedBy($followerId)->...otherConditions...->get();
The SQL generated won't go through foreach but only add one IF EXISTS select (generated by whereHas part of the code)
More on local scopes in Laravel is here https://laravel.com/docs/8.x/eloquent#local-scopes

Laravel Model factories, one to many relationship without database access

For my tests (Laravel 6) I am trying to use factories with make(), in order to bypass the DB operations just creating a new instance of the model.
Now, I am having an headache to make things work with my one to many relationship.
A skeleton of my code just to have an overview:
class Fine extends Model {
// Other things here
public function articles()
{
return $this->hasMany('App\Model\Article');
}
}
where Article does not have any other relationships.
Now, I would like to create a Fine factory to create an instance in which I can parse $fine->articles, all without any database interaction.
Here's the basic Fine factory that works (without using articles):
$factory->define(Fine::class, function (Faker $faker) {
return [
"id" => 10,
Fine::FIELDONE => 'xxx',
Fine::FIELDTWO => 'yyy',
];
});
that I use with  $fine = factory(Fine::class)->make(); .
Now I need to "prefill" articles. I have tried with  hydrate() but it does not work.
I have tried with afterMaking() with different combinations of code:
$factory->afterMaking(Fine::class, function (Fine $fine, Faker $faker) {
$articles = factory(\App\Model\Article::class, 3)->make([\App\Model\Article::FINE_ID => $fine->id]);
// TEST WITH SAVEMANY: $fine->saveMany(factory(\App\Model\Article::class, 10)->make());
// SAME WITH CREATEMANY
// TEST WITH HYDRATE: $multa->articles()->hydrate([$article]);
// $multa->load('articles');
});
Just to make a point I have tried different roads but, I admit, I am a bit lost.
Basically I end up with the classical
Illuminate\Database\QueryException: SQLSTATE[42S02]: Base table or view not found
error, so I am still accessing the database.
Now, I am starting to think that factories are a proper choice only if at the end one is going to store data on the database (I am sure that saveMany() is going to work if I use create() and not make()).
Can you please give me an hint on how to accomplish this task? If factories are not the right way to do it I am more than open to change my choice, too.
Thanks in advance!
Lorenzo
You can set this information on a non existing model instance if you really have to:
$fine->setRelation(
'articles',
factory(\App\Model\Article::class, 3)->make()
)
It depends what you plan on doing with this model and its relationship since there are no IDs set so they are not really related in that way.

Laravel - why is a Model's relations not being loaded?

I have a Laravel 5.3 site and I think maybe I have some weird things going on due to some actions happening in API controllers and some happening in the regular controllers.
Or maybe an issue where at some times I am dealing with a Model, and sometimes with an Eloquent Collection.
The issue is, I am trying to retrieve relations on a Model and am getting null.
For instance, I have course Model that relates to week Model.
In course Model I get week items as
public function weeks()
{
return $this->hasMany(Week::class, 'course_id');
}
In backend, these relations get sent in this way:
$course->load('weeks')
All is good.
But when course item gets deleted and I try and take action in the week controller as
static::deleting(function($course) {
$course->weeks->delete();
});
$course->weeks is null. At that time I see in the database that all is good and this course items does indeed have a week item related, but $course shows 0 relations.
So something odd is happening where $course->webinars is not grabbing the week items related.
Is there something that I am fundamentally doing wrong? Maybe it is because in the models I have these sorts of statements:
protected $table = 'Week';
Are these preventing the relations from being pulled? I always thought that is I had some function in a model that returns relations that those relations would always be available when I use syntax $course->weeks.
Ideas?
Thanks again,
You can simply setup migrations to automatically delete from weeks if you delete a course, provided you have foreign key relationship.
If you have a column course_id in weeks table then add this into your migration
$table->foreign('course_id')
->references('id')->on('courses')
->onDelete('cascade')
I think you can use Observers. In your AppServiceProvider, first register the observer.
public function boot()
{
Course::observe(CourseObserver::class);
}
Now, add an Observer class.
class CourseObserver
{
public function deleting(Course $course)
{
$course->weeks()->delete();
}
}

Laravel - latest record from relationship in whereHas()

This is my first post in here, so please forgive any mistakes :)
I'm currently working on the project of stock management application (Laravel). I came to the point where anything I do doesn't work, so now I beg for help with it.
I have a table with products, of which some are in the relationship with the others. Everything happens in one table. If the product has a child, the child overrides the parent.
products table view
Then, all the queries I run on them use the following logic:
If the item doesn't have any child, use it.
If the item has children, use the latest child (highest id)
Now I have the relationships created in model file:
public function childItems(){
return $this->hasMany('\App\OrderItem','parent_id');
}
public function parentItem(){
return $this->belongsTo('\App\OrderItem','parent_id');
}
public function latestChild(){
return $this->hasOne('\App\OrderItem','parent_id')->orderBy('id','desc')->limit(1);
}
The problem with latestChild() relationship is, that when you run this query:
\App\OrderItem::find(7)->latestChild()->get()
It works fine and returns only one (latest)(id 6) record in relationship - to do it I had to add orderBy and limit to hasOne().
But when I want to use this relationship in scopes, so in whereHas method, it doesn't work properly, as takes any of the children instead of the latest one.
public function scopeDue($query){
return $query->where(function($q){
$q->has('childItems','==',0)->has('parentItem','==',0)->whereDate('due_date','=', Carbon::today()->toDateString())->whereNull('return_date');
})->orWhere(function($q2){
$q2->has('childItems')->has('parentItem','==',0)->whereHas('childItems',function($q3) use($q2){
$q3->whereDate('due_date','=', Carbon::today()->toDateString())->whereNull('return_date');
});
})->with('latestChild');
}
However, with() at the end returns the right record.
I think, the reason it works so is because my relationship latestChild() returns all the children (despite hasOne()) and when i use it in whereHas it ignores the filtering functions I applied.
I know it's a little bit complex from what I describe, but to explain it better I will use an example. Executing the following in tinker
\App\OrderItem::due()->get();
Should return only record id 2, as the number seven has children, where of course id 5 is due, but the latest child is id 6 which is not due.
I hope I've explained it enough to let you help me, as I'm already going crazy with it.
If you have any ideas on how I could achieve what I need by changing exisiting one or changing the whole logic of it, please help!
Thanks,
Darek
Try this one:
->with(
[
'latestChild' => function (HasOne $query) {
return $query->latest('id')->limit(1);
}
]
);
I think the problem is in your latestChild() method where you do a limit(1). Why don't you try the last() method instead?
So:
public function latestChild(){
return $this->hasOne('\App\OrderItem','parent_id')->last();
}
EDIT:
What about returning the value like this:
public function latestChild(){
$item = App\OrderItem::all()->last();
return $item;
}

Resources