I've got two tables related to each other:
Users:
id | name | email | phone
Posts:
id | user_id | body | start:date | end:date
They're related like: User can have many Posts:
public function posts()
{
return $this->hasMany(Post::class)->with('comments');
}
and in Posts model:
public function users()
{
return $this->belongsTo(User::class, 'user_id');
}
I need to get All Users with Posts where start between [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()] and same condition for end column:
$users = User::whereHas('posts', function($q) use($currentMonth){
return $q->whereBetween('start', [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()])->orWhereBetween('end', [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()]);
})->get();
The main problem which I got is that I didn't get any User if condition is not OK. I need All Users, even if he doesn't have any posts, and the condition isn't valid, but the array of their Posts(or object array related) should be null.
So: I get all users with posts, if he/she matches the condition of date, get's only posts which match this, if he/she doesn't have posts or it didn't match condition in query his/her relation should be null or empty, but user should be added to collection.
Is it possible to achieve?
EDIT:
I got another problem. I've got form with the specific month page view(up to 12 future months). After selecting specific one I'm submitting form and passing it to Controller which look like that:
public function monthPage(Request $request)
{
$start = Carbon::parse($request['month'])->firstOfMonth();
$end = Carbon::parse($request['month'])->lastOfMonth();
$users = User::with(['posts' => function($q) use($start, $end){
return $q->whereBetween('start', [$start->firstOfMonth(), $end->lastOfMonth()])
->orWhereBetween('end', [$start->firstOfMonth(), $end->lastOfMonth()]);
}])->get();
return view('posts.specific-month')
->with('users',$users);
}
If Users has post with start on 20th of March and end on 20th May, the query isn't returning Posts for selection(in dropdown) of April.
I got for each month the page, so it works correct if I select March - it will display it until end of month and If I select May it will be displayed until 20th. If I select April, there's nothing to return, but this date is between 20th of March and 20th of May.
Here you go:
$users = User::with(['posts' => function($q){
return $q->whereBetween('start', [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()])
->orWhereBetween('end', [Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth()]);
}])
->get();
You should create a scope in the Post model:
public function applyDateRange(Carbon $start, Carbon $end)
{
return $this->whereBetween('start', [$start, $end])
->orWhereBetween('end', [$start, $end]);
}
Then call the scope in the relation like this:
$users = User::with(['posts' => function ($q) {
$q->applyDateRange(Carbon::now()->firstOfMonth(), Carbon::now()->lastOfMonth());
}])->get();
Related
Help me please.
I'm trying to write a function where I get all the categories of my forum with the 3 most recently updated Topics in the given categories.
But according to the result, take(3) filters by id (where the id is not higher than 3), and I need to get the last 3 records.
public function index()
{
$forums = Category::with(['posts' => function ($q){
return $q->take(3)->get();
}])->get();
dd($forums);
}
you should order your complete query by update_at descending, only after you can take the first 3.
$q->orderBy('update_at', 'desc')->take(3)->get();
Your Categories table seems to be a different table from posts, so when a post is created or updated you should also set update_at of its category to now.
As far as I know you can not use take() or limit() inside with();
EDIT: solution that was selected by mr.Sardov is to use package staudenmeir/eloquent-eager-limit.
Link is provided below this answer.
So for you need to do is by limit it from model relationship.
For example:
class Category extends Model {
public function posts()
{
return $this->hasMany('App\Models\Post');
}
public function limitPosts()
{
return $this->hasMany('App\Models\Post')
->limit(3);
}
public function limitLatestPosts()
{
return $this->hasMany('App\Models\Post')
->orderBy('created_at', 'desc'). // or use ->latest()
->limit(3);
}
}
And that use it like this:
Category::query()
->with(['limitPosts' => function($query) {
$query->orderBy('created_at', 'desc'); // the last records
}])
->where('id', '<=', 3) // id not higher than 3
->get();
Or
Category::query()
->with('limitLatestPosts')
->where('id', '<=', 3) // id not higher than 3
->get();
Hope this can help you out.
This question is really hard to word, so I apologise for the title!
Lets say I have a Rooms model and a Bookings model, 1 room can have many bookings.
I am building a table with filters, and I need to be able to run a filter on the booking relationship IF there is a booking, else grab the record anyway.
Lets say my room has a booking relationship like so
public function latestBooking(){
return $this->hasOne('App\Models\Bookings', 'room_id', 'id')
->orderBy('expiry_date', 'DESC')->limit(1);
}
The above relationship gets me the latest booking.
I'm running a 'vacant from' filter like so
if ($request->has('filters.vacant_from')) {
$rooms = $rooms->whereHas('latestBooking', function ($query) use ($request) {
$query->where('expiry_date', '<', Carbon::createFromFormat('d/m/Y',$request->input('filters.vacant_from'))->format('Y-m-d'));
});
}
The issue is that this only gets rooms that have a booking and are available after the date specified. I need it to be able to search all rooms, if it does have a booking, check if the expiry date is after the date specified, if it doesn't have a latest booking, get it anyway as its a vacant room.
Basically how would I structure my filter function so that it runs like this
Search all rooms, if it has a latest booking, check its expiry date is after the date specified and only grab it if its true/vacant, if it doesn't have a latest booking then grab it anyway as it is vacant
I have a suggestion for you regarding your latestBookingRelationship. You should create another relationship and add a greater than today condition to it and maybe name it activeLatestBooking.
Then, your latest Booking should comprise all booking rooms irrespective of whether their booking is active or not.
public function activeLatestBooking(){
return $this->hasOne('App\Models\Bookings', 'room_id', 'id')
->where('expiry_date', '>', date('d/m/Y', strtotime('today')))->orderBy('expiry_date', 'DESC')->limit(1);
}
public function latestBooking(){
return $this->hasOne('App\Models\Bookings', 'room_id', 'id')
->orderBy('expiry_date', 'DESC')->limit(1);
}
Then, to answer your question:
if ($request->has('filters.vacant_from')) {
$rooms = $rooms->whereHas('latestBooking', function ($query) use ($request) {
$query->where('expiry_date', '<', Carbon::createFromFormat('d/m/Y',$request->input('filters.vacant_from'))->format('Y-m-d'));
})->orWhereDoesntHave('latestBooking');
}
What the filter query does is this: It gets all latest bookings whose expiry_date is less than the vacant_from date...and the orWhereDoesntHave gets all rooms that have never been booked.
the booking relationship should be like this,
public function latestBooking(){
return $this->hasMany('App\Models\Bookings')->orderBy('expiry_date', 'DESC');
}
as this is a one to many relationship. laravel relationship
and you can write your filter like this,
if ($request->has('filters.vacant_from')) {
$rooms = $rooms->whereHas('latestBooking', function ($query) use ($request) {
$query->whereDate('expiry_date', '<', Carbon::createFromFormat('d/m/Y',$request->input('filters.vacant_from'))->format('Y-m-d'));
});
}
you can use whereDate to run date queries in laravel. Hope this helps
Assuming you have hasMany relation between Rooms and Bookings model with name bookings
$rooms = Rooms::whereHas('bookings', function ($query) use ($request) {
$query->whereDate('expiry_date', '<', Carbon::createFromFormat('d/m/Y',$request->input('filters.vacant_from'))->format('Y-m-d'));
})->orWhereDoesntHave('bookings')->get();
I need to get results from a DB divided by dates such as today, yesterday, this week, last week, etc.
I can easily do this with whereRaw and some SQL:
whereRaw('Date(created_at) = CURDATE()')->get();
I wonder if there is an easier, proper way to do this with Eloquent.
You could create a scope for a particular class like this:
public function scopeYourQuery($query, $user) {
return $query->where('user_id', $user->id)->orderBy('created_at', 'desc')->first();
}
This just gets the first item of a descending ordered list ordered by created_at date per user.
If you wanted something that was between date ranges? You just pass in your date and extend it a bit with some PHP, maybe something like this would work:
public function scopeSomeDateQuery($query, $fetch_date, $user)
{
//clone the users chosen month - so we can make the range until the following month
$also_fetch_date = clone $fetch_date;
$next_month = $also_fetch_date->addMonth();
$next_month = $next_month->format('Y-m-d');
$fetch_date = $fetch_date->format('Y-m-d');
//return the query for the monthname
return $query->orderBy('created_date')->where('created_date', '>=', $fetch_date)->where('created_date', '<', $next_month)->where('user_id', $user->id);
}
This would look in a monthly range (per user) to get an ordered list of items with a created_date in that range.
I'm trying to redesign my Laravel 4.2 code and like to group a list from results over the last days.
code:
public function getTrending($type = null, $category = null)
{
$posts = $this->posts($type, $category)->with('comments', 'votes', 'category', 'user', 'votes.user')
->leftJoin('votes', 'posts.id', '=', 'votes.post_id')
->leftJoin('comments', 'posts.id', '=', 'comments.post_id')
->select('posts.*', DB::raw('count(comments.post_id)*7 + count(votes.post_id)*30 + posts.views as popular'))
->groupBy('posts.id')->with('user')->orderBy('popular', 'desc')->whereMonth('posts.created_at', '>=', date('n', strtotime('-1 month')))
->paginate(perPage());
return $posts;
}
I want to group the results as it is (relevance: comments, votes, visit) + grouping the results on a daily base.
Like:
Today (with Date xx.xx.xx)
result 1 (Max Votes, Comment, ...)
result 2
result 3
Yesterday (with Date xx.xx.xx)
result 4 (Max Votes, Comments, ...)
result 5
result 6
Is this possible?
You can use Carbon;
->groupBy(function($date) {
return Carbon::parse($date->created_at)->format('Y'); // grouping by years
//return Carbon::parse($date->created_at)->format('m'); // grouping by months
});
No I don't believe you can group your data in a single query like that.
Assuming your code is giving you the dataset you desire, without grouping by date, I would loop over the dates I want and get the data for every day and format it with the date heading etc.
I have a list of items (papers) and want to display all papers with a specific area.
Now i want that the user has the ability to filter the list of papers depending on different attributes.
these are the tables/models
Papers
id
name
author_id
year
paperQuality
userRating
Authors
id
name
birthDate
Paper_areas
id
paper_id
area_id
significance
Area
id
name
creationYear
I'm trying to keep Eloquent related. The user should be able to filter after papers.year, papers.paperQuality, papers.Rating, paper_areas_significance, area.creationYear
this is what i have by now
$paper_areas_query = Paper_areas::join('areas', function($join){
$join->on('areas.id', '=', 'paper_areas.area_id')
->where('areas.creationYear', '=', 2011);
});
$paper_areas_query->join('papers', function($join){
$join->on('papers.id', '=', 'paper_areas.paper_id')
->where('papers.year', '=', 2011)
->where('papers.paperQuality', '=', 4)
->where('papers.userRating', '=', 5);
});
$paper_areas_query->join('authors', function($join){
$join->on('authors.id', '=', 'papers.author_id');
});
$paper_areas_query->select('paper_areas.id', 'paper_areas.paper_id', 'paper_areas.area_id');
$paper_areas_query->orderBy('authors.name','asc');
$paper_areas_query->orderBy('papers.title','asc');
$paper_areas_query->groupBy('papers.id');
Now only papers from the year 2011 are displayed, but i'm running into 2 problems
how can i repace the year with a parameter taken from the url?
how is it possible to filter the resultset in a way that i have a collection of Paper_areas? Looking into the code above it doesn't seem to be "nice" like other code.
I would personally modify how you're getting your data, and instead create helper functions inside the models themselves.
In your Papers model, consider something like this:
public function fromYear($year)
{
return $this->where('year', '=', $year);
}
public function author()
{
return $this->belongsTo('Author');
}
And in your Areas model:
public function papers()
{
return $this->belongsToMany('Paper', 'Paper_areas', 'id', 'topic_id');
}
Then, with a given Area instance, you can get all of the authors for all of the papers in that area for a given year:
$id = 1;
$area = Area::find($id);
$papers = $area->papers->fromYear(2011)->get();
foreach($papers as $paper) {
$author = $paper->author;
// ...
}
As far as getting the year from a URL parameter, you can do something like this in your routes:
Route::get('/my-route/{year}', 'MyController#action');
And something like this in your controller:
class MyController
{
public function action($year)
{
$area = /** get area instance **/;
$papers = $area->papers->fromYear($year)->get();
}
}
Afterwards, hitting the route /my-route/2014 would pass the string 2014 into the action method above. You should be able to do basically the same thing for all of your other filtering requirements.