Database relationship name & best practice for it, in my laravel app - php

I have a database schema in my laravel app. In that schema, there are three models - Boss, Employee & Review.
A Boss can have many employees. An Employee can have many bosses(we can consider bosses from previous jobs). An Employee can Review his/her Boss. Boss can't Review anyone, so only Employee can review.
Thus, there are these relationships -
Employee & Boss has many-to-many relationship
Employee & Review has one-to-many relationship ( one Employee can Review multiple Bosses, thus having multiple reviews from his/her side ).
As you can see there is no direct relationship between Boss & Review. But, the query is -
What are the reviews for Boss 'x'?
To answer this query in laravel, I first thought that it had 'hasManyThrough' relationship. But, 'hasManyThrough' works only when Boss & Employee have 'one-to-many' relationship. That is, when one Boss can have multiple Employee but not vice-versa.
So, my question is - Is there a relationship which is applicable in this scenario(like polymorphic etc.)? If yes, how to use it in laravel?

No need for that extra table with reviews.
Here's all you need - first the tables:
bosses: id, ...
employees: id, ...
boss_employee: id, boss_id, employee_id, review (nullable), ...
Then the models:
// Boss model
public function employees()
{
return $this->belongsToMany('Employee')->withPivot('review');
}
public function reviews()
{
return $this->hasMany('Review');
}
// Employee model
public function bosses()
{
return $this->belongsToMany('Boss')->withPivot('review');
}
public function reviews()
{
return $this->hasMany('Review');
}
// Review model
protected $table = 'boss_employee';
public function boss() // or eg. reviewed
{
return $this->belongsTo('Boss');
}
public function employee() // or eg. reviewer
{
return $this->belongsTo('Employee');
}
Now, with this setup you can do this:
// get boss and his review
$boss = $employee->bosses->first();
$review = $boss->pivot->review;
// get review for given boss of an employee
$review = $employee->reviews()->where('boss_id', $bossId)->first();
// get bosses with reviews
$bosses = Boss::whereHas('reviews', function ($q) {
$q->whereNotNull('review');
})->get();
// and so on and so forth
You can enhance your Review model by adding global scope so it returns only the rows from boss_employee table having not null review field. This would be pretty much the same as SoftDeletingScope, but the conditions need to be swapped - by default whereNotNull, instead of whereNull in the SoftDeletingScope.

Related

How to extend a Users model with another many to many relationship models Skills/Levels?

