How to modify a relationship? - laravel

My app has 2 main modules which are Foo and Bar. It also has 3 types of role: admin, manager & staff.
Each user is assigned to a supervisor, so that every supervisor will have some subordinates assigned to him/her.
For example, staff1 is supervised by manager1 whom is also supervised by admin1.
The current practice for this relationship is implemented in both modules. Therefore each supervisor is in charge for the subordinates in the matter of their Foo and Bar.
User.php
<?php
namespace App;
use Spatie\Permission\Traits\HasRoles;
use Illuminate\Support\Str;
class User {
protected $fillable = ['name','email','password','supervisor_id'];
protected $appends = ['role'];
public function getRoleAttribute(){
return $this->roles[0];
}
public function getNameAttribute($value){
return Str::title($value);
}
public function parent(){
return $this->hasOne('App\UserStructure', 'user_id');
}
public function scopeSupervisor($query){
return $query->where('id', $this->supervisor_id)->first();
}
public function foo(){
return $this->hasMany(Foo::class, 'user_id', 'id');
}
public function bar(){
return $this->hasMany(Bar::class, 'user_id', 'id');
}
}
UserStructure.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class UserStructure extends Model{
protected $fillable = ['user_id', 'parent_id'];
public function user(){
return $this->belongsTo('App\User', 'user_id');
}
}
Role.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model{
protected $fillable = ['name'];
public function roles(){
return $this->belongsTo('App\User', 'role');
}
}
RoleAndPermission.php
<?php
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class RolesAndPermissionsSeeder extends Seeder{
public function run(){
$roles = ['admin','manager','staff'];
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
foreach ($roles as $role) {
Role::updateOrCreate(['name' => $role]);
}
}
}
UserSeeder.php
<?php
use Illuminate\Database\Seeder;
use App\User;
use App\UserStructure;
class UserSeeder extends Seeder{
public function run(){
$items = [
['role'=> 'admin',
'name'=> 'admin',
'email'=> 'admin#myapp.com',
'password'=> 'password',
'supervisor_id'=> 1],
['role'=> 'manager',
'name'=> 'manager',
'email'=> 'manager#myapp.com',
'password'=> 'password',
'supervisor_id'=> 1],
['role'=> 'staff',
'name'=> 'staff',
'email'=> 'staff#myapp.com',
'password'=> 'password',
'supervisor_id'=> 2],
];
foreach($items as $data) {
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
'supervisor_id' => $data['supervisor_id'],
]);
$user->assignRole($data['role']);
}
$userStructure = [
['parent_id'=> 0, 'user_id'=> 1],
['parent_id'=> 1, 'user_id'=> 2],
['parent_id'=> 2, 'user_id'=> 3]
];
UserStructure::insert($userStructure);
}
}
My question is, how do I modify this relationship accordingly so that any supervisor [admin/ manager] will be assigned to the subordinate [manager/ staff] of one module only?
(E.g:
In Foo module, staff1 is supervised by manager1.
While in Bar module, he will be supervised by manager2.)

As i understand, you can add one column to the Role model (like type), and then assign roles to users with a condition

Related

How to use attach with two tables and two connect in laravel?

