customize table name polymorphic many-to-many relationship in Laravel? - laravel

Trying to implement a polymorphic many-to-many relationship, similar to the one in the documentation.
In my case, for label instead of tag. And using different table names
apples / oranges / ...
id - integer
name - string
label_types
id - integer
name - string
labels
label_type_id - integer -- foreign ID for LabelType
labeling_type - string
labeling_id - integer - ID of the model this labelType is used for
// Apple / Orange / ...
public function labels(LabelType $type = null)
{
// need to provide table name, as expected default table name is labelings
return $this->morphToMany(Label::class, 'labeling', 'labels');
}
// Label
public function apples()
{
return $this->morphedByMany(Apple::class, 'labeling', 'labels');
}
public function oranges()
{
return $this->morphedByMany(Orange::class, 'labeling', 'labels');
}
This results in an error.
$apple->labels;
Illuminate\Database\QueryException with message 'SQLSTATE[42000]: Syntax error or access violation: 1066 Not unique table/alias: 'labels'
select
`labels`.*,
`labels`.`label_id` as `pivot_label_id`,
`labels`.`label_type` as `pivot_label_type`
from
`labels`
inner join `labels` on `labels`.`id` = `labels`.`label_id`
where
`labels`.`label_id` = 1
and `labels`.`label_type` = 'App\Models\Apple'
In the example above I have added the table name to morphToMany().
Same error when using labelings as table name and adding to Label model: protected $table = 'labelings';
How do I get this to work? I do want to use label_types as it better fits with the rest of my application.
I looked at other polymorphic questions on Stackoverflow, but could not find an answer.

Related

Laravel get nested relationship from polymorphic table

I want to get nested relationship data from polymorphic table, I'm using MongoDB as database in my laravel app so you might notice that i don't use pivot table between movies and genres table for many to many relationship given the example below.
So what I want to achieve is to get the nested data like frontends > genres > movies all in one result.
let's say I have polymorphic frontends table, looks like this
frontends
id - integer
position - integer
frontable_id - integer
frontable_type - string
genres
id - integer
name - string
movie_ids - array[]
movies
id - integer
title - string
genre_ids - array[]
This is the model I have set
use Jenssegers\Mongodb\Eloquent\Model;
class Frontend extends Model
{
public function frontable()
{
return $this->morphTo(__FUNCTION__, 'frontable_type_api', 'frontable_id');
}
}
class Genre extends Model
{
public function movies()
{
return $this->belongsToMany(Movie::class, null, 'genre_ids', 'movie_ids');
}
}
class Movie extends Model
{
public function genres()
{
return $this->belongsToMany(Genre::class, null, 'movie_ids', 'category_ids');
}
}
What i have tried is to get the frontend table first and then its frontable_id relationship, not until the movies table
$frontends = Frontend::with('frontable.movies')->orderBy('position')->get();
return $frontends;
The relation movies has been declared in related model, but it won't get the movies data

Laravel 8 - How to relationship from 3 tables

I have 3 tables:
structurals
id
name
indicators
id
name
groupings
id
structural_id
indicator_id
And Result:
#structural name
indicator name
indicator name
#structuralname
indicator name
I've used method hasMany & hasManyThrough but errors.
In this case, an indicator may belong to one or more structural. And a structural can have one or more indicators.
What this describes is a many-to-many relationship where groupings is only a pivot table. So you should be using the belongsToMany instead:
class Indicator extends Model
{
public function structurals()
{
return $this->belongsToMany(Structural::class, 'groupings');
}
}
class Structural extends Model
{
public function indicators()
{
return $this->belongsToMany(Indicator::class, 'groupings');
}
}
Docs: https://laravel.com/docs/eloquent-relationships#many-to-many
You can also remove the id column from the groupings table, as it is unnecessary.

How can I use type as STRING for polymorphic relation table ids (...able_id) table in laravel?