I am trying to extend a User model with associated Skills and Levels. Where all users will have different skill levels for a variety of skills. Im trying to figure out the most efficient way to setup the relationships to accomplish this.
Im currently setting Skill to Level as a many to many relationship. User to skills_has_levels is one to many but of no current use.
There are many skills a user can have and each skill can have a different level from no experience to expert.
[Tables]
users
id, username, email, pwd, token, timestamps
skills (ie. laravel, angular, bootstrap, etc)
id, name, order
levels (ie. none, beginner, intermediate, expert)
id, name, value
skills_has_levels
id, skills_id, levels_id, users_id
We have three models:
1) User (has many skills with levels)
2) Skill (has many levels)
3) Level (has many skills)
In the Skill model: (to have the users_id available: unused)
$this->belongsToMany('App\Level’)
->withPivot(‘users_id’);
To list all users, their skills, and levels :
$users = User::all()->toArray();
foreach($users as $user)
{
echo "User ID: ".$user->id;
$skills = Skill::where(‘users_id’, ’=‘, $user->id)->get()->toArray();
print_r($skills);
echo "\n\n\n";
}
Ultimately, I would want the User model to return a skills/levels array of all the associated user skills & their levels: skill name, skill order, level name and level value.
What I currently do works but i feel it could be done better.
whats the best way to structure the relationships for the User model to connect to skills names and the associated levels values?
Is it possible to related the Skill/Level directly to the User model? So i only have to use the User model to access all of the associated skill and levels.
Or are my relationships wrong? Could change the models to this relationship:
A User has many Skill
Skill has many skill types
Skill has many skill levels
Thanks in advance.
Your database makes the work harder i guess
You can use dynamic properties if you want to make it available in usermodel
Models User <- -(Many to many) -> Levels <- -(Many to many)
protected $appends = ['skills_levels'];
public function getSkillsLevelsAttribute()
{
$skills = // query to get all skills ->pluck('skill','id');
$this->load('levels.skills')
$levels = $this->levels->groupBy('skills.skill_id')->toarray();
$output = [];
foreach ($skills as $id => $skill) {
$output[$skill] = [];
if (isset($levels[$id])) {
$output[$skill] = $levels[$id] // all levels user have for this skill
}
}
return $output
}
Well there are few changes to be done on the table skills_has_levels
you should change
skills_id to shareable_skills_id
levels_id to shareable_levels_id
and finally update your classes like below
Class SkillsHasLevels
{
public function shareable()
{
return $this->morphTo();
}
}
Class Skill
{
public function shares()
{
return $this->morphOne(SkillsHasLevels::class, 'shareable');
}
}
Class User
{
public function skills()
{
return $this->morphedByMany(Skill::class, 'shareable', 'shares');
}
}
I hope this will return you the skills of the user

Three-way pivot table or complex relationship in Laravel

I have two tables, shipments and customers. In the real world, a customer can be related to a shipment in three ways: as the biller, the destination and/or the origin.
So my question here is, do I have a pivot table with three columns, one for the shipment_id, one for the customer_id, and one for the relationship_type id? Or do I have separate tables? I'm not sure how best to approach this as it's the first of it's kind that I've run up against.
I faced this couple weeks ago and I came up with a solution.
Assuming that one customer can have different relations to different
shipments.
First of all you need a new model for customer roles obviously that model it will be Relation model.
First approach: You could solve this by using more than one pivot table which works but its not a good database design. I solved it first like this but realized its not optimal choice when it comes to db.
Second approach: You could solve this by defining pivot table as a model, but I havent tried that way even though I know it works and its a solution.
Better approach: use one pivot table for three models. In that case you have to define pivot table when you define a relationship example :
Customer model:
public function relations()
{
return $this->belongsToMany(Relation::class, 'customer_relation_shippment');
}
Relation model:
public function customers()
{
return $this->belongsToMany(Relation::class, 'customer_relation_shippment');
}
and the other model as well.
now lets say you want to add a relation to a customer.
Lets grab first customer and first shipment and say we want to add a relation as a biller:
$customer = Customer::first();
$shipment = Shipment::first();
$relation = Relation::where('name','biller')->get();
$customer->relations()->attach($shipment->id, ['relationship_type'=>$relation->id]);
By using only one pivot table of course its a bit more complex to perform operations towards those models like CRUD, but when it comes to database design/optimazation of course it is the right choice! Note that I came to this conclusion after dealing with a similar real world issue and it turned way more faster db interaction then using more than one pivot.
Here is how I would design your project.
I don't think you even need a pivot table or many-to-many relationship.
Note: For clarity and avoiding confusion with the User, I will use Account to refer to what you call a Customer. At the end you used customer account in your comments.
You have a shipment that relates to three different entities. However, those entities are represented by the same data model in your database: the Account model.
A basic one-to-many relationship will suffice.
An account can have many shipments. And the shipment belongs to one account.
Now, how to add the "Type" of the relationship? We don't need a pivot table, we just add another one-to-many relationship.
An Account as biller may have many shipments, and the shipment belongs to one biller.
An Account as origin may have many shipments, and the shipment belongs to one origin.
An Account as destination may have many shipments, and the shipment belongs to one origin.
To explain, here is an example code:
We have three models: User, Account, and Shipment
Let's start with the schema:
Schema::create('accounts', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
Schema::create('shipments', function (Blueprint $table) {
$table->increments('id');
$table->string('from');
$table->string('to');
$table->unsignedInteger('biller_id');
$table->unsignedInteger('origin_id');
$table->unsignedInteger('destination_id');
$table->foreign('biller_id')
->references('id')->on('accounts');
$table->foreign('origin_id')
->references('id')->on('accounts');
$table->foreign('destination_id')
->references('id')->on('accounts');
$table->timestamps();
});
We have three columns referencing the id on the accounts table.
For the models and the relationships:
Account Model:
class Account extends Model
{
public function billerShipments()
{
return $this->hasMany(Shipment::class, 'biller_id');
}
public function originShipments()
{
return $this->hasMany(Shipment::class, 'origin_id');
}
public function destinationShipments()
{
return $this->hasMany(Shipment::class, 'destination_id');
}
public function users()
{
return $this->belongsToMany(User::class);
}
}
Shipment Model:
class Shipment extends Model
{
public function billerAccount()
{
return $this->belongsTo(Account::class, 'biller_id');
}
public function originAccount()
{
return $this->belongsTo(Account::class, 'origin_id');
}
public function destinationAccount()
{
return $this->belongsTo(Account::class, 'destination_id');
}
}
Example to create a shipment
$billerAccount = \App\Account::create(['name' => 'account b']);
$originAccount = \App\Account::create(['name' => 'account a']);
$destinationAccount = \App\Account::create(['name' => 'account c']);
$newShipment = \App\Shipment::create([
'from' => 'city 1',
'to' => 'city 2',
'biller_id' => $billerAccount->id,
'origin_id' => $originAccount->id,
'destination_id' => $destinationAccount->id,
]);
echo $billerAccount->billerShipments()->count(); // 1
echo $originAccount->originShipments()->count(); // 1
echo $destinationAccount->destinationShipments()->count(); // 1
echo $newShipment->billerAccount->name === $billerAccount->name; // 1
echo $newShipment->originAccount->name === $originAccount->name; // 1
echo $newShipment->destinationAccount->name === $destinationAccount->name; // 1
For the account-user relationships, it can be many-to-many or one-to-many depending on your requirements.

Laravel Eloquent Relationship Filter by Column of Relationship

I have 3 Tables
Product:[Table]
Person:[Table]
Payment:[Table]
Many To Many Relationship Between Product and Person
One To Many Relationship Between Product and Payment (One Product Has Many Payments)
One To Many Relationship Between Person and Payment (One Person Has Many Payments)
Payment:[Table]
id
person_id
product_id
amount
The thing is that i am trying to get All persons with products and Product payments filtered by person_id.
Reason is that i dont want to have any other persons record in payments.
This is actually the query i am running yeah i know its wrong cuz i cant filter it by person_id.
$query = $person::with('product', 'payment')->where('is_active', 1);
I want to achieve something like this..
$query = $person::with(array('product', 'payment' => function ($query) {
$query->where('person_id', '=', 'person.id');
}))->where('is_active', 1);
If you setup your relations like:
class Person extends Model
{
public function payments()
{
return $this->hasMany(Payment::class);
}
}
class Payment extends Model
{
public function product()
{
return $this->belongsTo(Product::class);
}
}
Then you should be able to do:
$person = Person::with('payments.product')->where('id',$personId)->where('is_active',1)->first();
Which will return a Person with all the relations loaded and you can access those like:
#foreach($person->payments as $payment)
{{$person->name}} bought {{$payment->product->name}} for {{$payment->amount}}
#endforeach

PHP Laravel 5.2: Eloquent relationship for subscriptions

I think I got a little stuck and I just need someone to clarify things. So what I got is a user system which includes subscriptions for people to "subscribe" to their content (as you already know it from FB, Twitter, YT etc).
My database model looks like this:
Users
id
username
Subsccriptions
id
user_id
sub_id
Currently I have one model for Users and one Model for Subscriptions. The model from the user:
public function subscriptions()
{
return $this->hasMany('App\Subscription');
}
In comparison, my subscription object:
public function user()
{
return $this->belongsTo('App\User');
}
So in my personal opinion this is a 1:many relationship. If I call the user object with User->subscriptions()->get() I can get the subscription and it's sub id, so I know who THE CURRENT user has subscribed.
People told me the opposite and it's supposed to be a many-to-many relationship. Did I do something wrong? Can I automatically convert the sub_id into a user through relationships in Eloquent?
// EDIT:
Here is the current code to receive my subscribers for a user
public function index()
{
$subs = Auth::user()->subscriptions()->get()->all();
$submodels = [];
foreach($subs as $sub) {
array_push($submodels,User::find($sub->sub_id));
}
return view('home', [
'subscriptions' => $submodels
]);
}
}

Building ternary relationship using Laravel Eloquent Relationships

Have three entities:
Project
Employee
Employment
Problem description: Employee can work on many projects and for each he has one employment. I want to have access to all projects and your referred employments of a certain employee.
I'm not sure but the relationship must look like a ternary:
The physical table is not defined yet. So, be free to design (most basic) them.
And my question:
How i can build using Laravel Eloquent Relationships?
Basically your four tables will be something like:
employee
id
...your fields
project
id
...your fields
employments
id
...your fields
employee_project
employee_id
project_id
employment_id
You can split the problem in 2 by 2 relations:
class Employee extends Model{
public function projects(){
return $this->belongsToMany("Project")
}
// Second relation is Optional in this case
public function employments(){
return $this->belongsToMany("Employment", 'employee_project')
}
}
A Project model
class Project extends Model{
public function employees(){
return $this->belongsToMany("Employee")
}
// Second relation is Optional in this case
public function employments(){
return $this->belongsToMany("Employment",'employee_project')
}
}
A Employment model
class Employment extends Model{
public function employees(){
return $this->belongsToMany("Employee")
}
public function projects(){
return $this->belongsToMany("Project")
}
}
At this point in your controller you can manage your relation, for example if you want to add to $employee, the project with id 1 with the employment with id 2 you can simply
$employee->projects()->attach([1 => ['employment_id' => '2']]);
I hope this answer to your question.
If you need timestamps in your pivot table, add ->withTimesetamps() to your relationships.
Employee has Employment
Employment has Project

Categories