I use Laravel 8x, and i have two connections:
In .env :
DB_HOST=xx.xx.xx.xx
DB_PORT=xxxx
DB_DATABASE=product
DB_USERNAME=product_sp
DB_PASSWORD=xxxxxxxxx
DB_HOST_PROMOTION=xx.xx.xx.xx
DB_PORT_PROMOTION=xxx
DB_DATABASE_PROMOTION=product_sand
DB_USERNAME_PROMOTION=product_sp
DB_PASSWORD_PROMOTION=xxxxxxxxxxx
In model:
Product.php :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notification;
class Product extends Model
{
use HasFactory;
protected $table = 'product';
protected $keyType = 'string';
public $incrementing = false;
public function discount()
{
return $this->belongsToMany(Discount::class, 'product_discount', 'productId', 'discountId')->withPivot(['id','status', 'quantity', 'createdAt', 'updatedAt', 'createdBy','updatedBy']);
}
}
discount.php :
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Notifications\Notification;
class Discount extends Model
{
use HasFactory;
protected $table = 'discount';
protected $connection = 'promotion';
protected $keyType = 'string';
public $incrementing = false;
}
The product_discount table is an intermediate table of discount and product.
In the controller, I use attach
public function store(Request $request)
{
$req = $request->all();
$product = new Product($req);
$extraFieldPivot = [
'id' => (string) Str::uuid(),
'status' => 1,
'quantity' => 1,
'createdAt' => now(),
'updatedAt' => now(),
];
$product->discount()->attach($req['idDiscount'], $extraFieldPivot)
}
But when I check the data. The result has not been saved to the database. So where did I go wrong, please advise me. Thank you very much.
i think your intermediate table needs to be changed like productId to product_id and discountId to discount_id.
and change your relationship according to your change in table
If your model relationship is correct change your store function to
$public function store(Request $request)
{
$req = $request->all();
$product = Product::create($req);
$extraFieldPivot = [
'id' => (string) Str::uuid(),
'status' => 1,
'quantity' => 1,
'createdAt' => now(),
'updatedAt' => now(),
];
$product->discount()->attach($product, $extraFieldPivot)
}

incorrect table name inserting into using Laravel Excel to Collection

I'm attemptng to insert relational data using the Laravel Excel concern ToCollection
use Maatwebsite\Excel\Concerns\ToCollection;
however the query is attempting to insert into the table objective_years and not objective_year
ObjectivesImport.php
namespace App\Imports;
use App\Models\Objective;
use App\Models\ObjectiveYear;
use Illuminate\Support\Facades\Hash;
use Maatwebsite\Excel\Concerns\ToCollection;
use Illuminate\Support\Collection;
class ObjectivesImport implements ToCollection
{
public function collection(Collection $rows)
{
foreach ($rows as $row)
{
$objectiveDetail = Objective::create([
'description' => $row[0],
'guidance' => $row[1],
'team_id' => $row[2],
'subject_id' => $row[3],
]);
$yearDetail = ObjectiveYear::create([
'objective_id' => $row[4],
'year_id' => $row[5],
]);
}
}
}
I have two models with a many to many relatinship pivot.
Objective
class Objective extends Model
{
use HasFactory;
protected $fillable = [
'description', 'guidance','subject_id','team_id'
];
public function years ()
{
return $this->belongsToMany('App\Models\Year');
}
}
Year
class Year extends Model
{
use HasFactory;
protected $fillable = [
'name'
];
public function objectives()
{
return $this->belongsToMany('App\Models\Objective')->withTimestamps();
}
}
ObjectiveYear
class ObjectiveYear extends Model
{
use HasFactory;
protected $fillable = [
'objective_id', 'year_id'
];
}
Controller
class ImportController extends Controller
{
public function getImport()
{
return view('import');
}
public function import()
{
Excel::import(new ObjectivesImport, request()->file('csv_file'));
return redirect('/')->with('success', 'All good!');
}
}

Laravel Roles and Permissions based on Role specific Ability

