HasManyThrough Polymorphic model or morphto without intermediary? - php

Consider the following Eloquent docs polymorphic example as a starting point:
(https://laravel.com/docs/5.5/eloquent-relationships#polymorphic-relations)
In this variation, it's a bit of a reverse direction. User hasMany Medias. Medias are polymorphic and can be any model.
Goal is to be able to query User->media relation and return the corresponding post and video models.
Issue: Currently the only way I can get the post or video models, is query the mediable relation: User->media->mediable. In order to map the polymorphic models, we need the medias table where the media_id and media_type are recorded.
A HasManyThrough relation seems like the logical solution, but appears to be constrained by having to know the desired model type.
Is there a way to achieve my desired results?
DB:
user
id
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
medias
id - integer
user_id - integer
media_id - integer
media_type - string
Models:
class User
public function media()
{
return $this->hasMany('App\Media');
}
class Media
public function mediable()
{
return $this->morphTo();
}
class Post
public function Media()
{
return $this->morphMany('App\Media', 'mediable');
}
class Videos
public function Media()
{
return $this->morphMany('App\Media', 'mediable');
}

Related

Eloquent way of defining a one-to-one relationship using intermediate table to avoid foreign key cycle

Laravel rookie here. I've got titles and reviews. A title can have many reviews, a review belongs to a title.
So far, my tables look unsurprisingly as follows:
titles:
- id (pk)
...
reviews:
- id (pk)
- title_id (fk)
...
Now, I want a title to have zero or one (so-called) top reviews.
My first instinct was to add a nullable top_review_id column to the titles table, but I wanted to avoid a cycle of foreign keys. So instead, I created a third table called top_reviews as follows:
top_reviews:
- id
- title_id (fk, unique)
- review_id (fk, unique)
That way a title is guaranteed to have at most one top review and a review cannot be the top review for multiple titles. (I do realise that it is still possible to have a top review entry where the review actually belongs to a different title, but that's okay.)
My question is how do I wire that up cleanly in Laravel (7.x) ideally using Eloquent ORM relationships and following the framework's best practices?
So far I've got this:
class Title extends Model {
public function reviews() { return $this->hasMany(Review::class); }
public function topReview() { /* ??? */ }
}
class Review extends Model {
public function title() { return $this->belongsTo(Title::class); }
}
I've considered the following:
I could manually build something ugly like return Review::find(DB::table('top_reviews')->select('review_id')->where('title_id', $this->id)->get());, but I suspect there is a nicer Laravelesque way for these trivial relationships.
Simply using hasOne() doesn't seem to be the solution either since it will assume a different table name (namely reviews instead of top_reviews) and there is no way to specify a custom table.
Defining a model TopReview seems clumsy, but perhaps it is my best bet. I suppose that would allow me to define topReview() as hasOneThrough(Review, TopReview).
Feel free to correct me if I'm on the wrong track.
Thanks.
With belongsToMany relationship
class Title extends Model
{
public function reviews()
{
return $this->hasMany(Review::class);
}
public function topReview()
{
return $this->belongsToMany(Review::class, 'top_reviews', 'title_id', 'review_id');
}
}
Anyways you can skip that top_reviews table and just save top_review_id into your titles table and I think that's more efficient
Depend on you requirement
You one only one top reviews so I think you should Has One Through but if you want to have multi top reviewer on 1 title you should use Many To Many.
Has One Through
I will remove title_id if i use it
top_reviews:
- id
- review_id (fk, unique)
class Title extends Model {
public function topReview() {
return $this->hasOneThrough(TopReview::Class, Review::class);
}
}
Many To Many
top_reviews:
- id
- title_id (fk, unique)
- review_id (fk, unique)
class Title extends Model
{
public function topReview()
{
return $this->belongsToMany(Review::class, 'top_reviews', 'title_id', 'review_id');
}
}
but as you describe above. I think I will use has one through.

Laravel table relationship to pivot table

I'm confused on how to get my model setup in laravel with a table that is connected to a pivot table.
Here's the problem
Say I have
Locations
id
name
area_types
id
name
area_type_location (Pivot table)
id
location_id
area_type_id
area_test
id
area_type_location_id
clean
headCount
Relationship between the tables are different areas type belongs to different locations.
i.e: beach, 25m pools, kids pools, bbq, etc
area_test is connected to the pivot table because the test has to be generated from area that exists, in this case it is area that is registered under different locations. Thus it has to be tested daily, measured, etc.
I understand the structure between area_types and locations for many to many relationship, however I can't get over my head of how do i structure my area_test model? How do I get the data from locations table -> where are my test?
Should I create a model for my pivot table? Is that a good practice in laravel?
Does anyone has the same use case?
I read about eloquent has many through
relationship but I understand that it does not mention about getting through pivot table. I don't quite get if my use case is the same.
Thanks
Finally, apparently there are a couple of way to get data from locations table to area_tests
Tried at tinker and it works,
First Option
I need to create a Pivot model for my Pivot table:
class LocationAreaType extends Pivot{
public function location(){
return $this->belongsTo(Location::class);
}
public function areaType(){
return $this->belongsTo(AreaType::class);
}
public function AreaTests(){
return $this->hasMany(AreaTest::class, 'area_type_location_id');
}
}
I can use hasManyThrough relation that I need to create in my Location table
public function areaTests()
{
return $this->hasManyThrough(
AreaTest::class,
LocationAreaType::class,
'location_id',
'area_type_location_id');
}
this way I can get the areaTests easily by $location->areaTests, My problem was not determining the area_type_location_id as foreign. You need to determine this, apparently when I extends pivot and use hasMany laravel does not auto recognise the Foreign key by itself.
Second option
Another way to access it is from the relation table, I can define withPivot in the areaTypes() relation then access it like this:
$location->areaType[0]->pivot->areaTests
Since laravel only recognise foreign key from both tables location_id and area_type_id, I have to include the id of the pivot table to get the AreaTest table data
So in the Location model I have to get the column
public function areaTypes()
{
// Get the ID of the pivot table to get the poolTests table (connected with ID column)
return $this->belongsToMany(AreaType::class)
->using(AreaTypeLocation::class)
->withPivot('id');
}
There is no need to create a new model for pivot table.
Just declare in Location model below code:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function area_types()
{
return $this->belongsToMany('App\AreaType', 'area_type_location', 'location_id', 'area_type_id');
}
and declare below code in AreaType model:
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function locations()
{
return $this->belongsToMany('App\Location', 'area_type_location', 'area_type_id', 'location_id');
}
every time you need to get for example the locations of an area_type in every controller, you can call the function like this: $areatype->locations()->get();
Don't forget to create area_type_location table migration.

Laravel polymorphic relationship with pivot

Assume I have posts and videos that can be seen by multiple users.
- users
- id
- posts
- id
- videos
- id
- user_accessables (pivot)
- id
- user_id
- accessable_id
- accessable_type
In an example like that, I have set my User relationship like so but something feels wrong
class User extends Model {
public function posts() {
return $this->morphedByMany(
Post::class,
'accessable',
'user_accessables'
);
}
public function videos() {
return $this->morphedByMany(
Video::class,
'accessable',
'user_accessables'
);
}
public function allowedEntities() {
return ($this->posts)->merge($this->videos);
}
}
With the allowedEntities() I can get a collection of both models joined together.
However, I think the use of polymorphic relationship is returning a collection of entities through relationship rather than needing a combiner relationship, right?
I am having problems with understanding polymorphic with pivot table (the tag example in documentation doesn't seem like same scenario).
Because now I can't do:
$collection = collect(); // multiple models of Video & Post
$user->allowedEntities()->sync($collection);
As #Jonas Staudenmeir said is not possible to have a relationship that returns all related model, BUT you can define a method on the model that returns a query builder object with all entities you need (search with on the docs).

Eloquent model inheritance - multi-table design

Assume we have two db tables: posts and threads:
threads
id - integer
title - string
posts
id - integer
body - text
created_at - timestamp
thread_id - integer (fk)
and two Eloquent models:
class Post extends Model {
public function thread()
{
return $this->hasOne('App\Thread');
}
}
class Thread extends Post {
protected $table = 'threads';
public function post()
{
return $this->belongsTo('App\Post');
}
}
What I want to achieve is Thread object having id, title, body, created_at attributes, while Post object having id, body, created_at attributes.
Yet, I still get error:
Column not found: 1054 Unknown column 'body' in 'field list'
which is MySQL error that basically means that Laravel's trying to look up the body column in the threads table. However, it is stored in the posts table.
What you are trying to do is unnecessary with Eloquent.
The data that is retreived from the database iss storred in the $attributes property.
So unless you are sharing any behaviour you don't need to inherit from anything other than Model.
OK, so here is my updated answer after some research on your question. As you have defined table schema i.e
threads
id - integer
title - string
posts
id - integer
body - text
created_at - timestamp
thread_id - integer (fk)
This means Thread has many or just one (depends on relation) Post, and Post belongs to Thread, so the relationship would be something like this
class Thread extends Model{
protected $table = 'threads';
public function post()
{
return $this->hasMany('App\Post');//Or hasOne('App\Post')
}
}
class Post extends Model {
public function thread()
{
return $this->belongsTo('App\Thread');
}
}
What you want to achieve is Thread object having id, title, body, created_at attributes, while Post object having id, body, created_at attributes.
Now according to current scene, One Thread, may have more than one Post. Here you'll always get body and created_at in relationship for object Thread e,g Thread::with('post')->get();, and vice versa

Laravel eloquent model - get rows using intermediate table

I have 3 tables users, posts and photos.
post table has one - one relation to photos & users like, post.user_id=users.id and post_photo_id=photos._id.
I use
public function posts(){
return $this->hasMany('Post');
}
and I get all the posts by user using $user->posts().
What I need is to get all the photos by user, something like $user->photos.
SELECT photos.*
FROM photos
JOIN posts ON posts.photo_id=photos.id
JOIN users ON users.id=posts.user_id
WHERE user_id=1
Note: photos table has just 2 fields, id & photo.
In your User model, create a relationship like
Class User Extends Model
{
...
public function photos()
{
return $this->hasManyThrough('Photos','Posts','photo_id','id');
// Params (all strings): Final Model, Intermediate Model, Intermediate Model Key (posts), final Model Key (your photos)
}
public function Posts()
{
return $this->hasMany('Posts');
}
...
}// end class
Then in your controller, you'll just call your data with the relationships. This assumes you're hinting, but you get the idea...
$picturesByUser = $this->user->with(['posts','photos'])->find($id);
finally, in your blade, just eager load them...
#foreach(...)
$user->photos->pathToPicture;
$user->posts->pictureTitle;
#endforeach
Straight out of the Laravel Docs

Categories