I have a following query,
$users = $q->add('Model_User')
->join('profile.user_id', 'id')
->join('activity.profile_id', 'profile.user_id')
->addCondition('timestamp', '>=', date('Y-m-d'))
->addCondition('profile.isActive', true);
->addCondition('activity.isDelivered', false)
->addCondition('activity.priority', '>=', 2);
Now,
I want to traverse all 'users' their 'profiles' and 'activity' associated with each profile.
Relation between User & Profile is 1:n, relation between Profile and Activity is also 1:n.
Is it possible to get something like this?
foreach($users as $user) {
foreach($user->profile as $profile) {
foreach ($profile->activity as $activity) {
//Some actions
}
}
}
Earlier I have used fetching association via ref() but this is slow when number of users are very huge and I don't want to have multiple queries on DB.
My current setup is,
$users = $q->add('Model_User')
->addCondition('timestamp', '>=', date('Y-m-d'));
foreach($users as $user) {
$profiles = $users->ref('Profile', 'user_id');
foreach(profiles as $profile) {
if($profile['isActive']) {
$activities = $profiles->ref('Activity', 'profile_id');
foreach (activities as $activity) {
if(!$activity['isDelivered'] && $activity['priority'] >= 2) {
//Some actions
}
}
}
}
}
Something like this should work:
class Model_User_ForMailing extends Model_User {
function init() {
parent::init();
// join profile, add profile fields you need later
$join_p = $this->join('profile');
$join_p->addField('email');
// join activity, add activity fields you need later
$join_a = $join_p->join('activity');
$join_a->addField('number','act_number');
$join_a->addField('description','act_descr');
// add conditions, important where you add them
$this->addCondition('timestamp', '>=', date('Y-m-d'));
$join_p->addCondition('isActive', true);
$join_a->addCondition('isDelivered', false);
$join_a->addCondition('priority', '>=', 2);
}
function actionSendMail() {
$to = $this->get('email');
$subject = 'New activity #' . $this->get('act_number');
$message = 'You have new activity with this description: ' .
$this->get('act_descr');
mail($to, $subject, $message);
}
}
class mypage extends Page {
function init() {
parent::init();
$m = $this->add('Model_User_ForMailing');
foreach ($m as $junk) {
$m->actionSendMail();
}
}
}
But this is completely untested example. I posted it here just to give you idea how to work with joins and extended models. In this case there should be only one SQL request and only one loop, because you actually need to loop by mails not by users/profiles/activities.
Related
I create the CRUD App. In this App, the user can add a contact, and include their birthday in that contact.
I have two tables in PHP MySQL: students and users. When their contact at the students' table has a birthday, the user will receive an email.
To test the email work, I'm using MailHog. It's was work.
I test that there are two users with a birthday in that contact.
One user has a notification email. Another one has two notification emails because that contact has two data that are two birthdays.
The problem is, that I want to send one notification to every user. So if one user has many contacts on that birthday, it will give the user one email notification, instead of many emails.
this is the notification in MailHog:
user john has two notifications, instead of one.
this is my code:
public function handle()
{
info('mail first');
sleep(1);
info('mail second');
$student = Student::whereMonth('birth', date('m-d'))
->whereDay('birth', date('d') )
->get();
foreach ($student as $mystudent) {
$user = User::find($mystudent->user_id);
Mail::to($user)->send(new WelcomeMail($mystudent));
}
// dd($userid);
echo "check email";
}
this code in class WelcomeMail
public function __construct($students)
{
$this->data = $students;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view('emails.welcome')->with('students', $this->data);
}
this is in view:
{{ $students}}
I do this modiv in mail command:
$userid = [];
foreach ($student as $mystudent) {
$user = User::find($mystudent->user_id);
array_push($userid, $user);
Mail::to($user)->send(new WelcomeMail($mystudent));
}
At this point, it's still given me the result that the user has many emails for each contact.
hopefully, you help me train logic to solve this one.
+
this the database looks like:
You can start your query from the user, so you're sure that every user is present only once.
I'm assuming you have students() relation present on the User::class model
public function handle()
{
$users = User::wherehas('students', function($studentQuery) {
$studentQuery
->whereMonth('birth', date('m-d'))
->whereDay('birth', date('d'));
})
->with('students', function($studentQuery) {
$studentQuery
->whereMonth('birth', date('m-d'))
->whereDay('birth', date('d'));
})
->get();
foreach ($users as $user) {
Mail::to($user)->send(new WelcomeMail($user->students));
}
echo "check email";
}
$student = Student::whereMonth('birth', date('m-d'))
->whereDay('birth', date('d'))
->groupBy('user_id')
->get();
I'm in a situation where I need to display the last 5 unique commenters information at the top of the comment list as follows screenshot.
comment image
To do this. I did as follows:
Post Model
public function comments()
{
return $this->hasMany(Comment::class);
}
public function commenter_avatars(){
return $this->comments()->distinct('user_id')
->select('id','post_id','user_id','parent_id')
->whereNull('parent_id')
->with('user')->limit(5);
}
My Controller method as follows
public function index() {
$feeds = auth()->user()
->posts()
->with(['user:id,first_name,last_name,username,avatar', 'media', 'commenter_avatars'])
->orderBy('id', 'desc')
->paginate(10);
return PostResource::collection($feeds);
}
I tried to use groupBy and Distinct.. But did't work as expected.
Did I miss something? or Have there any more best way to solve this?
Thank you in advance!
Noted: I am using latest Laravel (8.48ˆ)
I don't know about your joining of post, user and comments table. But i guess, you can do something similar to following.
At first get latest 5 unique user id of one post:
$userIds = Comments::where("post_id", $post_id)->distinct("user_id")->orderBy("id")
->limit(5)->pluck('user_id');
Then, fetch those user information
$users = Users::whereIn("id", $userIds )->get();
Then, you can return those users
UPDATE
You may use map() to fetch and reorder output. Following is an idea for you:
In Controller:
public function index(Request $request) {
$skipNumber = $request->input("skip"); // this is need for offsetting purpose
$userIds = [];
$feeds = Posts::with("comments")->where("comments.user_id", Auth::id())
->skip($skipNumber)->take(10)->orderBy('comments.id', 'desc')
->map(function ($item) use($userIds){
$users = [];
$count = 0;
foreach($item["comments"] as $comment) {
if(!in_array($comment["user_id"], $userIds) && $count < 5){
$count++;
$userIds.push($comment["user_id"])
$user = User::where("id", $comment["user_id"])->first();
$users.push($user);
}
if($count == 5) break;
}
$data = [
"post" => $item,
"latest_users" => $users
];
return $data;
})->get();
return PostResource::collection($feeds);
}
My code syntax may be slightly wrong. Hopefully you will get the idea.
I have solved this issue by using eloquent-eager-limit
https://github.com/staudenmeir/eloquent-eager-limit
Edit: For clarity, this is Laravel 5.8.
This is for a HR app I'm working on.
They requested a report to show people who have punched in late. Sure, no problem I thought.
So I have a form with some custom parameters the user can punch in, start_date, end_date, wage, and an array of departments.
public function show()
{
request()->validate([
'start_date' => 'required|date|before_or_equal:today'
]);
$start = Carbon::parse(request('start_date'));
$end = request('end_date') ? Carbon::parse(request('end_date')) : today();
$wage = request('wage');
$departments = request('departments');
$query = EmployeePunch::with([
'employee' => function($query) use ($wage, $departments) {
// IF I UN COMMENT THESE, IN THE FILTER BLOCK BELOW, THE EMPLOYEE BECOMES UNDEFINED.
// if($wage != null) {
// $query->where('hourly', $wage);
// }
// if($departments) {
// $query->whereIn('department_id', $departments);
// }
},
'employee.group',
'employee.department'
])
->whereBetween('punch_time', [$start->startOfDay(), $end->endOfDay()])
// only care about punch in for the day
->where('type', 1);
$results = $query->get();
$latePunches = $results->filter(function ($i) {
$day = strtolower($i->punch_time->format('D'));
$startTime = Carbon::parse(sprintf('%s %s',
$i->punch_time->format('d-m-Y'),
$i->employee->group[$day.'_start_time'])
);
return $i->punch_time->isAfter($startTime)
&& $i->punch_time->diffInMinutes($startTime) >= 5;
});
return view('hr.employeeLateReport.show', compact('latePunches'));
}
So, my problem is in my eager loading and I can't figure this out. If I uncomment the filters in the eager loading of employees, in the filter block near the end of the code block, the $i->employee becomes undefined. If omit the filters, everything works peachy. I've checked the queries being produced and it all looks great.
Any help would be greatly appreciated.
Here's the relationship methods
Employee.php
public function punches()
{
return $this->hasMany(EmployeePunch::class);
}
public function group()
{
return $this->belongsTo(Group::class);
}
public function department()
{
return $this->belongsTo(Department::class)->withDefault();
}
EmployeePunch.php
public function employee()
{
return $this->belongsTo(Employee::class);
}
SQL Output
Try and use whereHas and nest whereBetween:
$query = EmployeePunch::with([
'employee' => function($query) use ($wage, $departments) {
if($wage != null) {
$query->where('hourly', $wage);
}
if($departments) {
$query->whereIn('department_id', $departments);
}
},
'employee.group',
'employee.department'
])->whereHas('employee', function($q) use($start, $end) {
$q->whereBetween('punch_time', [$start->startOfDay(), $end->endOfDay()]);
})->where('type', 1);
I've got 3 models: Client, Organization nad Industry. I want to filter and display clients, that are assigned to any organization in choosen Industry.
Relations:
Client N-N Organization N-N Industry
One client can have multiple organizations and one organization can have multiple industries. I use form with GET method.
My controller:
class ClientListsController extends Controller
{
public function index()
{
$clients = new Client;
$queries = array();
$columns = ['statuses'];
foreach ($columns as $column) {
if (request()->has($column)) {
if ($column == 'industries') {
// filter by industries
}
if ($column == 'statuses' && request($column) == 1 ) {
$clients = optional($clients->has('organizations'))->paginate(100, ['*'], 'client_page');
}else if($column == 'statuses' && request($column) == null ){
$clients = Client::paginate(100, ['*'], 'client_page');
}
else{
$clients = $clients->whereDoesntHave('organizations')->paginate(100, ['*'], 'client_page');
}
$queries[$column] = request($column);
}else{
$clients = Client::paginate(100, ['*'], 'client_page');
}
}
$organizations = Organization::paginate(100, ['*'], 'org_page');
return view('clientLists.index')->with(['clients' => $clients, 'organizations' => $organizations]);
}
}
I've tried multiple things, such as looping through current result with organizations, but that returned true or false whe using has().
I've also tried using $clients = Client::all() but my point is to paginate it further so I've worked with Client::where('id', '*') so I didn't get collection, but that caused, that statuses filter does not work.
I'm working with Laravel 6. Do you have any idea how to make universal filtering with pagination? Thanks a lot!
EDIT
relevant relationship in details (f.e. pivot table for Client - Organization is client_organization) with foreign keys defined.
App\Client
public function Organizations()
{
return $this->belongsToMany('App\Organization')->withTimestamps();
}
App\Organization
public function Clients()
{
return $this->belongsToMany('App\Client')->withTimestamps();
}
public function Industries()
{
return $this->belongsToMany('App\Industry')->withTimestamps();
}
App\Industry
public function Organizations()
{
return $this->belongsToMany('App\Organization')->withTimestamps();
}
EDIT 2:
I'm trying to achieve my goal through SQL query, but I'm stuck with JOINS with many-to-many relationships.
$clients = DB::table('clients')
->leftJoin('client_organization', 'client_organization.client_id', '=', 'clients.id')->leftJoin('industry_organization', 'industry_organization.organization_id', '=', 'organizations.id')
->whereIn('industry_organization.industry_id', request($column))
->paginate(100, ['*'], 'client_page');
}
select
count(*) as aggregate
from
`clients`
left join `client_organization` on `client_organization`.`client_id` = `clients`.`id`
left join `industry_organization` on `industry_organization`.`organization_id` = `organizations`.`id`
where
`industry_organization`.`industry_id` in (2)`
This query returns Column not found: 1054 Unknown column 'organizations.id' in 'on clause'
So, if you have the $industry you can find all of the Organisations within it using your relationships:
$organisations = $industry->organisations();
Then, you want to get all clients for each organisation:
$clients = [];
foreach ($organisations as $organisation) {
$clients[$organisation] = $orgination->clients()->toArray();
}
Or, alternatively, to give it a go all in one query:
$industries = request($column);
$clients = DB::table('clients')
->leftJoin('client_organization', 'client_organization.client_id', '=', 'clients.id')
->leftJoin('organizations', 'organizations.id', '=', 'client_organization.id')
->leftJoin('industry_organization', 'industry_organization.organization_id', '=', 'organizations.id')
->whereIn('organisations.industry_id', $industries)
->paginate(10);
Try this line by line to check it out and debug it easily (try ->get() after the first leftJoin and check that it works; if that's okay, try it after the second.
I managed to do it this way:
if (request()->industries[0] != "") {
$clients = $clients->with('organizations')->whereHas('organizations', function($query){
$query->with('industries')->whereHas('industries', function($query2){
$query2->whereIn('industry_id', request()->industries);
});
});
}
Thanks for your help boys :)
here is my current issue im trying to figure out
i have a laravel query for example below
$users = User::where('country', $country)
->where('age', '>=' , $lfmin)
->where('age', '<=' , $lfmax)
->get();
return $users;
and this works out all well and good. but I now have another sql table called datingblockedusers. each record in that table has the user id of the blockee and blocker. I also created a static function in datingblockeduser model that goes like this
public static function checkblock($id1, $id2)
{
//check user is blocked
$query = Datingblockeduser::where('uone', $id1)->where('utwo', $id2)->get();
if($query->count() > 0)
{
return true;
}
else
{
$query = Datingblockeduser::where('utwo', $id1)->where('uone', $id2)->get();
if($query->count() > 0)
{
return true;
}
else
{
return false;
}
}
}
How do i filter my main query such that the id of each user in that query is not in the datingblockeduser table under the fields uone or utwo (user one or user two)
EDIT: I wanted to implement a block list kind of related to a friends list. I created a new migration as such
Schema::create('blocked_user', function(Blueprint $table) {
$table->increments('id');
$table->integer('blocked_id')->unsigned()->index();
$table->integer('user_id')->unsigned()->index();
$table->foreign('blocked_id')->references('id')->on('users')->onDelete('cascade');
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
and in the user model i did this
// blocked
public function blocked()
{
$blocked = $this->belongsToMany('User', 'blocked_user', 'user_id', 'blocked_id');
return $blocked;
}
public function block_user($blocked_id)
{
$this->blocked()->attach($blocked_id); // add friend
$blocked = User::find($blocked_id); // find your friend, and...
$blocked->blocked()->attach($this->id); // add yourself, too
}
public function remove_blocked($blocked_id)
{
$this->blocked()->detach($blocked_id); // remove friend
$blocked = User::find($blocked_id); // find your friend, and...
$blocked->blocked()->detach($this->id); // remove yourself, too
}
now can i now use it as part of the query above or any other query from now on to make sure i only return users who are not on the blocked list?
why don't you query all blocked user_id than change main query using whereNotIn()
// get blocked user id by authenticated user. (change where condition yourself)
$blockedId = Datingblockeduser::where('blocker_id', auth()->id())->pluck('id');
$users = User::where('country', $country)
->whereNotIn('id', $blockedId)
->where('age', '>=' , $lfmin)
->where('age', '<=' , $lfmax)
->get();