Clarification with eager loading/mutators/accessors for laravel - laravel

I'm making a laravel app for work and I need to load all users with their attached role without any nesting of the roles. I used this tutorial for roles: https://medium.com/#ezp127/laravel-5-4-native-user-authentication-role-authorization-3dbae4049c8a . If I use public $with = ['roles']; on my User model it returns the entire role object within the user object and I need it to just return role:'role_name';
/**
* set up eloquent between roles/user
*
* #return \Illuminate\Database\Eloquent\Relations\belongsToMany
*/
public function roles()
{
return $this->belongsToMany(Role::class);
}
Above is in my User model and below is in my Role model to define the relationships.
/**
* provides a many-to-many relationship to User model
*
* #return User::class
*/
public function users()
{
return $this->belongsToMany(User::class);
}
I thought that by adding this to the User model:
protected $appends = ['role_name'];
public function getRoleNameAttribute()
{
return $this->attribute['name'];
}
it would return everything but all it does is create a role_name: 'user_name'; On the model. So I guess I realize I'm accessing just the Users table and not the Roles table in the DB, but again not really sure what to do. Any help would be greatly appreciated.

If for the purpose of convenience you need to access the role name directly from the model you should refer to the actual relationship data:
protected $appends = ['role_name'];
public function getRoleNameAttribute()
{
return $this->roles->pluck('name');
}
This should append the array of role names to your user model. It will be an array because roles seem to have a many-to-many relationship with the User model.

The issue is you're returning $this->attributes['name'] in the getRoleNameAttribute. You want the role names, so instead you'd do something like this:
If you want an array with the names:
return $this->roles()->pluck('name')
If you want the names as a string:
return array_implode(", ", $this->roles()->pluck('name')->toArray());

Related

Laravel Mutator to add predefined values into database

I'm new into Laravel and I'm trying to store the user's company id on a column of the products table each time a user creates a new product. The company's id it's retrieved from the user's session. I'm trying it with Laravel's Mutator:
public function setFirstNameAttribute($value) {
$this->attributes['company_id'] = session()->get('company.id');
}
But each time I create a new Product the company id stored it's null. Seems like the function it's never executing. Is there any other resource to perform actions like this?
You must use model events - this will be executed on model creation before saving. Or you can use another events depends on you logic - see docs.
class YourModel extends Model
{
/**
* The "booted" method of the model.
*
* #return void
*/
protected static function booted()
{
static::creating(function (YourModel $model) {
$model->company_id = session()->get('company.id');
});
}
}
Mutators only works when you change mutating field directly:
$model->first_name = 'new_name'
And with your code - you will lost "new_name".
I noticed that the function name is incorrect, since the accessors use "studly" cased name of the column you wish to access, it may be as simple as to change
public function setFirstNameAttribute($value)
to
public function setCompanyIdAttribute($value)

Separate roles/permissions if user belongs to many organizations

