Laravel query with hasmany relationship - laravel

I'm new to laravel so forgive me for the possible noobish question. Also, I did look into all the other 'similar' questions but either they're don't reproduce the right solution or i'm honestly having a hard time wrapping my head around it.
The scenario:
I have a Post model and a Subject model. This is what they look like at the moment.
in Post.php
public function subjects() {
return $this->belongsToMany('Subject', 'posts_subjects');
}
in Subject.php
public function posts() {
return $this->belongsToMany('Post', 'posts_subjects');
}
Now, what i need to achieve is:
In case an query parameter is passed to the request(ie q=Food) i want to return only the posts that contain the subject food in their subjects relationship and none of the others. if nothing is passed, then i just show everything...
This is what my PostsController's index action looks like atm.
public function index() {
$q = Input::get('q');
$posts = Post::all();
return View::make('posts.index', ['posts' => $posts]);
}
How would i go about doing that? would greatly appreciate some help.
Thanks a lot
PS. I'm able to get all the posts with this code.

This is untested code, but it probably will work for you, if you have only one Subject returned by that query:
public function index()
{
if($q = Input::get('q'))
{
if($subject = Subject::with('posts')->where('name', $q)->first())
{
$posts = $subject->posts;
}
else
{
$posts = array();
}
}
else
{
$posts = Post::all();
}
return View::make('posts.index', ['posts' => $posts]);
}

Related

Laravel Model request with several joins

While I am able to make simple requests with Model, I can't say the same for more complicated ones.
I know I don't necessarily have to use Model and can use DB facade but still, I want to know how it's supposed to be done.
Here's a request I made using DB :
DB::table('relationships')
->Join('users','users.id','=','relationships.user_id')
->Join('roles','roles.id','=','relationships.role_id')
->Join('bundles','bundles.id','=','relationships.related_id')
->Join('pools','bundles.id','=','pools.bundle_id')
->whereIn('pools.name',$pools)
->whereIn('roles.name',$roles)
->select('users.first_name','users.last_name','users.mail_address','roles.name AS role_name','bundles.name AS bundle_name', 'pools.name AS pool_name')
->get();
On a first attempt, I tried this:
User::whereHas('relationships', function($req) use($roles) {
$req->whereHas('bundle', function($req){
$req->whereIn('name', $pools);
});
$req->whereHas('role', function ($req){
$req->whereIn('name', $roles);
});
})
->with('relationships', 'relationships.role:id,name', 'relationships.bundle:id,name')
->get();
}
Problem is, using "with" just select everything unconditionally, ignoring previous tests you made earlier (whereHas, whereIn).
So I'd have to again filter on each table in the with statement.
Then I ended up doing this:
$pools = request()->input('pools.*.name');
return $prepReq = User::whereHas('relationships', function($req) use($pools, $roles) {
$req->whereHas('bundle', function($req) use ($pools){
$req->whereHas('pools', function($req) use ($pools){
$req->whereIn('name', $pools);
});
});
$req->whereHas('role', function ($req){
$req->whereIn('name', $roles);
});
})
->with(['relationships' => function ($query) use($pools, $roles){
$query->whereHas('role', function ($query){
$query->whereIn('name', $roles)
->select('id','name');
})->select('id','name');
}])
->get(['id', 'first_name', 'last_name', 'mail_address']);
Then I got lost into this and gave up.
Another thing that made me sweat is that when you go nested using "with", you can select columns only on the last table.
For example: "relations.bundle.pools" => I can select columns on pools but not on relationships or bundles, does that mean i have to imbricate with statements for each table ?
As you can see, I am a bit clueless on how things are supposed to be done
I would like any advice or help regarding this matter
Thanks in advance for your time
When using Laravel, you should be setting up the eloquent relationships for each model
Based on your select statement from above, and assuming you want to get the user, bundle, role, and pool, then I would do the following, may be off depending on how your actual DB and models are set up
// Relationship.php
public function user()
{
return $this->belongsTo(User::class);
}
public function role()
{
return $this->belongsTo(Role::class);
}
public function bundle()
{
return $this->belongsTo(Bundle::class, 'id', 'related_id');
}
// Bundle.php
public function relationship()
{
return $this->hasOne(Relationship::class, 'related_id');
}
// Role.php
public function relationship()
{
return $this->hasOne(Relationship::class);
}
// Pool.php
public function bundle()
{
return $this->belongsTo(Bundle::class);
}
Then you could do something like
Pool::with('bundle.relationship.user')->whereIn('name', $pools);
Role::with('relationship.user')->whereIn('name', $roles);

Laravel Route with Multiple Parameters - How to get correct database result

I am a couple of weeks in to learning Laravel and have come across a problem which I can not find the answer to by myself, or online.
I am building a directory website with urls structured like:
directory.co.uk/parks
directory.co.uk/parks/{county-name}
directory.co.uk/parks/{county-name}/{park-name}
As {park-name} is not unique, I am struggling to return the page for an individual park. The controller needs to look up the county.id using the county.slug and then the park.id using the park.county_id and the park.slug.
I have routes in the web.php file such as:
Route::get('/parks','ParksController#index')->name('parks');
Route::get('/parks/{county}/{park}','ParksController#show')->name('park');
I have Parks and Counties models and (belongsTo and hasMany relationships set up between the two).
I have this is in both models:
public function getRouteKeyName()
{
return 'seo_url';
}
Then in my ParksController, I am at a loss. I currently have:
public function show(Counties $county, Parks $park)
{
//return $park;
//dd($park);
return view('parks.park', ['park'=>$park]);
}
I have also tried the non-Eloquent way:
public function show($county_slug,$park_slug)
{
$county = DB::table('counties')->where('seo_url',$county_slug)->get();
$county_id = $county->pluck('id');
$park = DB::table('parks')->where('county_id', $county_id)->where('seo_url', $park_slug)->get();
//dd($county_id);
//return $park;
return view('parks.park', ['park'=>$park]);
}
This returns a 404 error. Any help would be much appreciated. (I have done a lot of reading on Route model binding, but can not see any examples like mine.)
Laravel has an undocumented feature in its explicit model binding, where the callback can be given the current Route the binding is for. This can allow you to access the other parameters and use them to add conditionals.
Router::bind('park', static function ($value, Route $route) {
$query = Parks::where('seo_url', '=', $value);
if ($route->hasParameter('county')) {
$county = $route->parameter('county');
$query->where('county_id', '=', $county instanceof Counties ? $county->id : $county);
}
return $query->first() ?? abort(404);
});

