Polymorphic relationshoip in Laravel - php

I'm trying to understand polymorphic relationship in Laravel. I know how it works in principle, but the choice of wording in Laravel is not intuitive in this part. Given the exanple,
namespace App;
use Illuminate\Database\Eloquent\Model;
class Like extends Model
{
/**
* Get all of the owning likeable models.
*/
public function likeable()
{
return $this->morphTo();
}
}
class Post extends Model
{
/**
* Get all of the product's likes.
*/
public function likes()
{
return $this->morphMany('App\Like', 'likeable');
}
}
class Comment extends Model
{
/**
* Get all of the comment's likes.
*/
public function likes()
{
return $this->morphMany('App\Like', 'likeable');
}
}
How do yo put in plain English sentence morphTo for instance? It is "belongsto"? and morphmany, hasMany? going further,
$post = App\Post::find(1);
foreach ($post->likes as $like) {
//
}
$likeable = $like->likeable;
morphToMany and morphByMany
How do you describe in plain english?

A polymorphic relationship means an object can have a relationship to more than one type of object. This is determined by two fields in the database rather the typical one foreign key field you would normally see.
Using the code you included in your question any type of object extending the Model class can have a relationship with a Like object. So you could have Comments and Posts that can have Likes associated to them. In your likes table you may have rows where 'likable_type' = 'post' and 'likable_id' = 1 or 'likable_type' = 'comment' and 'likable_id' = 4 for example.

Related

Laravel Multi BelongsTo RelationShip Merge with Eager Loading

Laravel version:7.0
reviews table (Model - Review) has id, product_type, product_id, rating columns.
product_type can be service, plugin, module and each value has own model App\Service, App\Plugin, App\Module. I could put model names directly in product_type but I prefer to use those values.
Here is Review model relationship.
public function plugin()
{
return $this->belongsTo(Plugin::class, "product_id")->withDefault();
}
public function module()
{
return $this->belongsTo(Module::class, "product_id")->withDefault();
}
public function service()
{
return $this->belongsTo(Service::class, "product_id")->withDefault();
}
public function getItem()
{
if($this->product_type=='module')
{
return $this->module;
}elseif($this->product_type=='service')
{
return $this->service;
}else {
return $this->plugin;
}
}
Now I want to get them with eager loading in Review Model as following:
$reviews = Review::with("getItem")->get();
Without Eager loading, I could use $review->getItem()->name // this returns name of product.
How can I get them with eager loading?
You could have implemented this easily as a polymorphic relationship. In your Reviews Model, you could do this:
Model Structure
App\Review.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Review extends Model
{
public function reviewable()
{
return $this->morphTo();
}
}
Then add reviews() method to your App\Service, App\Plugin and App\Module models
public function reviews()
{
return $this->morphMany('App\Review', 'reviewable');
}
Table Structure
You reviews table could look like this:
reviews
id - integer
body - text
reviewable_id - integer
reviewable_type - string
Note the reviewable_id and reviewable_type fields. The reviewable_id stores the id of the item reviewed and the reviewable_type stores the model related to the item.
Retrieving The Relationship
You may access the relationships via your models. For example, to access all of the reviews for a service, we can use the reviews dynamic property:
$service = App\Service::find(1);
foreach ($service->reviews as $review) {
//
}
You may also retrieve the owner of a polymorphic relation from the polymorphic model by accessing the name of the method that performs the call to morphTo. In your case, that is the reviewable method on the Review model. So, we will access that method as a dynamic property:
$review = App\Review::find(1);
$reviewable = $review->reviewable;
The reviewable will return the model on the Review model either Service, Plugin or Module

Laravel polymorphic relationship 0 results

