Laravel Eloquent distinct not working as intended - php

I'm currently creating a basic private messaging system in Laravel. In my database I have a messages table. Two of the columns are "sender_id" and "recipient_id".
What I am trying to do is use eloquent to go through this table and list in the inbox all the times the current logged in user's id is listed in these columns. I will then use "distinct" to ignore duplicate entries so that the user is only listed once. Using this ID, I can display the username of the recipient so that when the user clicks on the username, it will take them to the message thread for that user.
For the sake of testing, I am only currently retrieving the column "recipient_id" and outputting it into the blade. Here is the line I believe should only output the recipient once despite being found many times in the table.
MessageController
$messages = Message::where('recipient_id', $user)->distinct('recipient_id')->get();
However in the blade, the same user is being output twice (as they have 2 messages found) when I would have expected the distinct function to remove the duplicate entry.
I have also tried
$messages = Message::where('recipient_id', $user)->distinct()->get();
But that also did not work.
If I have received 3 messages from 2 different users, it is outputting something like below:
Messages from:
Example User 1
Example User 1
Example User 2
What it should output
Example User
Example User 2
Thanks

Use Laravel Collection and changed code to:
$messages = collect(Message::where('recipient_id', $user)->get());
$messagesUnique = $messages->unique('recipient_id');
$messagesUnique->values()->all();

You can follow this
$payment = Payment::with('invoice','customer')->get();
$payment = $payment->unique('customer_id');
It will return all the unique customer payment data.