Laravel : Get all models that their last relationship have some condition

I have two models Post and Comment, i'd like to get all posts that their last comment is active:
// Model Post
public function comments()
{
return $this->hasMany('comments');
}
//Model Comment
public function post()
{
return $this->belongsTo('post');
}
i tried this solution :
public function lastComment()
{
return $this->hasOne('comment')->latest()
}
and in my controller :
$postsWithLastActiveComment = Post::whereHas('lastComment', function($q){
$q->where('active',1);
})->all();
but in this solution if the last comment is not active Previous comment will be taken
I am not sure if there's another simpler way of doing this, but maybe you can try it with a sub-query?
$lastComment = Comment::select('active')
->whereColumn('post_id', 'posts.id')
->latest()
->limit(1)
->getQuery();
$posts = Post::select('posts.*')
->selectSub($lastComment, 'last_comment_is_active')
->having('last_comment_is_active', 1)
->get();
->latest() only orders the posts by created_at so to get only the latest comment you need ->latest()->first()
I think the code below should work !
public function comments()
{
return $this->hasMany('comments');
}
public function lastComment()
{
return $this->comments()->latest()->first();
}
Shouldn't this
$postsWithLastActiveComment = Post::whereHas('lastComment', function($q){
$q->where('active',1);
})->all();
be
$postsWithLastActiveComment = Post::whereHas('lastComment', function($q){
$q->where('active',1);
})->get();
According to your question, the Post model has many Comments. And you want to get the comment from the post where active is one and must be the lastest id.
Get the last comment like the following
public function lastComment()
{
return $this->hasOne('comment')->latest()->take(1);
}
Get all posts which had the lastComment like the following
$latestCommentPosts = Post::whereHas('lastComment')->get()
And filter the latestCommentPosts like the following
$latestCommentPosts->where('active', 1)->get()
Or, you can archive by one query like the following as well.
Post::whereHas('comments', function($q) {
$q->where('active', 1);
})->get()
Like that, you got all the latest comment with active is 1.

Laravel nested eager loading can't work as expected

This problem has troubled me for days, I cannot solve this problem until now, so I have to ask for help.
Below is the relevant code snippet:
Model Category.php
public function child()
{
return $this->hasMany('App\Http\Models\Category', 'pid', 'id');
}
public function logs()
{
return $this->hasMany('App\Http\Models\Log', 'cate_id');
}
public function products()
{
return $this->hasMany('App\Http\Models\Product', 'cate_id');
}
public function newProduct()
{
return $this->products()->orderBy('created_at', 'desc');
}
public function latestLog()
{
return $this->logs()->where([
'product_id' => $this->newProduct()->first()->id,
'status' => 1,
])->orderBy('created_at', 'desc');
}
Controller CategoryController.php
public function getLatestLog()
{
// Category with id 1 with nested eager loading
$subCategory = Category::find(1)
->with(['child.newProduct', 'child.latestLog'])
->first()->child;
// Get latest log for subCategory with id 3
dd($subCategory->find(3)->latestLog);
}
In this case, I want to use nested eager loading to get latest log. But what bothers me is when I add child.checkLatestLog it just outputs empty, but when I delete it, it will output normally.
I think the problem is related to the $this->newProduct()->first()->id variable. Because I tried to manually enter a product ID that exists in the log table, it's worked normal.
It may be my fault, but I don't know where it was wrong. I would like to thank you for asking for help.
update
A solution for this Question:
public function latestLog()
{
return $this->hasManyThrough(
'App\Http\Models\Log',
'App\Http\Models\Product',
'cate_id',
'product_id',
'id',
'id'
)->orderBy('created_at', 'desc');
}
The problem is $this->newProduct()->first()->id: You can't use eager loading with model-dependent attributes like that.
You'll have to use a JOIN solution.

Get all championships that are teams in Eloquent

I have a tournament, a tournament can have many >
public function championships()
{
return $this->hasMany(Championship::class);
}
and a Championship hasOne Category. In Category, I have the isTeam attribute.
Now I need a function that get me all the championships that have the isTeam = 1 in Category table.
public function teamChampionships()
{
}
Of course, I have defined : $tournament->championships, $championship->category
In my controller, I get all of them:
$tournament = Tournament::with('championship.category')->find($tournament->id);
Any idea???
Try
$tournament = Tournament::with(['championships' => function ($query) {
$query->whereHas('category', function($subquery) {
$subquery->where('isTeam', '=', 1);
});
}])->get();
If the above doesn't work, try a different approach. Define isTeam() scope in Category model
public function scopeIsTeam($query) {
return $query->where('isTeam', 1);
}
Then you can use it like this
$tournament = Tournament::with('championships.categoryIsTeam')
->find($tournament->id);
Even better, create another scope in Championship that loads only teams
public function categoryTeam() {
return $this->hasOne(Category::class)->isTeam();
}
Sorry for too much information. One of those should do the job.

Resources