Laravel get conversation where users in array - php

I am making a messaging app, and i need to check if a conversation already exists that has a certain list of users (no more, no less). I have this model:
class Conversation{
public function users(){
return $this->belongsToMany('App\User');
}
public function messages(){
return $this->hasMany('App\Message');
}
}
I have these tables:
conversations:
id
user_id <- the owner of the conversation
users:
id
email
password
conversation_user:
id
conversation_id
user_id
I want to make a post request like this:
{
"users": [1,4,6], <- user ids
"message": "Some message"
}
If a conversation already exists with all and only users 1,4,6, the message should be added to that conversation to avoid having duplicate conversations in the database. Otherwise i will make a new conversation with the specified users.
This is the best i have been able to do so far:
$existing_conversation = $user->conversations()->whereHas('users',
function($query) use ($data){
$query->whereIn('user_id', $data['users']);
}
)->has('users', '=', count($data['users']));
But it just returns the conversations that has exactly the amount of users that was in the users array. It ignores the inner query..
Does anyone have an idea for this? :)

You can try the following query
$existing_conversation = $user->conversations()->wherePivotIn('user_id', $data['users'])->has('users', count($data['users'])->get();
Haven't tested, should work I think.
UPDATE
Not a very elegant solution, however it works. You can add some helper methods in your controller like
//Get user's conversations with no of users equal to count($data['users']);
protected function get_conversations_with_equal_users(User $user, array $user_ids)
{
return $user->conversations()
->wherePivotIn('user_id', $user_ids)
->has('users', '=', count($user_ids))
->get();
}
//Get the id of a user conversation with exactly same users as $data['users'] if it exists otherwise it will return 0;
protected function get_existing_conversation_id(User $user, array $user_ids)
{
$existing_conversation_id = 0;
$user_conversations_with_equal_users = $this->get_conversations_with_equal_users($user, $user_ids);
foreach($user_conversations_with_equal_users as $conv)
{
$ids = [];
foreach($conv->users as $user)
{
$ids[] = $user->id;
}
if($this->array_equal($user_ids, $ids))
{
$existing_conversation_id = $conv->id;
}
}
return $existing_conversation_id;
}
//Function to compare two arrays for equality.
protected function array_equal($a, $b) {
return (
is_array($a) && is_array($b) &&
count($a) == count($b) &&
array_diff($a, $b) === array_diff($b, $a)
);
}
The you can use the following in your controller to get the existing conversation for user (if it exists)
$existing_conversation_id = $this->get_existing_conversation_id($user, $data['users']);
if($existing_conversation_id)
{
$existing_conversation = Conversation::with('users')
->whereId($existing_conversation_id)
->get();
}

Related

Laravel/LaraCSV Managing Complex Relationships

I'm running into some issues with my Collection/Model relationships with regards to LaraCSV. Here is its documentation: https://github.com/usmanhalalit/laracsv#full-documentation. I have 3 models that interact right now: Doctor, Patient, Script
Doctor belongsToMany Patient
Patient belongsToMany Doctor
Patient hasMany Script
Script belongsTo Patient
I also created a relationship link inside of my Doctor model that can be used to tie Doctor to Script, but does not appear to work in this instance:
public function scripts() {
$this->load(['patients.scripts' => function($query) use (&$relation) {
$relation = $query;
}]);
return $relation;
}
What I am attempting to do is allow admin staff and our users to download CSV files that contain all of their scripts. While this works fine for admin staff as I can reference the models directly, I am not able to make it work for users because they are tied to the doctors, and I cannot seem to tie this into scripts as normal. Here is a perfectly working version for admin staff:
$doctors = Doctor::orderBy('last_name', 'asc')->get();
$patients = Patient::orderBy('last_name', 'asc')->get();
$scripts = Script::orderBy('prescribe_date', 'desc')->get();
$csvExporter->beforeEach(function ($script) use ($doctors, $patients) {
$patient = $patients->where('id', $script->patient_id)->first();
$doctor = $patient->doctors->first();
$script->patient = $patient->full_name;
$script->doctor = $doctor->full_name;
});
Here is how the user-specific version appears:
$doctors = User::
find(Auth::user()->id)
->doctors()
->orderBy('last_name', 'asc')
->get();
$patients = Patient::orderBy('last_name', 'asc')->get();
$scripts = $doctors->scripts()->get();
Trying to chain in my Doctor model scripts() function results in an error: Method Illuminate\Database\Eloquent\Collection::scripts does not exist.
$doctors = User::
find(Auth::user()->id)
->doctors()
->orderBy('last_name', 'asc')
->get();
$patients = array();
$scripts = array();
foreach ($doctors as $doctor_fe) {
foreach ($doctor_fe->patients as $patient_fe) {
$patients[] = $patient_fe;
foreach ($patient_fe->scripts as $script_fe) {
$scripts[] = $script_fe;
}
}
}
I also tried to pull the information using arrays, but unfortunately, it must be a Collection passed in via this error: Argument 1 passed to Laracsv\Export::addCsvRows() must be an instance of Illuminate\Database\Eloquent\Collection, array given
I settled by placing all of the patients belonging to the user's doctors through a foreach loop, then using another one to grab the patient's id. I then took the patient's id array and used the whereIn function to compare the Script's patient_id field to get the correct strips.
$doctors = User::
find(Auth::user()->id)
->doctors()
->orderBy('last_name', 'asc')
->get();
$patients_array = array();
foreach ($doctors as $doctor_fe) {
$patients_fe = $doctor_fe->patients;
foreach ($patients_fe as $patient_fe) {
$patients_array[] = $patient_fe->id;
}
}
$patients = Patient::orderBy('last_name', 'asc')->get();
$scripts = Script::whereIn('patient_id', $patients_array)->get();
$csvExporter->beforeEach(function ($script) use ($doctors, $patients) {
$patient = $patients->where('id', $script->patient_id)->first();
$patient_initials = substr($patient->first_name, 0, 1) . substr($patient->last_name, 0, 1);
$doctor = $patient->doctors->first();
$script->patient = $patient_initials;
$script->doctor = $doctor->full_name;
});
If I interpret you question correctly, you want to get all scripts of all patients of a doctor. Laravel provides the hasManyThrough() Method for this:
class Doctor extends Model
{
/**
* Get all of the scripts for the patient.
*/
public function scripts()
{
return $this->hasManyThrough(App\Script::class, App\Patient::class);
}
}
The first param is the model you want to get (the scripts); the 2nd param is the intermediate model (the patient).
To use it:
$doctor = Doctor::first();
$scripts = $doctor->scripts;

Compare laravel query results

Inside the $get_user and $get_code queries they both have a group_id.
I have dd(); them Both and made 100% sure.
the $get_user query has multiple group_id's and the $get_code only has one group_id which is equal to one of the $get_user group_id's.
The goal at the moment is to create a group_id match query.
Get the code that has a group ID equal to one of the $get_user group_id's
public function getCodesViewQr($code_id)
{
$userid = Auth::id();
$get_user = GroupUser::all()->where('user_id',$userid);
$get_code = Code::all()->where('id',$code_id);
$group_match = GroupUser::where('group_id', $get_code->group_id);
$view['get_users'] = $get_user;
$view['get_codes'] = $get_code;
$view['group_matchs'] = $group_match;
return view('codes.view_qr_code', $view);
}
The group match query does not work. $get_code->group_id does not get the code group_id.
If there is a match then set $match equal to rue. else $match is False
$group_match = GroupUser::where('group_id', $get_code->group_id);
I'm using two Models Code and GroupUser
My Code table is like this :
-id
-group_id (This is the only on important right now)
-code_type
My GroupUser table is like this :
-id
-group_id (This is the only on important right now)
-user_id
-user_role
I have linked the Models
Inside my Code Model I have the relationship to GroupUser
public function group_user()
{
return $this->belongsto('App\GroupUser');
}
And Inside my GroupUser Model I have the relationship to Code
public function code()
{
return $this->belongsto('App\Code');
}
Inside My Code controller I have included my models.
use App\Code;
use App\GroupUser;
Hi guys so I had some help from a guy I work with and this is the solution he came up with. We made a few adjustments. all the Databases and results stayed the same. we just changed the method we used to get the results.
I really appreciate all the help from #linktoahref
public function view_code($random)
{
$code = Code::where('random', $random)->first();
$view['code'] = $code;
if ($code->code_type == 1)
{
// Its a coupon
if (!empty(Auth::user()))
{
// Someones is logged in
$user = Auth::user();
$view['user'] = $user;
$user_groups = GroupUser::where('user_id',$user->id)->pluck('group_id')->toArray();
if (in_array($code->group_id, $user_groups))
{
// The user is an admin of this code
return view('view_codes.coupon_admin', $view);
}else
{
// Save the code to that users account
return view('view_codes.generic_code', $view);
}
}else
{
// Anon
return view('view_codes.coupon_anon', $view);
}
}elseif ($code->code_type == 2)
{
// Voucher..
}else
{
// We don't know how to deal with that code type
}
}
$get_code = Code::find($code_id);
// Check if the code isn't null, else give a fallback to group_id
$group_id = 0;
if (! is_null($get_code)) {
$group_id = $get_code->group_id;
}
$group_match = GroupUser::where('group_id', $group_id)
->get();
$match = FALSE;
if ($group_match->count()) {
$match = TRUE;
}

Laravel - query depending on user

In Laravel I have a scenario in which different users can go to a view blade where they can see posts they have created.
At the minute I'm just passing in all the data, but I'm wondering how to pass data to a view depending on the user.
E.g if I'm a root user I get to see everything so like
Post::get()
Then
return view('someview', compact('post')
Which would return the posts
Essentially what Im trying is something like this...
if(user->role = their role) then you get query 1 else you get query 2
Do you think this is acheivable using conditional query scopes?
UPDATE
Is this a horrible solution?
if($user->department == "Loans")
{
echo "you are from loans FAM";
$articles = Article::where('department', '=', 'Loans')->get();
}
else if($user->department == "Digital")
{
echo "you are from digital FAM";
$articles = Article::where('department', '=', 'Digital')->get();
}
else if($user->department == "Consulting")
{
echo "you are from Consulting FAM";
$articles = Article::where('department', '=', 'Consulting')->get();
}
You could achieve that with a query scope if you wanted to. Something like this:
class Post extends Model
{
// ...
public function scopeByUser($query, User $user)
{
// If the user is not an admin, show only posts they've created
if (!$user->hasRole('admin')) {
return $query->where('created_by', $user->id);
}
return $query;
}
}
Then you can use it like this:
$posts = Post::byUser($user)->get();
In response to your update:
class Article extends Model
{
// ...
public function scopeByUser($query, User $user)
{
// If the user is not an admin, show articles by their department.
// Chaining another where(column, condition) results in an AND in
// the WHERE clause
if (!$user->hasRole('admin')) {
// WHERE department = X AND another_column = another_value
return $query->where('department', $user->department)
->where('another_column', 'another_value');
}
// If the user is an admin, don't add any extra where clauses, so everything is returned.
return $query;
}
}
You would use this in the same kind of way as above.
Article::byUser($user)->get();

Laravel relationship count()

I want to get a total user transaction (specific user) with relationship.
I've done it but i'm curious is my way is good approach.
//User Model
public function Transaction()
{
return $this->hasMany(Transaction::class);
}
//Merchant Model
public function Transaction()
{
return $this->hasMany(Transaction::class);
}
public function countTransaction()
{
return $this->hasOne(Transaction::class)
->where('user_id', Request::get('user_id'))
->groupBy('merchant_id');
}
public function getCountTransactionAttribute()
{
if ($this->relationLoaded('countTransaction'))
$this->load('countTransaction');
$related = $this->getRelation('countTransaction');
return ($related) ? (int)$related->total_transaction : 0;
}
//controller
$merchant = Merchant::with('countTransaction')->get();
What make me curious is part inside countTransaction. I put where where('user_id', Request::get('user_id')) directly inside the model.
is it good approach or any other way to get specific way?
expected result:
"merchant:"{
"name": "example"
"username" : "example"
"transactions": {
"count_transactions: "4" //4 came from a specific user.
}
}
I need to get the merchant data with the transaction count for specific user. This query is based on logged in user. so when a user access merchant page, they can see their transaction count for that merchant.
Thanks.
You really want to keep request data outside of your models (instead opting to pass it in). I'm also a little confused about why you have both a 'hasOne' for transactions, and a 'hasMany' for transactions within the merchant model.
I would probably approach the problem more like the below (untested, but along these lines). Again I'm not fully sure I understand what you need, but along these lines
// Merchant Model
public function transactions()
{
return $this->hasMany(Transaction::class);
}
public function countTransactionsByUser($userId)
{
return $this
->transactions()
->where('user_id', $userId)
->get()
->pluck('total_transaction')
->sum();
}
// Controller
$userId = request()->get('user_id');
// ::all() or however you want to reduce
// down the Merchant collection
//
$merchants = Merchant::all()->map(function($item, $key) {
$_item = $item->getAttributes();
$_item['transactions'] = [
'count_transactions' => $item->countTransactionsByUser($userId);
];
return $_item;
});
// Single total
// Find merchant 2, and then get the total transactions
// for user 2
//
$singleTotal = Merchant::find(2)
->countTransactionsByUser($userId);

Laravel - Multi fields search form

I'm builind a form with laravel to search users, this form has multiple fields like
Age (which is mandatory)
Hobbies (optional)
What the user likes (optional)
And some others to come
For the age, the user can select in the list (18+, 18-23,23-30, 30+ etc...) and my problem is that i would like to know how i can do to combine these fields into one single query that i return to the view.
For now, i have something like this :
if(Input::get('like')){
$users = User::where('gender', $user->interested_by)->has('interestedBy', Input::get('like'))->get();
if(strlen(Input::get('age')) == 3){
$input = substr(Input::get('age'),0, -1);
if(Input::get('age') == '18+' || Input::get('age') == '30+' )
{
foreach ($users as $user)
{
if($user->age($user->id) >= $input){
$result[] = $user;
// On enregistre les users étant supérieur au if plus haut
}
else
$result = [];
}
return view('search.result', ['users' => $result]);
}
elseif (strlen(Input::get('age')) == 5) {
$min = substr(Input::get('age'), 0, -3);
$max = substr(Input::get('age'), -2);
$result = array();
foreach($users as $user)
{
if($user->age($user->id) >= $min && $user->age($user->id) <= $max)
$result[] = $user;
}
return view('search.result', ['users' => $result]);
}
}
else
$users = User::all();
And so the problem is that there is gonna be 2 or 3 more optional fields coming and i would like to query for each input if empty but i don't know how to do it, i kept the age at the end because it's mandatory but i don't know if it's the good thing to do.
Actually this code works for now, but if i had an other field i don't know how i can do to query for each input, i know that i have to remove the get in my where and do it at the end but i wanna add the get for the last query..
Edit: my models :
User.php
public function interestedBy()
{
return $this->belongsToMany('App\InterestedBy');
}
And the same in InterestedBy.php
class InterestedBy extends Model{
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'interested_by';
public function users()
{
return $this->belongsToMany('App\User');
}
}
you can use query builer to do this as follow
$userBuilder = User::where(DB::raw('1')); //this will return builder object to continue with the optional things
// if User model object injected using ioc container $user->newQuery() will return blank builder object
$hobbies = Request::input('hobbies') // for laravel 5
if( !empty($hobbies) )
{
$userBuilder = $userBuilder->whereIn('hobbies',$hobbies) //$hobbies is array
}
//other fields so on
$users = $userBuilder->get();
//filter by age
$age = Request::input('age');
$finalRows = $users->filter(function($q) use($age){
return $q->age >= $age; //$q will be object of User
});
//$finalRows will hold the final collection which will have only ages test passed in the filter
A way you could possible do this is using query scopes (more about that here) and then check if the optional fields have inputs.
Here is an example
Inside your User Model
//Just a few simple examples to get the hang of it.
public function scopeSearchAge($query, $age)
{
return $query->where('age', '=', $age);
});
}
public function scopeSearchHobby($query, $hobby)
{
return $query->hobby()->where('hobby', '=', $hobby);
});
}
Inside your Controller
public function search()
{
$queryBuilder = User::query();
if (Input::has('age'))
{
$queryBuilder ->searchAge(Input::get('age'));
}
if (Input::has('hobby'))
{
$queryBuilder->searchHobby(Input::get('hobby'));
}
$users= $queryBuilder->get();
}

Categories