I have an application where a user can belong to multiple organizations. I want to set it up in a way that a user can have different roles/permissions for each organization. I am using Laravel and plan on implementing Spatie/laravel-permission. What is the best way to implement this?
I have tried setting up two guards, one for the main user account and another for the pivot model between the user and the organization they log into. So basically when they log into the app using the main user model, I ask them which organization they would like to log into, when they choose the organization I will then also set up an auth session on the pivot model that links the user to the organization and access the roles off that model. This works, but having to manage the auth sessions is kind of a pain.
// User Model
class User extends Authenticatable
{
public function organizationUsers()
{
return $this->hasMany(OrganizationUser::class);
}
}
// OrganizationUser Model
class Organziationuser extends Authenticatable
{
use HasRoles;
public $guard_name = 'organization_user';
public function organization()
{
return $this->belongsTo(Organization::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
}
I would expect a user to be able to log into the application using a single login, but also be able to have different permissions for different organizations.
I have got around this issue by doing the following. I would welcome feedback on peoples views of this method!
Note: Currently I am only using the model_has_roles table with Spatie permissions and always use $user->can('Permission') to check permissions.
Our company model has the following relationships and method
class Company extends Model
{
public function owner(): HasOne
{
return $this->hasOne(User::class, 'id', 'user_id');
}
public function users(): BelongsToMany
{
return $this->belongsToMany(
User::class, 'company_users', 'company_id', 'user_id'
)->using(CompanyUser::class);
}
public function addTeamMember(User $user)
{
$this->users()->detach($user);
$this->users()->attach($user);
}
}
We modify the pivot model to have the Spatie HasRoles trait. This allows us to assign a role to the CompanyUser as opposed to the Auth User. You also need to specify the default guard or Spatie permissions squarks.
class CompanyUser extends Pivot
{
use HasRoles;
protected $guard_name = 'web';
}
On the user model, I have created the HasCompanies Trait. This provides the relationships and provides a method for assigning the roles to the new company user. Additionally, it overwrites the gate can() method.
A user can belong to many companies, but can only have one active company at a time (i.e. the one they are viewing). We define this with the current_company_id column.
It is also important to ensure the pivot table ID is pulled across (which it will not be as standard) as this is now what we are using in the Spatie model_has_roles table.
trait HasCompanies
{
public function companies(): HasMany
{
return $this->hasMany(Company::class);
}
public function currentCompany(): HasOne
{
return $this->hasOne(Company::class, 'id', 'current_company_id');
}
public function teams(): BelongsToMany
{
return $this->belongsToMany(
Company::class, 'company_users', 'user_id', 'company_id'
)->using(CompanyUser::class)->withPivot('id');
}
public function switchCompanies(Company $company): void
{
$this->current_company_id = $company->id;
$this->save();
}
public function assignRolesForCompany(Company $company, ...$roles)
{
if($company = $this->teams()->where('companies.id', $company->id)->first()){
/** #var CompanyUser $companyUser */
$companyUser = $company->pivot;
$companyUser->assignRole($roles);
return;
}
throw new Exception('Roles could not be assigned to company user');
}
public function can($ability, $arguments = [])
{
if(isset($this->current_company_id)){
/** #var CompanyUser $companyUser */
$companyUser = $this->teams()->where('companies.id', $this->current_company_id)->first()->pivot;
if($companyUser->hasPermissionTo($ability)){
return true;
}
// We still run through the gate on fail, as this will check for gate bypass. i.e. Super User
return app(Gate::class)->forUser($this)->check('InvalidPermission');
}
return app(Gate::class)->forUser($this)->check($ability, $arguments);
}
}
Now we can do something like this:
Create the role & permission
/** #var Role $ownerRoll */
$ownerRoll = Role::create(['name' => 'Owner']);
/** #var Permission $permission */
$permission = Permission::create([
'name' => 'Create Company',
'guard_name' => 'web',
]);
$ownerRoll->givePermissionTo($permission);
Create a new company with an owning user and then switch this company to that owner's active company.
public function store(CompanyStoreRequest $request)
{
DB::transaction(function () use($request) {
/** #var User $owner */
$owner = User::findOrFail($request->user_id);
/** #var Company $company */
$company = $owner->companies()->create($request->validated());
$company->addTeamMember($owner);
$owner->assignRolesForCompany($company, 'Owner');
$owner->switchCompanies($company);
});
return redirect()->back();
}
So this all works, my main concerns are that:
We are overwriting the can method. There may be other authorization methods/gate functions that are not caught.
We have 2 sets of model_permissions. The Auth user and the company user. I think I need to build in some checks to ensure that only the correct kinds of users can be assigned to the roles. At this stage, all administrator users would have permissions assigned to their auth user, while any users who own a company should only have permissions on the company user model

How can I access the instantiated user within the user model itself (without creating another relationship)?

I want to create a custom method by accessing the instantiated users id, like:
public function ware(){
return File::where('owner', $this->user->id);
}
same could be a accessor or scope. and then being able to call
$user = User::find(5);
$user->ware()->all()...
You would define relationships using PK/FK as outlined in the Laravel documentation here and then could access them via relationship like:
Make sure your file table has a column for user_id which would allow the relationship to function as outlined above.
Add to User Model:
/**
* Get the files for the user.
*/
public function files()
{
return $this->hasMany('App\File');
}
Add to File Model:
/**
* Get the user for the file.
*/
public function user()
{
return $this->hasOne('App\User');
}
You could then access it like User::find(5)->files (to get user files) or File::find(5)->user to return the file user.

Mapping of Eloquent models to tables?

We are able to create Eloquent models for tables. But how Laravel knows to which table to associate a model with? Do we have something similar to hbm.xml(mapping file we use for Hibernate) which says this model means this table.
The table name is a protected property:
class User extends Eloquent {
protected $table = 'my_users';
}
Laravel Docs
You can manually override the table name as the above answer states.
Its just a protected member of the Model.php class.
class User extends Eloquent {
protected $table = 'my_users';
}
Otherwise, a lowercase, plural format is automatically used, based on the classname of the Model. (class_basename($this))
As shown here... (Illuminate/Database/Eloquent/Model.php)
/**
* Get the table associated with the model.
*
* #return string
*/
public function getTable()
{
if (isset($this->table)) {
return $this->table;
}
return str_replace('\\', '', Str::snake(Str::plural(class_basename($this))));
}

laravel4 How can i use eloquent in my controller?

I would like to create a relationship between 2 tables with eloquent but i can't find exactly how to proceed...
Here are my 2 models with relationship :
Table "etablissement":
<?php class Etablissement extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'etablissement';
public function annulation()
{
return $this->hasMany('Annulation');
}}
Table "annulation":
<?php class Annulation extends Eloquent {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'annulation_remboursement';
public function etablissement ()
{
return $this->belongsTo('Etablissement');
}}
In the "Etablissement" table there is an id for each etablissement (id_etablissement) and in the "annulation" there is a column with the id_etablissement. How can i return in my controller a relation in order to have the etablissement's name with the annulation->id_etablissement :
class AnnulationsController extends \BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index()
{
}
It should be something like this within your index method:
$annulation = Annulation::find(1);
$annulation->etablissement->name
The annulation_remboursement table should have a establissement_id field.
Perhaps the error may be in the keys of the relation.
In https://laravel.com/docs/4.2/eloquent#one-to-one we see:
Take note that Eloquent assumes the foreign key of the relationship based on the model name. In this case, Phone model is assumed to use a user_id foreign key. If you wish to override this convention, you may pass a second argument to the hasOne method. Furthermore, you may pass a third argument to the method to specify which local column that should be used for the association:
return $this->hasOne('Phone', 'foreign_key');
return $this->hasOne('Phone', 'foreign_key', 'local_key');
[...] One To Many: Again, you may override the conventional foreign key by passing a second argument to the hasMany method. And, like the hasOne relation, the local column may also be specified:
return $this->hasMany('Comment', 'foreign_key');
return $this->hasMany('Comment', 'foreign_key', 'local_key');
* You should also checkout Defining The Inverse Of A Relation at the same page.
So, in your case you have a key named id_etablissement but Laravel is searching for etablissement_id. If you wish to override this behaviour and specify a key you should do something like:
protected $table = 'etablissement';
public function annulation()
{
return $this->hasMany('Annulation','id_etablissement');
}
and according to "The Inverse Of A Relation"
protected $table = 'annulation_remboursement';
public function etablissement ()
{
return $this->belongsTo('Etablissement','id_etablissement');
}
Note that I didn't put any of the local keys, but those will be the third parameter of the relation.

Resources