Laravel model to get specific piece of data - laravel

I am trying to make a function that will help me get needed data quickly.
With all the trials I have been able to get to the following
Tables:
Users (id,name)
Projects (id,name)
User-Project (id, user_id, project_id, manager) where manager is a boolean , there can only be one manager for each project (but employees can still see the project reason why we have a pivot table, manager = 0 for other normal users that can access that project)
In the Project Model I have:
public function Manager(){
return $this->belongsToMany('App\User')->wherePivot('manager', true);
}
In the View I have:
<p><strong>Project Manager:</strong> {{$project->manager}}</p>
On the actual page I get:
Project Manager: [{"id":4,"name":"Daniel Doe","email":"danieldoe#hotmail.com","phone":"70846556","email_verified_at":null,"created_at":"2020-12-20 21:05:50","updated_at":"2020-12-20 21:05:50","pivot":{"project_id":1,"user_id":4,"manager":1}}]
When I change the view to:
<p><strong>Project Manager:</strong> {{$project->manager[0]->name}}</p>
I get:
Project Manager: Daniel Doe
This is what I actually want but I would like to do it from the model if possible. So I tried:
public function Manager(){
return $this->belongsToMany('App\User')->wherePivot('manager', true)->first()->name;
}
But I get the following error:
must return a relationship instance
Can this be done from the model's function?

You can keep your defined relationship, but to access ->first()->name, you'll need to use an "Accessor":
public function manager() {
return $this->belongsToMany('App\User')->wherePivot('manager', true);
}
public function getManagerNameAttribute() {
return $this->manager->first() ? $this->manager->first()->name : 'No Manager';
}
Then, in your code, you simple access:
{{ $project->manager_name }}
If your manager() function returns a Collection of at least 1 record, it will return the name, otherwise it will display 'No Manager' as a fallback.

If you don't want to change the structure of this you can use an accessor to get this information, roughly something like this:
class Project ...
{
public function users()
{
return $this->belongsToMany(...)->withPivot(...);
}
public function getManagerAttribute()
{
return $this->users()->wherePivot('manager', 1)->first()?->name;
}
}
You can do this in different ways, you could use the loaded users relation and use a the Collection methods to filter the manager. You could create another relationship called managers that uses the wherePivot off of users(), etc ...
The only thing to worry about with this setup is that every call to $model->manager would be causing that query, so it may be a good idea to create another relationship manager so that you can load that once and keep using it without the need to keep querying the database:
public function managers()
{
return $this->users()->wherePivot(...);
}
public function getManagerAttribute()
{
return $this->managers->first()?->name;
}
Though, as mentioned already it is probably better to have something like a manager_id on the Project itself.

Related

Laravel Create multiple records in Pivot table

I'm trying to create a function in our Laravel 5.8 app that would add multiple records to a pivot table. At present we have the following setup;
Users
Training Courses
Users Training Courses (pivot table for the above relationships, with a few extra fields)
I want to be able to show all users in the database, then check their name, pick a training course and hit "Add" and it'll create a record in the pivot table for each user that was selected.
I can't figure out where to start with this - it seems like I need to have a "for each user selected, run the store function" loop in the controller, but I have no idea where to start.
I wasn't sure if there was an easy way to do this in eloquent or not. Is there a simple way to do this?
Eloquent does this automatically if you set up the relationships correctly and you don't have to worry about pivot tables.
class Users
{
public function trainingCourses()
{
return $this->hasMany(TrainingCourses::class);
}
}
class TrainingCourses
{
public function user()
{
return $this->belongsTo(User::class);
}
}
Then you can use the save() method to create the relationship. But I find it better to wrap this function inside a helper method that you can use throughout your code:
class Users
{
...
public function assignTrainingCourse(TrainingCourse $trainingCourse)
{
return $this->trainingCourses()->save($trainingCourse);
}
}
In your code, you could then do something as simple as this:
$user = User::find(1);
$trainingCourse = TrainingCourse::find(1);
$user->assignTrainingCourse($trainingCourse);
Building on this, suppose you have the following route to assign a training course, where it expects a trainingcourse_id in the request:
Route::post('/users/{user}/trainingcourses', 'UserTrainingCoursesController#store');
Thanks to route model binding, Laravel can inference the parent model (user) from the URL, and your controller might look like this:
// UserTrainingCoursesController.php
public function store(User $user)
{
$trainingCourse = TrainingCourse::find(request()->input('trainingcourse_id'));
$user->assignTrainingCourse($trainingCourse);
return back();
}
Of course, you'll want to put some validation in here, but this should get you started.

Access Method in a hasManyThorugh

I have 4 tables,
props, listing, offers, contact
props has many listing, listing belongs to props
public function listings()
{
return $this->hasMany('App\Models\Listing\Listing');
}
offer belongs to listing,
public function property()
{
return $this->belongsTo('App\Models\Property\Property')->with('owners');
}
then
offer belongsToMany contact trough offer_contact table
public function buyers()
{
return $this->belongsToMany(Contact::class, 'offer_contact', 'offer_id', 'contact_id')->with('primary_email');
}
My question is, how to access buyers()?
Something like $props->buyers()
In props model, what I did is
return $this->hasManyThrough('App\Models\Offer\Offer', 'App\Models\Listing\Listing');
You cannot. You may use nested iterations to get properties, listings belongs to each property, offers belongs to each listing and then customers belonging with the offer.
Alternatively, you may use the raw query to get the desired result using DB::statement();
I created a HasManyThrough relationship with unlimited levels: Repository on GitHub
After the installation, you can use it like this:
class Property extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function buyers() {
return $this->hasManyDeep(Contact::class, [Listing::class, Offer::class, 'offer_contact']);
}
}

