Laravel Join On Auth Table - php

I'm using the default Authentication system that Laravel provides. However, I want to join two tables (perform two inner joins) on my Auth table (users). So far, I have created two relationships between two models and this works fine and gets me the data I want, however it performs two extra queries when I think in hindsight doing two joins on the table would be much more efficient.
What I don't know how to do is to perform the Joins on the User Model so that I'm not having to create a new query (but rather use the one that is created by calling Auth::user()).
My tables:
Users: id username iconid avatarid
Icons: id image
Avatars: id image
So what I'd like to do is:
SELECT icons.image as icon_image, avatars.image as avatar_image FROM users
LEFT OUTER JOIN icons ON icons.id = users.iconid
LEFT OUTER JOIN avatars ON avatars.id = users.avatarid
Is this possible? Do I just need to overwrite a method in my User model in order for Auth::user() to run the above query?
Thank you to anyone who replies!

Put this in your user model:
public function icon()
{
return $this->hasOne('Icon');
}
public function avatar()
{
return $this->hasOne('Avatar');
}
Then get the user like this:
$user = User::with(['icon', 'avatar'])->find(1);
Then you can access the icon and avatar like this:
$user->icon
$user->avatar
To do this with joins you can do this:
$user_id = 1;
DB::table('users')
->join('icons', 'icons.id', '=', 'users.icon_id')
->join('avatars', 'avatars.id', '=', 'users.avatar_id')
->select('users.name', 'icons.path', 'avatars.path')
->where('users.id', $user_id)
->first();

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();

Laravel Eloquent join 2 tables together

I want to join 2 tables together using laravel eloquent. I could easily do this in mysql but have no idea using this.
I have a model called device.php this contains the following method:
public function wifi_client_connection(){
return $this->hasMany('UserFrosting\WifiClientConnection');
}
but I need to link the WifiClientConnection.php model using a column called mac_address but WifiClientConnection doesnt have that column it has a column called device_uuid. The mac_address is converted into the device_uuid using the following method:
public function getDeviceUUID(){
return hex2bin(md5($this->mac_address . $this->number));
}
In MySQL I join the 2 tables together like this:
LEFT OUTER JOIN device ON wifi_client_connection.device_uuid = UNHEX(MD5(CONCAT(device.mac_address, device.number)))
how can I do something like that using Laravel Eloquent.
I also need the relationship the other way round so in the WifiClientConnection.php model I have this method:
public function device(){
return $this->belongsTo('UserFrosting\WifiClientConnection');
}
but again I would need to convert the device_uuid to mac_address for this relationship to work
Thanks for any help
I would add a column with the converted value, then you can create the relationship.
public function device(){
return $this->belongsTo('UserFrosting\WifiClientConnection', 'device_uuid', 'new_column_with_converted_value');
}

Laravel Eloquent query unexpected result

