I need an help understanding Eloquent has many relation in this particular scenario.
This is the scenario:
Airport Table
| id | code | name |
---------------------
| 1 | XXX | XXX |
---------------------
Route Table
| id | from | to |
-------------------
| 1 | 1 | 2 |
--------------------
As above, ONE airport table 'id' can be a foreign key in 'from' OR 'to' Route Table column.
What I'd like to do is to retrieve all records from Airports table with its routes (Where the airport can appear as a departure or as a destination).
For example:
[
{
'id': 1,
'code': 'JFK',
'name': 'John F. Kennedy International Airport'
'routes' : [
{
'id': 1,
'from': 1 //JFK airports table id
'to': 22 //somewhere
},
{
'id': 1,
'from': 334 //somewhere
'to': 1 //JFK airports table id
}
]
}
]
Obviously I know how to do it with a SQL query or using Eloquen but what I want to understand is if it can be done natively in the Model by expressing a relationship.
Maybe adding to the relationship
->orWhereColumn('Route.to','AirportTable.id')
The "Eloquent has many relation" you are imagining does not exist, and also does not make sense as a relation. Pivot table columns have semantic meaning, just like in your case of from and to.
Firstly, this is not a HasMany, it is a BelongsToMany (Many to Many) in Eloquent speak. If you want to do this purely with relations, then you will need to define both of them. Airports are destinations when they are in the to column, and Airports are origins when they are in the from column.
To get all routes an Airport is a part of, we can simply merge the results of the two relations.
The $appends attribute is used to automatically append the data when serializing.
The $with attribute is used to always eagerload these relations when retrieving Airports from the database.
Airport.php
protected $appends = ['routes'];
protected $with = ['destinations', 'origins'];
public function destinations()
{
return $this->belongsToMany(Airport::class, 'routes', 'from', 'to')
->withPivot(['id', 'from', 'to']);;
}
public function origins()
{
return $this->belongsToMany(Airport::class, 'routes', 'to', 'from')
->withPivot(['id', 'from', 'to']);
}
public function getRoutesAttribute()
{
return $this->destinations->pluck('pivot')
->merge($this->origins->pluck('pivot'));
}
Now if you were to do:
Airport::query()->take(5)->toArray();
You would see all of the routes associated on each of the Airport results.
Related
My client has an old site built on 4.x that I'm trying to get to work with 7.4. I have most of it working, but am stuck on a belongsToMany relationship
I have a Manufacturer class that has a many-to-many relationship with Subcategories, through a table named membercategory. However, the subcategories property always returns an empty array. What am I missing?
membercategory
+------+------------------+-----------------+
| ID | FKManufacturerID | FKSubCategoryID |
+------+------------------+-----------------+
| 3203 | 24 | 301 |
| 3202 | 24 | 292 |
| 3201 | 24 | 295 |
+------+------------------+-----------------+
and my Manufacturer class
class Manufacturer extends Model {
public function subcategories() {
# have tried swapping the two FK parameters, same result
return $this->belongsToMany('App\Subcategory','membercategory','FKSubCategoryID','FKManufacturerID');
}
}
I'm testing it in my controller using this
dd($manufacturers[0]->subcategories);
where $manufacturers[0] returns the object for Manufacturer ID=24
According to the documentation, your code should look like this:
class Manufacturer extends Model {
public function subcategories() {
return $this->belongsToMany('App\Subcategory','membercategory','FKManufacturerID','FKSubCategoryID');
}
}
Looking at the belongsToMany signature:
public function belongsToMany(
$related,
$table = null,
$foreignPivotKey = null,
$relatedPivotKey = null,
$parentKey = null,
$relatedKey = null,
$relation = null) {}
You will see that you need to have the related column in the 4th param and the foreign one in the 3rd.
EDIT
Make sure the types of columns in the model tables and the pivot table match.
Furthermore, according to the belongsToMany signature, you should add the parentKey and relatedKey params if they are not the default: id ( ID != id )
Here is a list of eloquent model conventions in Laravel 7.x and you can see:
Eloquent will also assume that each table has a primary key column named id. You may define a protected $primaryKey property to override this convention:
That means that provided both your model tables have the primary key columns named "ID" ( as you showed you do in the chat ) your code should look like this:
class Manufacturer extends Model {
public function subcategories() {
return $this->belongsToMany(
'App\Subcategory',
'membercategory',
'FKManufacturerID',
'FKSubCategoryID'
'ID',
'ID'
);
}
}
Just replace the positions of third and fourth columns like this:
return $this->belongsToMany('App\Subcategory', 'membercategory', 'FKManufacturerID', 'FKSubCategoryID');
I am having trouble wrapping my head around the HasManyThrough relationship in Laravel 5.8 which I think is what I need to use, but I am not sure how to implement. I've tried following along a Laracasts video where Jeffrey describes it and read the docs, but I can't seem to grasp the concept and feeling rather stupid right now.
How should I define the relationships in my models and write the correct Eloquent query in my ProductsController to display posts in a product view that shares the same tag id?
For example I have a post tagged with "mbti" and I have a product tagged also with "mbti". How would I go about displaying those related posts in the relevant product view? I've managed to display the tags in the view so far, but I want the posts associated with that tag to display as well. I appreciate any guidance on how I should approach this.
I have 3 tables and 2 pivot tables (some column names removed for brevity):
| products |
+----------+
|id |
------------
| posts |
+----------+
|id |
------------
| tags |
+----------+
|id |
------------
| post_tag |
+----------+
|post_id |
|tag_id |
------------
| product_tag |
+-------------+
|product_id |
|tag_id |
---------------
My models:
Post.php
public function tags() {
return $this->belongsToMany('App\Tag')->withPivot('tag_id');
}
Tag.php
public function posts()
{
return $this->belongsToMany('App\Post')->withPivot('post_tag');
}
public function products()
{
return $this->belongsToMany('App\Product')->withPivot('product_tag');
}
Product.php
public function tags()
{
return $this->belongsToMany('App\Tag')->withPivot('product_id');
}
I ended up adding a tag_id column to my products table and referencing the tag id there. A product in my application can only be assigned one tag, so the need for a pivot table was kind of redundant and added unnecessary complexity.
In my Tag.php model, I defined the relationship as:
public function products()
{
return $this->hasManyThrough('App\Post', 'App\Product');
}
and in my ProductsController.php I select the tags as such in mthe show method:
$tag = Tag::where('id', '=', $product->tag_id)->first();
if($tag) {
$tags = Tag::whereName($tag->name)->first();
}
I have a students table and fees table.
students
--------------
|id | name |
--------------
fees
---------------------------------------------------------
| id| student_id | paid_fees | created_at | updated_at |
---------------------------------------------------------
Here, student_id from fees is foreign key that references to id on students.
I have two models for these tables, and I have defined a hasMany relationship between them.
Because ,A student can submit the total fees in parts, So he can have more than one record in fees table
Student.php
lass Student extends Authenticatable
{
use Notifiable;
protected $fillable = [
'name', 'roll_no', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
protected $casts = [
'email_verified_at' => 'datetime',
];
public function fees(){
return $this->hasMany('App\StudentFees');
}
}
Fee.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Fees extends Model
{
public function student(){
return $this->belongsTo('App\Student');
}
}
I tried to get all the occurenece of the records related to the srudent_id.
$student->fees->where('student_id', $student - >id)
It gives me an arrays of the records as a object. Please suggest if there is a better way.
I am trying to get paid_fee record which is related to a student_id with created_at or updated_at record too.
Maybe like this: [ [2500, '2019-05-19], [3500, '2019-06-28'] ]
So I am trying to get a list of all submitted fees along with date related to student_id.
Once you get your StudentFees object you can access the Student with just $studentFee->student.
But if you need absolutely that kind of output, you can write something like
$studentFees = StudentFees::all();
if ($studentFees) {
$output = $studentFees->map(function($fee) {
return [ $fee->paid_fees, $fee->student->created_at ];
});
// do whatever you want with $output;
}
In my thinking, you created your file Fee.php and your class model is StudentFees. it seems to be not match.i think you should rename that file is StudentFees.php.
I would like to be able to output leaders in a section by their position in a group. I'm thinking of combining these tables through a pivot table ordered by position from the groups table.
These are the three class I have relationships established
class Leader extends Model
{
public function groups()
{
return $this->belongsToMany('App\Group');
}
}
class Group extends Model
{
public function leaders()
{
return $this->belongsToMany('App\Leader');
}
}
class GroupLeader extends Model
{
// Maybe some function here?
}
I would like to be able to get the groups in the controller and pass them individually kinda like this.
$group1 = Leader::where('group', '=', 'group1_name')->orderBy('position', 'asc');
$group2 = Leader::where('group', '=', 'group2_name')->orderBy('position', 'asc');
return view('page.leadership')->with([
'group1' => $group1,
'group2' => $group2,
]);
Then I would pass the variables to include files which would loop through them in order.
Here is my table structure
leaders
--------
| id
| first_name
| last_name
| deceased
----------
groups
----------
| id
| group
| title
| position
-----------
group_leader
-------------
| id
| group_id
| leader_id
------------
Try it
$groups = Leader::whereIn('group', ['group1_name', 'group2_name'])
->orderBy('position', 'asc')
->groupBy('group', 'id')
->get(['group', 'id']);
public function run()
{
factory(App\ProjectProcurementManagementPlan::class, 5)->create()->each(function ($a) {
$a->paps()
->saveMany( factory(App\ProjectsProgramsActivities::class, 5)->make() )
->each(function ($b) {
$b->pap_schedules()->save(factory(App\PapSchedule::class)->make());
$b->procurement_modes()-> ????????;
});
});
}
I have this code here. I want to seed a pivot table after creation of each of the models indicated above. $b->procurement_modes()-> ???????? will be the part where I do the factory stuff to a pivot table.
the pivot table looks like this.
| id | pap_id | procurement_mode_id |
My plan is, to , for the sake of simplicity, I'll just attach a single procurement_mode_id to each ProjectsProgramsActivities created.
I have tried to use
$b->procurement_modes()->sync(factory(App\PAPProcurementMode::class)->make());
but it gives me a
SQLSTATE[HY000]: General error: 1366 In
correct integer value: '' for column 'procurement_mode_id' at row 1 (SQL: insert
into paps_procurement_modes (pap_i, procurement_mode_id) values (1, ))
The factory for that pivot table is this
$factory->define(App\PAPProcurementMode::class, function (Faker $faker) {
return [
'procurement_mode_id' => 1,
];
});
So what would be the best way to do this ?
For those who will encounter problems like this.
I simply
$ids = 1;
$b->procurement_modes()->sync($ids);