I will use the db example below (modified the example in laravel docs) to explain.
Table structure (modified example)
posts
id - integer
name - string
videos
id - string // here I have to use "string"
name - string
tags
id - integer
name - string
taggables
tag_id - string // should normally be "integer"
taggable_id - string // should normally be "integer"
taggable_type - string
Model Relationship (modified example)
// Post.php
public function tags()
{
return $this->morphToMany( Tag::class, 'taggable', 'taggables', 'taggable_id', 'tag_id');
}
// Video.php
public function tags()
{
return $this->morphToMany( Tag::class, 'taggable', 'taggables', 'taggable_id', 'tag_id');
}
// Tag.php
public function posts()
{
return $this->morphedByMany(Post::class, 'taggable', 'taggables', 'tag_id', 'taggable_id');
}
public function videos()
{
return $this->morphedByMany(Video::class, 'taggable', 'taggables', 'tag_id', 'taggable_id');
}
In my project I need to use the videos table id type as string. According to this, being able to store the relation "videos<->tags" the tabbables table's taggable_id must be a string as well, otherwise I can't store the relation.
As long as NO "video<->tag" relation exist it the table everything works fine but as soon as I add even one single "video<->tag" relation the problem starts.
I can use all available methods on the "video<->tags" relation without any problem. $video->tags()->attach([1,2]) or $video->tags()->sync() or $video->tags()->detach() are all working.
But the post<->tag relation's methods have problems. Altough I can attach relations like $post->tags()->attach([1,2]) or call like $post->tags, I can't use $post->tags()->sync() or $post->tags()->detach() methods. When I try, I get this error below:
Illuminate/Database/QueryException with message 'SQLSTATE[22007]: Invalid datetime format:
1292 Truncated incorrect DOUBLE value: 'here_string_id_of_video' (SQL: delete from
`taggables` where `taggable_id` = 300 and `taggable_type` = App/Models/Video)'
This is a uncommon and tricky situation. I know that this is logical because the query expects the serched record type to be ingeter. But I need a way to overcome this problem. Any recommendations and views would be appreciated.
UPDATE
Those are some solutions that could come to mind.
I could use string type for the other tables as well. Which does not sound plausible. This would need a lot of work.
I could process the intermediate table manually with a model.
App\Models\Taggable::where('taggable_id', '300')
->where('taggable_type', 'App\Models\Video')->delete()
I could (as #GauravGupta suggested) create an extra id field (external_id) that is string type and keep the native integer id intact.

"column isn't in GROUP BY" when using "belongsToMany"

I need to check if ManyToMany relationship exists. I have a business that can have many members and members that can have many businesses.
I have pivot table: business_member. I am trying to use the code I found in this post, but I'm getting an error on Laravel.
When I try to run the mysql query from an editor, I get no errors and an empty results.
In my Business model:
public function membersCount()
{
return $this->belongsToMany('App\Member')->selectRaw('count(members.member_id) as aggregate')->groupBy('pivot_business_id');
}
public function getMembersCount()
{
if ( ! array_key_exists('membersCount', $this->relations)) $this->load('membersCount');
$related = $this->getRelation('membersCount')->first();
return ($related) ? $related->aggregate : 0;
}
In my controller:
$business = Business::findOrFail($id);
dd($business->getMembersCount());
I get this error:
SQLSTATE[42000]:
Syntax error or access violation:
1055 'ccf.business_member.member_id' isn't in GROUP BY
(SQL: select count(members.member_id) as aggregate, `business_member`.`business_id` as `pivot_business_id`, `business_member`.`member_id` as `pivot_member_id` from `members` inner join `business_member` on `members`.`member_id` = `business_member`.`member_id` where `business_member`.`business_id` in (2) group by `pivot_business_id`)
It is much easier than that. First you need to define manyToMany relationship in your Business model like this:
/**
* The members that belong to the business.
*/
public function members()
{
return $this->belongsToMany('App\Members');
}
If you want to check how many members a given business has you can do it like this for example:
$business = Business::findOrFail($id);
dd($business->members()->count());

How do I return related models based on IDs in two columns in pivot table (two foreign keys)?

I have a pivot table set up with the following columns:
table - contributions
=====================
id - int, pk
user_id - int, fk
resource_id - int, fk
linked_id - int, fk
...
This basically creates a many-to-many relationship between users and resources. Now, the thing is, linked_id is also a foreign key which points to the ID in the resources table. In most cases, linked_id will just be null and won't be a problem. But sometimes, I want a contribution to be linked to a user, a resource, and one other resource.
In my Resource model I have the following code:
public function contributions()
{
return $this->hasMany('Contribution');
}
But this won't return anything if I'm calling this on a Resource which has its ID in the linked_id column. Is there some way to return all the rows/relations when the resource's ID is found in either the resource_id or the linked_id column? (Somehow has a second $foreignKey value).
Hm, options:
1) create a custom query where you retrieve the Resource, join the contributions table where you match resource_id or linked_id on the current id from the Resource object.
something like:
SELECT R.*
FROM resources AS R
INNER JOIN contributions AS C ON (
C.`resource_id` = R.`resource_id`
OR C.`linked_id` = R.`resource_id`
)
WHERE R.`id` = {$Resource->id}
GROUP BY R.`id`
2) create a second relation method, linkedContributions, which matches the linked_id column.
public function linkedContributions()
{
return $this->hasMany('Contribution', 'linked_id');
}
and retrieve the data:
$Resource = Resource::with(['contributions', 'linkedContributions'])->find(1);
3) other idea; you are currently able to link only 1 resource to another resource. If you create an extra table + hasMany 'linkedResources' relation, you will be able to link multiple resource back to the current Resource. (and skip this problem here altogether)
I modified my contributions function inside my Resource model like so:
public function contributions()
{
return DB::table('contributions')
->where('resource_id', '=', $this->id)->orWhere('linked_id', '=', $this->id)
->join('resources', function($join) {
$join
->on('resources.id', '=', 'contributions.resource_id')
->orOn('resources.id', '=', 'contributions.linked_id');
})
->groupBy('contributions.id')
->select(
'contributions.id', 'contributions.description', 'contributions.created_at', 'resources.id AS tileId', 'resources.name AS tileName',
'users.id AS userId', 'users.username')
->get();
}
The linked contributions will now show up in both models without having to create two entries for each link.

Resources