I've found some query result really unexpected.
It's Laravel 5.2
We have following entity:
User with method:
public function roles() : BelongsToMany
{
return $this->belongsToMany(Role::class)->withPivot('timestamp');
}
Each User can have many roles, so we have also Role entity (but it doesn't matter much in my question) and pivot table user_role with timestamp field (and ids of course), because we hold information about time, when User achieved specific role.
I want to get all Users with theirs last assigned Role
When I create query (in User context in some repository):
$this->with(['roles' => function($query) {
$query->orderBy('timestamp', 'desc');
}])->all();
the result will contain Users with Roles entities inside itself ordered by timestamp - it's ok. But I want to retrieve only one last role inside each User entity not all ordered.
So...
$this->with(['roles' => function($query) {
$query->orderBy('timestamp', 'desc')->limit(1);
}])->all();
And then I retrieve Users but only User which achieved some Role for the very last time contains it! All the other Users have their roles field containing empty array.
Why ordering was performed on each Users relation separately, but when I added limit it behaved like a global limit for all.
It drives me crazy...
Thanks for advices.
EDIT
I've created lastRoles() method to get all Roles ordered desc. But all, retrieving one is impossible.
public function lastRoles() : BelongsToMany
{
return $this->BelongsToMany(Roles::class)->withPivot('timestamp')->latest('timestamp');
}
And for testing:
$users = (new User())->with('lastRoles')->get();
But now I must iterate over Users and invoke lastRoles() on each one:
foreach ($users as $user) {
var_dump($user->lastRoles()->get()->first()->name);
}
Then I retrieve names of latest Roles assigned to each User.
So... There is no way to do it in one query? This is the only way?
For this to work, you would need a helper function:
public function latestRole()
{
return $this->hasOne(Role::class)->withPivot('timestamp')->orderBy('timestamp', 'DESC');
}
And then:
$this->with('latestRole')->get();
Credits to this awesome article.
When you eager load a relationship with query constraint(s), the query will be run once to load all relationships, not each one individually. This is the expected behavior. Think about it, eager loading exists to turn many queries into one query in order to optimize performance. There is only one query executed, so your limit constraint will limit the entire result set, rather than on a per model basis.
To circumvent this, you could try creating another belongsToMany method that adds the desired limit constraint. The following code is untested:
public function lastRole() : BelongstoMany
{
return $this->belongsToMany(Role::class)
->withPivot('timestamp')
->orderBy('timestamp', 'desc')
->limit(1);
}
Assuming this works, you can then simply change the relationship method from roles to lastRole and remove your query constraint:
$this->with('lastRole')->all();

Push an array into Laravel collection with matching keys

I have a collection called User, I also have an array containing two models Post relating to the User model.
The User collection contains a primary key id and each model in my Post collection I have a foreign key user_id.
I am currently executing the following:
foreach ($users as $user) {
foreach ($posts as $post) {
if ($post->user_id == $user->id) {
$user->posts->push($post);
}
}
}
This somewhat works, but not entirely because it pulls in all related posts instead of the recent two posts a user has made.
The array looks like the following:
My User schema looks like; with a hasMany relationship to Post:
You can load the posts associated to a User using with, something like
$user = User::with('posts')->find($id);
But your scenario sounds specifically collecting the latest two Post belonging to a User. To limit your results you can also use scopes.
Something like the following on your Post model would work:
public function scopeLatest($query, $latest = 2)
{
return $query->limit($latest);
}
Then collect these by:
// The user record.
$user = User::find($id);
// Latest 2 posts for this user.
$posts = $user->posts()->latest();
// Latest 5 posts for this user.
$posts = $user->posts()->latest(5);
However, should you with to load the latest 2 posts with the user in a single query - then you could make a new relation:
public function latestPosts()
{
return $this->hasMany(Post::class, 'post_id', 'id')
->orderBy('created_at', 'ASC')
->limit(2);
}
This would work in the following way:
// Load the user with the latest 2 posts.
$user = User::with('latestPosts')->find($userId);
// Access these using; this will be a Collection containing 2 `Post` records.
dd($user->latestPosts);
Basically with Eloquent, when you call $this->latestPosts Eloquent will run latestPosts() and hydrate the related records. Using with this hydration occurs with a single query and the relations are already defined.
The difference between the method latestPosts() and the property $latestPosts is simple.
The method will always return a specific Relation Collection allowing you to chain additional conditions;
So: $user->latestPosts()->get() is the same as $user->latestPosts.
You cannot use query constraints / eager loading to do this. Doing so will only work if you are retrieving the posts for one user. However, if you try to retrieve the posts for multiple users, it will fail because eager loading / query constraints will limit the related results as a whole. To understand, you have to look at the queries Eloquent generates. Lets take a look at an example where you only need one user's posts.
$user = User::with(['posts' => function($query) {
$query->limit(2);
}])->find(1);
In this example, we are getting a user with a primary key of 1. We also also retrieving his/her posts but limiting it so we only retrieve 2 posts. This works, and it will generate 2 queries similar to this:
select * from `users` where `users`.`id` = 1 limit 1
select * from `posts` where `posts`.`user_id` in (1) limit 2
Okay. Now, why doesn't this work if you try to get more than 1 user (or a collection of users)? For example:
$user = User::with(['posts' => function($query) {
$query->limit(2);
}])->get();
In this case, I changed find(1) to get(), and it will generate 2 queries like this:
select * from `users`
select * from `posts` where `posts`.`user_id` in (?, ?, ?, ... ?) limit 2
It's important to take a look at the second query. It's retrieving all the related posts, but at the end, you'll see that it has limit 2. In other words, it's limiting the entire related collection to only 2, which is why query constraints do not work for this.
Achieving this is actually pretty complex, but a fellow member (Jarek Tkaczyk) came up with a solution using MySQL variables, which you can find here: Laravel - Limit each child item efficiently
You can do this a bit simpler with https://laravel.com/docs/5.2/eloquent-relationships#eager-loading constraints.
Example: Users have many Dogs, but only take 2
$user = App\User::with(['dogs' => function ($query) {
$query->limit(2);
}])->find($user_id);
dump($user);
The anonymous constraining function would also have an orderBy in your case

Loading Relational Data from Views in CodeIgniter

I am using CodeIgniter
I have two tables -
Company
int id ,
varchar name
Employee int id, varchar name , int company_id
I have a simple controller called Employees Controller
<?php
class Employees extends CI_Controller {
public function index()
{
----
----
$data['employees'] = $selected_employees;
$this->load->view('employees/index',$data);
}
}
This controller passes an array of Employees to the view .
So inside my view , I can freely use employees[4].name , employees[3].id etc
Now If I want to show the name of the Company of the employees , it seems the only way is the Controller should pass another array with the name of the companies to the view . Is there any better way to achieve this - so that the controller doesnt have to explicitly have to send the data ?
For eg - say I want to access employees[4].company.name
I have been spoilt by Rails . I used to take these for granted .
The best way to go about this is using a join statement in your SQL query. The SQL query would typically look something like the following:
SELECT * FROM Employee JOIN Company ON Employee.company_id = Company.id
However, CodeIgniter's active record class will help us to simplify this (see http://ellislab.com/codeigniter/user-guide/database/active_record.html). In your model, you could write your query like so:
$this->db->select('*');
$this->db->from('Employee');
$this->db->join('Company', 'Employee.company_id = Company.id');
$query = $this->db->get();
You can tweak this to select the exact data that you want like you would any SQL query:
$this->db->select('Employee.id, Employee.name, Employee.company_id, Company.name AS company_name');
You can also add left, right, outer, inner, left outer, or right outer as a third parameter to the $this->db->join() function to allow for left joins, right joins, etc.
I would recommend using the Active Record class when possible in your CodeIgniter applications. It will help keep your queries clean, organized, and readable.
Do a join on your model
public function get_ selected_employees(){
$this->db->select('Employee.*, Company.name as company_name');
$this->db->from('Employee');
$this->db->join('Company', 'Employee.company_id = Company.id');
return $this->db->get()->result();
}
To get the company name just do employees[4].company_name

Categories