You should use something like this:
$data = Message::select([ DB::raw('DISTINCT(recipient_id)')'])
->where('recipient_id', $user)
->get();
instead of using a collection, as when you use collection you returned with all data you needed or not then you filter it. but when using distinct in the query you just take them in one process.
Or you can try one of these options: mentioned here
$diff = Crud::distinct()->pluck('name');
$diff = Crud::distinct()->get(['name']);
$diff = Crud::distinct()->select('name')->get();

Related

retrieve child object by evaluating grandchildren object in laravel

I have 2 models, User and Conversation related to each other with a many-to-many relationship, many users to many conversations.
On my models I have:
User:
public function conversation() {
return $this->belongsToMany('App\Models\Conversation');
}
Conversation:
public function users() {
return $this->belongsToMany('App\Models\User');
}
So I could get the conversations a user has by : $user->conversation; and vice versa, retrieve the users a conversation has by $conversation->users, works like a charm. The problem is that I want a way to retrieve all users a certain user has made contact before, in few words, something like:
$user = User::find(1);
$talkedUsers = $user->conversation->user.
And also a way to check all the conversation user 'x' has made with user 'y' ('if any')
$userX = User::find(1);
$userY = 2;
$talkedUser = $userX->conversation->where('user.id', '=', $userY);
Obviously, the code above won't work. I wish to know if something like this is possible to accomplish without adding a complex raw query, I hope is possible only using only QueryBuilder.
The problem is that I want a way to retrieve all users a certain user
has made contact before,
$user->conversation is actually a collection of conversations, it is better to use plural i.e. $user->conversations. Then you can use higher order messages to deal with multiple objects at once. See https://laravel.com/docs/8.x/collections#higher-order-messages for more information
$users = $user->conversations->flatMap->users->unique('id');
flatMap combines multiple User collections retrieved from multiple Conversation objects into one collection of User objects then to remove duplicate users, you can use unique('id')
And also a way to check all the conversation user 'x' has made with
user 'y' ('if any')
First you should query all conversations from x then filter only conversations that have relationships with y.
$userX = User::find($x);
$conversations = $userX->conversations()
->whereHas('users', function($query) use ($y) {
$query->where('users.id', $y);
})
->get();

How to simplify this Laravel PHP code to one Eloquent query?

I assume that this should all be in one query in order to prevent duplicate data in the database. Is this correct?
How do I simplify this code into one Eloquent query?
$user = User::where( 'id', '=', $otherID )->first();
if( $user != null )
{
if( $user->requestReceived() )
accept_friend( $otherID );
else if( !$user->requestSent() )
{
$friend = new Friend;
$friend->user_1= $myID;
$friend->user_2 = $otherID;
$friend->accepted = 0;
$friend->save();
}
}
I assume that this should all be in one query in order to prevent
duplicate data in the database. Is this correct?
It's not correct. You prevent duplication by placing unique constraints on database level.
There's literally nothing you can do in php or any other language for that matter, that will prevent duplicates, if you don't have unique keys on your table(s). That's a simple fact, and if anyone tells you anything different - that person is blatantly wrong. I can explain why, but the explanation would be a lengthy one so I'll skip it.
Your code should be quite simple - just insert the data. Since it's not exactly clear how uniqueness is handled (it appears to be user_2, accepted, but there's an edge case), without a bit more data form you - it's not possible to suggest a complete solution.
You can always disregard what I wrote and try to go with suggested solutions, but they will fail miserably and you'll end up with duplicates.
I would say if there is a relationship between User and Friend you can simply employ Laravel's model relationship, such as:
$status = User::find($id)->friends()->updateOrCreate(['user_id' => $id], $attributes_to_update));
Thats what I would do to ensure that the new data is updated or a new one is created.
PS: I have used updateOrCreate() on Laravel 5.2.* only. And also it would be nice to actually do some check on user existence before updating else some errors might be thrown for null.
UPDATE
I'm not sure what to do. Could you explain a bit more what I should do? What about $attributes_to_update ?
Okay. Depending on what fields in the friends table marks the two friends, now using your example user_1 and user_2. By the example I gave, the $attributes_to_update would be (assuming otherID is the new friend's id):
$attributes_to_update = ['user_2' => otherID, 'accepted' => 0 ];
If your relationship between User and Friend is set properly, then the user_1 would already included in the insertion.
Furthermore,on this updateOrCreate function:
updateOrCreate($attributes_to_check, $attributes_to_update);
$attributes_to_check would mean those fields you want to check if they already exists before you create/update new one so if I want to ensure, the check is made when accepted is 0 then I can pass both say `['user_1' => 1, 'accepted' => 0]
Hope this is clearer now.
I'm assuming "friends" here represents a many-to-many relation between users. Apparently friend requests from one user (myID) to another (otherId).
You can represent that with Eloquent as:
class User extends Model
{
//...
public function friends()
{
return $this->belongsToMany(User::class, 'friends', 'myId', 'otherId')->withPivot('accepted');
}
}
That is, no need for Friend model.
Then, I think this is equivalent to what you want to accomplish (if not, please update with clarification):
$me = User::find($myId);
$me->friends()->syncWithoutDetaching([$otherId => ['accepted' => 0]]);
(accepted 0 or 1, according to your business logic).
This sync method prevents duplicate inserts, and updates or creates any row for the given pair of "myId - otherId". You can set any number of additional fields in the pivot table with this method.
However, I agree with #Mjh about setting unique constraints at database level as well.
For this kind of issue, First of all, you have to enjoy the code and database if you are working in laravel. For this first you create realtionship between both table friend and user in database as well as in Models . Also you have to use unique in database .
$data= array('accepted' => 0);
User::find($otherID)->friends()->updateOrCreate(['user_id', $otherID], $data));
This is query you can work with this . Also you can pass multiple condition here. Thanks
You can use firstOrCreate/ firstOrNew methods (https://laravel.com/docs/5.3/eloquent)
Example (from docs) :
// Retrieve the flight by the attributes, or create it if it doesn't exist...
$flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
// Retrieve the flight by the attributes, or instantiate a new instance...
$flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
use `firstOrCreate' it will do same as you did manually.
Definition of FirstOrCreate copied from the Laravel Manual.
The firstOrCreate method will attempt to locate a database record using the given column / value pairs. If the model can not be found in the database, a record will be inserted with the given attributes.
So according to that you should try :
$user = User::where( 'id', '=', $otherID )->first();
$friend=Friend::firstOrCreate(['user_id' => $myId], ['user_2' => $otherId]);
It will check with both IDs if not exists then create record in friends table.

Laravel mysql select result

I am trying to add a feature to a website built with Laravel.
There is a table containing vote numbers and user. I want to get the total points a user has in a certain category. I do not have any PHP or Laravel experience but said I would give this a shot.
$votes1 = UserVotes::select ('select vote from user_votes where feedback_id = ? and feedback_type = 1', Auth::user()->id);
This should return an object containing the vote amount. I want to interrogate the the object to check if the vote number is above a certain amount and then do something based on that being the case or not.
if vote > 50{
//do stuff
}
foreach ($votes1 as $vote1) {
echo $vote1->vote;
}
The query should return 1. I have verified this by querying the database, so the problem is with my understanding of Laravel or php. What I am doing wrong?
You don't need to construct your own SQL statement; Eloquent will do that for you.
If your models are set up in the default way, your query would look something like:
$votes = UserVotes::where('feedback_id', Auth::user()->id)
->where('feedback_type', 1)
->get();
You can then iterate over that as normal.
Additionally, if there is a relationship set up with the user model you could do something like
$votes = Auth::user()->votes()
->where('feedback_type', 1)
->get();
Check out the documentation here: http://laravel.com/docs/4.2/eloquent
assuming UsersVotes extends Model, here's how you should do it:
UsersVotes::select('vote')->where('feedback_type', 1)->where('feedback_id', Auth::user()->id)-get();

Laravel Eloquent Model Properties

My scenario is:
PHP Script 1:
$user = User::find(1);
$user->email = 'email#emailness.com';
$user->save();
PHP Script 2:
$user = User::find(1);
while ($user->email != 'email#emailness.com') {
/** Do stuff **/
}
Now my question is, does the email variable get updated when it's updated from another script? For example, Script 2 runs, while it runs, Script 1 also runs. Will the while statement update and move since the condition isn't true anymore?
I think the simplest way to achieve something like this is to run a query every minute or so checking for updated columns. The alternative would be to make your daemon run some sort of server which gets pinged by a model event.
If you only need to monitor a single user's email, it's easy enough - User::find($id); then sleep(60). If you need to monitor more than one user it gets a bit trickier.
If your model uses timestamps (created_at and updated_at), it's possible to query only for models that have been updated recently. Let's say you want to re-query the database every 60 seconds - what we want to do is query all User models that have an updated_at greater than 60 seconds ago.
I'll use the Carbon class, which is an extension of DateTime included with Laravel, but you can also construct a normal DateTime object or a datetime string manually.
$dt = Carbon\Carbon::now()->subSeconds(60);
$users = User::where('updated_at', '>=', $dt)
->get();
foreach ($users as $user) {
// do something with users that have been updated
}
sleep(60);
You can also replace get() with lists('email') if you only want a flat array of emails of updated users.
There may also be more efficient ways than using sleep(60), maybe using Symfony's Process class, but I'll not get into that here.

Symfony Criteria Join unexpected behaviour

i'm trying to do a complex query with Criteria in a Symfony project using Propel ORM.
the query i want to make is, in human words:
Select from the 'interface' table the registers that:
- 1 are associated with a process (with a link table)
- 2 have a name similat to $name
- 3 its destiny application's name is $apd (application accecible by foreign key)
- 4 its originapplication's name is $apo (application accecible by foreign key)
here the code i made, and not working:
$c = new Criteria();
$c->addJoin($linkPeer::CODIGO_INTERFASE,$intPeer::CODIGO_INTERFASE); //1
$c->add($linkPeer::CODIGO_PROCESONEGOCIO,$this->getCodigoProcesonegocio());//1
if($name){
$name = '%'.$name.'%'; //2
$c->add($intPeer::NOMBRE_INTERFASE,$name,Criteria::LIKE); //2
}
if($apd){
$apd = '%'.$apd.'%'; //3
$c->addJoin($appPeer::CODIGO_APLICACION,$intPeer::CODIGO_APLICACION_DESTINO);//3
$c->add($appPeer::NOMBRE_APLICACION,$apd,Criteria::LIKE); //3
}
if($apo){
$apo = '%'.$apo.'%';//4
$c->addJoin($appPeer::CODIGO_APLICACION,$intPeer::CODIGO_APLICACION_ORIGEN);//4
$c->add($appPeer::NOMBRE_APLICACION,$apo,Criteria::LIKE);//4
}
After that i did a $c->toString() to see the SQL generated and i saw that when i send only an $apd value, the SQL is correct, when i send an $apo value too. But when i send both, only the $apo AND apears on the SQL.
I guess its because the $c->add(...) call is the same with a distinct parameter, but not sure at all. Is this the error? What is the best way to generate my query correctly?
Thank you very much for your time! :D
Yes, it's overriding the previous call as the Criteria object only stores one condition per field. The solution is to create 2 or more separate Criterion objects and mix them into the Criteria object:
//something like this
$cron1 = $criteria->getNewCriterion();
$cron1->add($appPeer::NOMBRE_APLICACION,$apo,Criteria::LIKE);//4
$criteria->add($cron);
//and the same with the other criterion
However, it would be much easier to upgrade to Propel15+, where you work on the Query class level, and multiple restrictions on the same field don't override each other.
Hope this helps,
Daniel

Categories