I have a project in which I want a Specific page to be viewed by a specific user which have a role of viewing for example I have User 1 that has an Admin Role and the Admin Role has the Ability to View this page in my design I made 3 models Users, Roles, and Abilities
User Model:
<?php
namespace App;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'name', 'email', 'password','district','area','committee','position',
];
/**
* The attributes that should be hidden for arrays.
*
* #var array
*/
protected $hidden = [
'password', 'remember_token',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
public function answer()
{
return $this->hasMany('App\Answer');
}
public function roles()
{
return $this->belongsToMany('App\Role');
}
public function hasRole($role)
{
if ($this->roles()->where('name', $role)->first()) {
return true;
}
return false;
}
public function assignRole($role)
{
$this->roles()->save($role);
}
}
Role Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Role extends Model
{
protected $fillable = ['name'];
public function abilities()
{
return $this->belongsToMany('App\Ability');
}
public function hasAbility($ability)
{
if ($this->abilities()->where('name', $ability)->first()) {
return true;
}
return false;
}
public function assignAbility($ability)
{
$this->abilities()->save($ability);
}
public function users()
{
return $this->belongsToMany('App\User');
}
}
Ability Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ability extends Model
{
protected $fillable = ['name'];
public function roles()
{
return $this->belongsToMany('App\Role');
}
}
This is my UserPolicy:
<?php
namespace App\Policies;
use App\User;
use App\Role;
use Illuminate\Auth\Access\HandlesAuthorization;
class UserPolicy
{
use HandlesAuthorization;
public function view (Role $role)
{
return $role->hasAbility('view');
}
public function manage (User $user)
{
return true;
}
public function edit (User $user)
{
return true;
}
public function update (User $user)
{
return true;
}
public function add (User $user)
{
return true;
}
}
And the Controller of The Policy
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use App\User;
use App\Role;
class MemberController extends Controller
{
public function index(Role $role)
{
$this->authorize('view', $role);
return view ('members.create')->with('users', User::all());
}
public function manage(User $user)
{
$this->authorize('manage', $user);
return view ('members.manage')->with('users', User::all());
}
public function edit(User $user)
{
$this->authorize('edit', $user);
return view ('members.edit')->with('user', User::all())->with('roles', Role::all());
}
public function update(Request $request, User $user)
{
$this->authorize('update', $user);
$user->roles()->sync($request->roles);
return redirect('/members/edit');
}
public function store(User $user)
{
$this->authorize('add', $user);
$this->validate(request(), [
'name' => ['required', 'string', 'max:255'],
'district' => ['required', 'string', 'max:255'],
'area' => ['required', 'string', 'max:255'],
'committee' => ['required', 'string', 'max:255'],
'position' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
]);
$data = request()->all();
$member = new User();
$member->name = $data['name'];
$member->district = $data['district'];
$member->area = $data['area'];
$member->committee = $data['committee'];
$member->position = $data['position'];
$member->email = $data['email'];
$member->password = Hash::make($data['password']);
$member->save();
return redirect('/members/create');
}
}
The index function should be the one related to the function view in the UserPolicy
and this is the can located in my blade.php file
#can('view', \App\Role::class)
<li class="">
<a class="" href="/members/create">
<span><i class="fa fa-user-plus" aria-hidden="true"></i></span>
<span>Add Member</span>
</a>
</li>
#endcan
in the policy when I link it to the name of the role of the logged in user everything works just fine but if I want to link it to an ability of the role it doesn't work so any idea on how the View Function in the UserPolicy should be implemented ?
The first parameter that is passed to the policy is the authenticated User, not its Role. I don't think it works. Maybe if you reimplement using an EXISTS query.
public function view (User $user)
{
return $user->roles()->whereHas('abilities', function ($ability) {
$ability->where('name', 'view');
})
->exists();
}
->exists() turns the query into an EXISTS query, which will return a boolean value if the query finds anything without having to return any rows.
https://laravel.com/docs/7.x/queries#aggregates
You could put that logic into an User method.
# User model
public function hasAbility($ability): bool
{
return $this->roles()->whereHas('abilities', function ($ability) {
$ability->where('name', 'view');
})
->exists();
}
public function view (User $user)
{
return $user->hasAbility('view');
}

How do I interact with the four Eloquent models correctly?