Laravel model create relationships twice between two tables

I am trying to create relation between two tables, users and messages in Laravel models, as the user can send a message to another user so that I have two foreign-keys (fromUser_id and toUser_id) as shown in the image below.
For the first relation it is straightforward that I will create a function with the name messages
public function messages(){
return $this->hasMany('App\Models\Message', 'fromUser_id');
}
However I do not know how to name the second relation as far as I know it should be messages too, according to the standard naming of Laravel, which will obviously issue an error as we have the first function with the same name.
public function messages(){
return $this->hasMany('App\Models\Message', 'toUser_id');
}
Would you please let me know what should I name it and how this will affect the models.
Well, you should not use simple messages as relationship but rather use receivedMessages and sentMessages like this:
public function sentMessages()
{
return $this->hasMany('App\Models\Message', 'fromUser_id');
}
public function receivedMessages()
{
return $this->hasMany('App\Models\Message', 'toUser_id');
}

Limits to multiple Eloquent relationships ?

I have a number of different roles, who are all essentially "users" in my laravel app.
I'm having a problem with eloquent relationships where I can easily get one of the relationships, in the context of the user as a car owner, but when I try get another relationship in the context of a (different) user as a maintenance manager, I get this error: Cannot redeclare class App\Models\User.
serviceAgreement model
public function manager()
{
return $this->belongsTo('carfreak\Models\User','manager_id','id');
}
Works fine:
$managers = $owner->serviceAgreement()->get();
produces error
$managers = $owner->serviceAgreement()->with('manager')->get();
I'm thinking the problem lies somewhere in how I've written my relationship - my referring to the \Models\User. I've tried to refer to the logged in user, but it (a) doesn't make sense for this application and (b) doesn't work anyway.
return $this->belongsTo(Auth::User(),'manager_id','id');
Some pointers please?
That is happen because the App\Models\User class is declared at least 2 times. Change
public function manager()
{
return $this->belongsTo('carfreak\Models\User','manager_id','id');
}
to
public function manager()
{
return $this->belongsTo('App\Models\User','manager_id','id');
}
You should go fine now.
Hope it helps.

Laravel 4: How to add more data to Auth::user() without extra queries?

I'm rather new to Laravel 4 and can't seem to find the right answer, maybe you can help:
A User in our application can have many Accounts and all data is related to an Account, not a User. The account the User is currently logged into is defined by a subdomain, i.e. accountname.mydomain.com.
We added a method account() to our User model:
/**
* Get the account the user is currently logged in to
*/
public function account()
{
$server = explode('.', Request::server('HTTP_HOST'));
$subdomain = $server[0];
return Account::where('subdomain', $subdomain)->first();
}
The problem is that there is always an extra query when we now use something like this in our view or controller:
Auth::user()->account()->accountname
When we want to get "Products" related to the account, we could use:
$products = Product::where('account_id', Auth::user()->account()->id)->get();
And yet again an extra query...
Somehow we need to extend the Auth::user() object, so that the account data is always in there... or perhaps we could create a new Auth::account() object, and get the data there..
What's the best solution for this?
Thanks in advance
Just set it to a session variable. This way, you can check that session variable before you make the database call to see if you already have it available.
Or instead of using ->get(), you can use ->remember($minutes) where $minutes is the amount of time you wish to keep the results of the query cached.
You should take a look at Eloquent relationships : http://laravel.com/docs/eloquent#relationships
It provides simple ways to get the account of a user and his products. You said that a user can have many accounts but you used a first() in your function I used a hasOne here.
Using Eloquent relationships you can write in your User model:
<?php
public function account()
{
// I assume here 'username' is the local key for your User model
return $this->hasOne('Account', 'subdomain', 'username');
}
public function products()
{
// You should really have a user_id in your User Model
// so that you will not have to use information from the
// user's account
return $this->hasMany('Product', 'account_id', 'user_id');
}
You should define the belongsTo in your Account model and Product model.
With Eager Loading you will not run a lot of SQL queries : http://laravel.com/docs/eloquent#eager-loading
You will be able to use something like
$users = User::with('account', 'products')->get();
To get all users with their account and products.
I think this is a good example for the purpose of Repositories.
You shouldn't query the (involved) models directly but wrap them up into a ProductRepository (or Repositories in general) that handles all the queries.
For instance:
<?php
class ProductRepository
{
protected $accountId;
public function __construct($accountId)
{
$this->accountId = $accountId;
}
public function all()
{
return Product::where('account_id', $this->accountId)->get();
}
}
//now bind it to the app container to make it globaly available
App::bind('ProductRepository', function() {
return new ProductRepository(Auth::user()->account()->id);
});
// and whenever you need it:
$productRepository = App::make('ProductRepository');
$userProducts = $productRepository->all();
You could group the relevant routes and apply a filter on them in order to bind it on each request so the account-id would be queried only once per repository instance and not on every single query.
Scopes could also be interesting in this scenario:
// app/models/Product.php
public function scopeCurrentAccount($query)
{
return $query->where('account_id', Auth::user()->account()->id);
}
Now you could simply call
$products = Product::currentAccount()->get();

Resources