Laravel polymorphic relationship with pivot - php

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).

Related

HasManyThrough Polymorphic model or morphto without intermediary?

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');
}

Applying hasManyThrough to deeper relationships

The Laravel docs seem to indicate that the hasManyThrough declaration can only be used for relationships that are two levels "deep". What about more complex relationships? For example, a User has many Subjects, each of which has many Decks, each of which has many Cards. It's simple to get all Decks belonging to a User using the hasManyThrough declaration, but what about all Cards belonging to a User?
I created a HasManyThrough relationship with unlimited levels: Repository on GitHub
After the installation, you can use it like this:
class User extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function cards() {
return $this->hasManyDeep(Card::class, [Subject::class, Deck::class]);
}
}
As stated in the comments, hasManyThrough doesn't support this level of specificity. One of the things you can do is return a query builder instance going the opposite direction:
//App\User;
public function cards()
{
Card::whereHas('decks', function($q){
return $q->whereHas('subjects', function($q){
return $q->where('user_id', $this->id);
});
});
}
We're going from Cards -> Decks -> Subjects. The subjects should have a user_id column that we can then latch onto.
When called from the user model, it would be done thussly:
$user->cards()->get();
Well, actually the best solution will be put the extra column to Card table - user_id, if you have so frequent needs to get all cards for the user.
Laravel provides Has-Many-Through relations for 2-depth relation because this is very widely often used relation.
For the relations Laravel does not support, you need to figure out the best table relationship yourself.
Any way, for your purpose, you can use following code snap to grab all cards for the user, with your current relation model.
Assumption
User has hasManyThough relationship to Deck,
So Project model will have following code:
public function decks()
{
return $this->hasManyThrough('Deck', 'Subject');
}
Deck has hasMany relationship to Card
Code
$deck_with_cards = $user->decks()->with("cards")->get();
$cards = [];
foreach($deck_with_cards AS $deck) {
foreach ($deck->cards as $c) {
$cards[] = $c->toArray();
}
}
Now $cards has all cards for the $user.

How to assign relation when working with pivot table in Laravel 4?

Details
I have 3 tables :
catalog_downloads
export_frequencies
export_frequencies_catalog_downloads (Pivot Table)
Diagram
I am not sure if I set the relation between them correctly.
Please correct me if I am wrong.
Here is what I did
In CatalogDownload.php
public function export_frequencies(){
return $this->belongsToMany('ExportFrequency','export_frequencies_catalog_downloads','export_frequency_id','catalog_download_id');
}
In ExportFrequency.php
public function catalog_downloads(){
return $this->belongsToMany('CatalogDownload','export_frequencies_catalog_downloads','export_frequency_id','catalog_download_id');
}
Questions
According to my diagram - Did I assign the relationship correctly ?
I hope I didn't mix up between hasMany and belongsTo
Will I need a class or a model for a Pivot Table ?
Thanks
Since export_frequencies is in the CatalogDownload model you have to invert the ID's because the parameters of belongsToMany are as follows:
1. Name of the referenced (target) Model (ExportFrequency)
2. Name of the Pivot table
3. Name of the id colum of the referencing (local) Model (CatalogDownload in this case)
4. Name of the id colum of the referenced (target) Model (ExportFrequency in this case)
what leads to this function:
public function export_frequencies(){
return $this->belongsToMany('ExportFrequency','export_frequencies_catalog_downloads','export_frequency_id','catalog_download_id');
}
The other function was correct.
If you had some data in your pivot table, for instance a colum with the name someCounter then you will have to tell the relation to load that column when creating the pivot object like this:
public function export_frequencies(){
return $this->belongsToMany('ExportFrequency','export_frequencies_catalog_downloads','export_frequency_id','catalog_download_id')->withPivot('someCounter');
}
That will load the column and make it avalible like this:
$catalogDownload->export_frequencies()->first()->pivot->someCounter;
You will need a separate Pivot Model if you need to do some special handling for the fields or if that pivot itself has a relation of its own but then you might consider using a full blown model instead of a pure Pivot Model.
As an added note to the accepted answer, you are able to set up your many to many relationships without referencing the pivot table and the relevant id's as long as you follow a specific convention.
You can name your pivot table using singular references to the related tables, like 'catalog_download_export_frequency'. Notice the alphabetic order of the singular references.
Then you can simply do:
// CatalogDownload Model
public function exportFrequencies()
{
return $this->belongsToMany('ExportFrequency');
}
// ExportFrequency Model
public function catalogDownloads()
{
return $this->belongsToMany('CatalogDownload');
}
This will then allow you to run queries using the query builder or Eloquent like:
$catalogDownload->exportFrequencies()->get(); // Get all export frequencies for a specific CatalogDownload.
Or
$this->catalogDownload->with('exportFrequencies')->find($id); // Using eager loading and dependency injection, when CatalogDownload is assigned to $this->catalogDownload
Hope this helps!

Laravel: How do I create a custom pivot model?

I'm working on a project that has a many to many relationship between User and Club. This relationship works and I can get the respective objects with: $user->clubs. The pivot table I've named memberships. I can get the pivot data with $club->pivot. Foreign keys are defined for the memberships table in migrations.
However, I'd like the pivot table to be represented by a model so that I can easily update attributes of Membership such as role (Or even add a Role model to Membership!) or status.
I've looked at "Defining A Custom Pivot Model" in the docs, but what it says doesn't work for me, I get:
ErrorException
Argument 1 passed to Illuminate\Database\Eloquent\Model::__construct() must be of the type array, object given
I've also looked at this description of how to do it, but it's more or less the same as above.
Membership model:
class Membership extends Eloquent {
protected $table = 'memberships';
public function user()
{
return $this->belongsTo('User');
}
public function club()
{
return $this->belongsTo('Club');
}
}
Has anyone done this before?
This has been solved by a suggestion by a reddit user to extend Pivot in my Membership model. I also had to add ->withPivot('status', 'role')->withTimestamps() to the relationship declarations in the User and Club models.
You can do this by adding some manual code.
Your Membership model looks ok. You can get all clubs of a $user easily if you define method $user->clubs() where you get all the clubs manually.
clubs() {
$memberships=$this->memberships;
$clubs=new \Illuminate\Database\Eloquent\Collection();
foreach($memberships as $m) {
$clubs=$clubs->merge($m->clubs);
}
return $clubs;
}

Laravel 4 - Pivot Tables

I understand how relationships in Laravel (Eloquent) work, but let's say I've got a many to many relationship between two models, call them Posts and Tags.
So our relationship is fine, from Posts I can access all the related Tags, and from Tags I can get all related Posts.
But... I have the ID of a row in the pivot table, and want to return both the Post and Tag relating to this row, how would I go about doing this?
EDIT:
Let's introduce a third Model, called Foo. This contains the ID of the row in the pivot table,
is there anyway I can create a relationship from this to the two other models?
A Model for your pivot:
<?php
class PostTagPivot
{
public function Post()
{
return $this->belongsTo('Post');
}
public function Tag()
{
return $this->belongsTo('Tag');
}
}
And retrieve Post and Tag via:
$Post = PostTagPivot::find($fooBarId)->Post;
$Tag = PostTagPivot::find($fooBarId)->Tag;

Categories