The project consists of four related tables:
orders (id, status, client_email, partner_id)
order_products (id, order_id, product_id, quantity, price)
partners (id, email, name)
prods (id, name, price, vendor_id)
creating 4 models:
<?php
//App\Order.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $fillable = [
'id', 'status', 'client_email', 'partner_id',
];
public function partner()
{
return $this->belongsTo('App\Partner');
}
public function order_product()
{
//belongsToMany - server error
return $this->belongsTo('App\Prod', 'order_products');
}
public function prod()
{
return $this->belongsTo('App\Prod');
}
}
<?php
//App\Partner.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Partner extends Model
{
protected $fillable = ['id', 'email', 'name'];
public function order()
{
return $this->hasOne('App\Order');
}
}
<?php
//App\Order_product.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order_product extends Model
{
protected $fillable = ['order_id', 'product_id', 'quantity', 'price'];
public function order()
{
return $this->hasOne('App\Order');
}
}
<?php
//App\Prod.php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Prod extends Model
{
protected $fillable = ['order_id', 'product_id', 'quantity', 'price'];
public function order()
{
return $this->belongsToMany('App\Order', 'order_products');
}
}
creating Order Resources:
<?php
//App\Http\Resources\Order.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Order extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'status' => $this->status,
'client_email' => $this->client_email,
'partner_id' => $this->partner_id,
'partner_name' => $this->partner->name,
//return server error
'product_id' => Prod::collection($this->order_product),
'name' => $this->prod->name,
];
}
}
creating Prod Resource:
<?php
//App/Http/Resources\Prode.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class Prod extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'price' => $this->price,
];
}
}
And return from Order Controller:
<?php
//App\Http\Controllers\Api\OrderController.php
namespace App\Http\Controllers\Api;
use App\Order;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Resources\Order as OrderResource;
class OrderController extends Controller
{
public function index()
{
$orders = Order::with(['partner', 'order_product', 'prod'])->get();
return OrderResource::collection($orders);
}
}
How do I get the product name in the order?
Order_product seems like a name for a pivot table. This isn't necessary in laravel because the frameworks assumes the table structure by default. The recommended approach is creating the following models:
Order
namespace App;
use Illuminate\Database\Eloquent\Model;
class Order extends Model
{
protected $fillable = [
'id', 'status', 'client_email', 'partner_id',
];
public function partner()
{
return $this->belongsTo('App\Partner');
}
public function products()
{
return $this->belongsToMany('App\Product', 'order_products');
}
public function prod()
{
return $this->belongsTo('App\Prod');
}
}
Product (Prod)
namespace App;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
protected $table = 'prod';
protected $fillable = ['order_id', 'product_id', 'quantity', 'price'];
public function orders()
{
return $this->belongsToMany('App\Order', 'order_products');
}
}
and with the following resources:
OrderResource
<?php
//App\Http\Resources\Order.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class OrderResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'status' => $this->status,
'partner_id' => $this->partner_id,
'partner_name' => $this->partner->name,
'products' => ProductResource::collection($this->products),
'name' => $this->prod->name,
];
}
}
ProductResource
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class ProductResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'price' => $this->price,
];
}
}
more details over many to many relations can be found in the docs.

Laravel Call to undefined method Illuminate\Database\Eloquent\Builder::privilege()

I would like to display privileges('name') instead of idPrivilege in the user collection. I have tried to add a relationship and use it in an Eloquent call but I'm getting an error.
User model
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
protected $table = 'users';
protected $primaryKey = 'idUser';
protected $fillable = [
'name', 'email',
];
protected $hidden = [
'password', 'updated_at',
];
public function privilege()
{
return $this->hasOne(Privilege::class, 'idPrivilege', 'idPrivilege');
}
}
Privilege model
namespace App;
use Illuminate\Database\Eloquent\Model;
class Privilege extends Model
{
protected $table = 'privileges';
protected $primaryKey = 'idPrivilege';
protected $fillable = [
'name',
];
protected $hidden = [
'updated_at',
];
public function user()
{
return $this->belongsTo(User::class, 'idPrivilege', 'idPrivilege');
}
}
UserController
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
class UserController extends Controller
{
public function relationTest()
{
return User::where('idUser', 1)->privilege()->get();
}
}
I'm getting the below error when I use with('privilege') to my User collection is added privilege collection.
Call to undefined method Illuminate\Database\Eloquent\Builder::privilege().
where returns a Builder instance on which a privilege method does not exist, so you can simply use it as such:
return User::find(1)->privilege()->get();;
-- EDIT
User::find(1)->with(['privilege' => function($query) {
$query->select('name');
}])->get();
I can achieve it by using resource:
$user = User::where('idUser', 1)->with('privilege')->first();
return UserResource::make($user);
Inside UserResource:
public function toArray($request)
{
return [
'idUser' => $this->idUser,
'name' => $this->name,
'email' => $this->email,
'privilege' => $this->privilege['name'],
'createdAt' => $this->created_at,
];
}
but was hoping there is simplier method of getting that.
output:
{
"data": {
"idUser": 1,
"name": "Martin",
"email": "martin#martin.martin",
"privilege": "user",
"createdAt": "2019-05-05T01:11:43.000000Z"
}
}

Resources