I have a polymorphic relationship with Laravel.
My polymorphic relationship is Message->messageable becoming either Group or Chat.
Going from Message->messageable will give me the proper result(e.g the group or chat the message is associated with).
Going from example Group::first()->with('messages')->get() will return an empty messages array.
My db tables for message are
messageable_id
messageable_type
My model methods are as follows
class Message extends Model
{
public function messageable(){
return $this->morphTo();
}
^works as intended
class Group extends Model
{
function messages(){
return $this->morphMany('Message','messageable');
}
class Chat extends Model
{
public function messages(){
return $this->morphMany('Message','messageable');
These return empty.
example data used:
1
Any ideas?
You relationships are incorrect. You need to reference the full class namespace, not just the class name itself. E.g.
class Group extends Model
{
public function messages()
{
return $this->morphMany(\App\Models\Message::class', 'messageable');
}
}
Forgot to include namespaces in my Database messageable_type.
Changed from Group to App\Group.

Laravel 5.3 Polymorphic many to many - how to get all the related rows regardless of their table sorted by pivot column?

I have a model Page and many models called SomethingSection - they're connected through a polymorphic m-m realtionship and the pivot has an additional column 'position'.
I need to write a relationship (or accessor maybe?) on the Page model that will return a collection of all connected Sections, regardless of their model (read: table).
My models:
class Page extends Model {
public function introSections()
{
return $this->morphedByMany(IntroSection::class, 'pagable');
}
public function anotherSections()
{
return $this->morphedByMany(AnotherSection::class, 'pagable');
}
}
class IntroSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable');
}
}
class AnotherSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable');
}
}
The pivot column looks like this:
pagables
-page_id
-pagable_id
-pagable_type
-position
I'm looking for a way to call a method/attribute on the Page model and get all the connected sections in a single collection, sorted too. What would be a good way to go about this?
I understand that the connected sections do not have the same interface, but in my case that's not a problem at all (in terms of what I will do with the data).
I also understand that relationships perform a separate query (for each relationship), so getting all of them with 1 query is impossible (also different interfaces would be a problem here). And for the same reason the sorting will need to be done on the collection level, not in query.
How could I make this as maintainable as possible and preferably with as small a performance hit as possible.
Thanks in advance.
You can use withPivot() method after your relationship to get the pivot columns with relation like this:
class Page extends Model {
public function introSections()
{
return $this->morphedByMany(\HIT\Models\Sections\IntroSection::class, 'pagable')
->withPivot(['position']);
}
public function anotherSections()
{
return $this->morphedByMany(AnotherSection::class, 'pagable');
}
}
class IntroSection extends Model {
public function pages()
{
return $this->morphToMany(Page::class, 'pagable')
->withPivot(['position']);
}
}
and you can use collection's sortBy to sort the collection by using sortBy() method like this:
$sorted_collection = IntroSection::pages->sortBy('pagables.position');
UPDATE:
You can use collection's combine() method to get all the relationships like this, add this method inside your Page Class:
public function getAllSections()
{
return $this->introSections->combine($this->anotherSections-toArray())
->sortBy('pagables.position'):
}
Hope this helps!

How to return one property of a belongsTo model with the child model in Laravel resource controller

I'm using Laravel as a REST API for a SPA. I have a relationship where families have multiple contributions. The contributions table has a foreign key reference to family's id. I can call on the contributions route with the hasMany/belongsTo set up, and every contribution gets the entire family model it belongs to. But I don't need all that data, I just need a single field from the family table (not the id, but a different field) with each contribution.
Here are my models and resource controller:
class Family extends Eloquent {
protected $table = 'families';
// relationships
public function contributions() {
return $this->hasMany('Contribution');
}
}
class Contribution extends Eloquent {
protected $table = 'contributions';
// relationships
public function family() {
return $this->belongsTo('Family');
}
public function other_field() {
return $this->belongsTo('Family')->select('other_field');
}
}
class ContributionController extends BaseController {
public function index()
{
// works - but returns the entire family with every contribution
$contributions = Contribution::with('family')->get();
// returns other_field == null with every contribution
$contributions = Contribution::with('other_field')->get();
return Response::json($contributions->toArray(),
200);
}
Where am I going wrong with selecting this single field from the belongsTo relationship?
You can use query constraints on the relationship if you use eager loading.
Family::with(['contributions', function($query)
{
$query->select('column');
}])->get();

Laravel attach() method not working to hasMany side

The application has the models:
Atividade.php
class Atividade extends Eloquent {
public function intervencoes() {
return $this->belongsToMany('Intervencao');
}
}
Intervencao.php
class Intervencao extends Eloquent {
public function atividades() {
return $this->hasMany('Atividade');
}
}
The following code works:
Atividade::find($id)->intervencoes()->attach($intervencao_id);
But, this...
Intervencao::find($id)->atividades()->attach($atividade_id);
Returns an BadMethodCallException:
Call to undefined method Illuminate\Database\Query\Builder::attach()
SOLUTION (thanks to #gnack):
I was trying to set a many-to-many relationship, so just needed to change this...
return $this->hasMany('Atividade');
To this:
return $this->belongsToMany('Atividade');
See the Laravel documentation here:
http://laravel.com/docs/eloquent#inserting-related-models
Basically you have set up two different types of relationships for the same two tables - you've set up a many-to-many and a one-to-many. It looks as though you probably wanted a many-to-many, so you'll need to change this line:
return $this->hasMany('Atividade');
To this:
return $this->belongsToMany('Atividade');
This will set the relationship up as a many-to-many relationship, which will then support the attach() method.
The attach() method is only for many-to-many, for other relationships there's save() or saveMany() and associate() (see the docs linked above).
See the documentation Laravel 5.7
A Comment belongTo an unique Post
class Comment extends Model
{
/**
* Get the post that owns the comment.
*/
public function post()
{
return $this->belongsTo('App\Post');
}
}
A Post can Have multiple Comments
class Post extends Model
{
/**
* Get the comments for the blog post.
*/
public function comments()
{
return $this->hasMany('App\Comment');
}
When you want to update/delete a belongsTo relationship, you may use the associate/dissociate method.
$post= App\Post::find(10);
$comment= App\Comment::find(3);
$comment->post()->associate($post); //update the model
$comment->save(); //you have to call save() method
//delete operation
$comment->post()->dissociate();
$comment->save(); //save() method
attach() is for many-to-many relationships. It seems your relationship is supposed to be a many-to-many but you have not set it up correctly for that.
class Intervencao extends Eloquent {
public function atividades() {
return $this->belongsToMany('Atividade');
}
}
Then the attach() should work
In my case I've two roles() methods that's why it's throwing this error.

Categories