How do I do Polymorphic Relations between addons
Created 7 years ago by endder

So I have a Tags addon which creates relations in many other addons like: releases addon, videos addon, documents addon, images addon, etc.

I used the multiple field type (anomaly.field_type.multiple) in my migrations for my releases, videos, documents, images, addons and it all work great on the frontend.

{#This work prefect#}
{% for release in releases  %}
        <li> {{ release.title }}</li>
            <ul>
                {% for tag in release.tags %}
                <li>{{ tag.name }}</li>
                {% endfor %}
            </ul>
        {% endfor %}

but I want to be able to retrieve the reverse relate via tags. example below:

{#This doesn't work#}
{% for tag in tags  %}
            <ul>
                {% for article in tag.articles %}
                    <li>{{ article.title }}</li>
                {% endfor %}
            </ul>
            <ul>
                {% for image in tag.images %}
                    <li>{{ image.title }}</li>
                {% endfor %}
            </ul>
            <ul>
                {% for video in tag.videos %}
                    <li>{{ video.title }}</li>
                {% endfor %}
            </ul>
        {% endfor %}

I did try adding a morphedByMany method to my TagModel class but that didn't seem to work. example below:

class TagModel extends TagsTagsEntryModel implements TagInterface
{

    public function releases()
    {
        return $this->morphedByMany('Anomaly\ReleasesModule\Release\ReleaseModel', 'default_releases_releases_tags', 'entry_id', 'related_id');
    }
}

Thanks in advance

piterden  —  7 years ago Best Answer

Look,

You have create the migrations, then when you runs Streams Platform generates models depending on your migrations. If you set up in your migration of releases (for example) and set to it field tags, which is anomaly.field_type.multiple, then you should see that tags() method is reveal itself inside generated model

storage/streams/{app_reference}/models/{slug}{namespace}sEntryModel.php

But there's no method releases inside TagsEntryModel. If you want to add such, you have to create this method manually, but not in the model, generating by the Streams. You'd better not edit that files anyway) But you'd create method inside your model which extends automatic generated.

So what we have at the finish:

  1. Field config in fields section of module migration here we sets just a listing of all fields you going to use in all streams of exactly this addon, together

        //.....
        'tags' => [
            "type"   => "anomaly.field_type.multiple", // never use mixed quotes, just in case)
            "config" => [
                'related' => TagModel::class,
                'mode' => 'lookup', // you should try this...
            ]
        ],
        //.....

    also we need to set up each stream migration with assignments and stream config: columns in tables depends exactly on the assignments array, but this is not straight depending, don't forget it

    protected $stream = [
        'slug'         => 'releases',
        'title_column' => 'name',
        'translatable' => true,
        'searchable'   => true,
        'trashable'    => true,
        'sortable'     => true,
    ];
    
    protected $assignments = [
        'name'      => [
            'required' => true,
            'unique'   => true,
        ],
        'tags', // actually you need only name here, but you may set config too, if you use same field for different streams
    ];

    then, after installing module you need to open Anomaly/TagsModule/Tags/TagsModel and add to it:

    public function releases()
    {
        return $this->belongsToMany(ReleaseModel::class, 'default_pivot_table_name', 'enty_id', 'related_id'); // not 100% sure, but like this - look at docs of Laravel
    }
piterden  —  7 years ago

Could you show your migration config?

endder  —  7 years ago

@piterden find below my tags migrations and releases migrations

Tags

<?php

use Anomaly\Streams\Platform\Database\Migration\Migration;

class AnomalyModuleTagsCreateTagsFields extends Migration
{

    /**
     * The addon fields.
     *
     * @var array
     */
    protected $fields = [
        'name' => [
            'type' => 'anomaly.field_type.text'
        ],
        'slug' => [
            'type' => 'anomaly.field_type.slug',
            'config' => [
                'type' => '-',
                'slugify' => 'name'
            ]
        ]
    ];

}

Releases

<?php

use Anomaly\Streams\Platform\Database\Migration\Migration;
use Anomaly\TagsModule\Tag\TagModel;

class AnomalyModuleReleasesCreateReleasesFields extends Migration
{

    /**
     * The addon fields.
     *
     * @var array
     */
    protected $fields = [
        'title' => 'anomaly.field_type.text',
        'slug' => [
            'type' => 'anomaly.field_type.slug',
            'config' => [
                'type' => '-',
                'slugify' => 'title'
            ]
        ],
        'excerpt' => 'anomaly.field_type.textarea',
        'content' => 'anomaly.field_type.wysiwyg',
        'state' => [
            'type' => 'anomaly.field_type.boolean',
            'config' => [
                "mode" => "switch",
                "on_text"       => "Live",
                "off_text"      => "Halt",
            ]
        ],
        'published_at' => [
            'type' => 'anomaly.field_type.datetime'
        ],
        'tags' => [
            "type"   => "anomaly.field_type.multiple",
            "config" => [
                'related' => TagModel::class
            ]
        ]
    ];

}
piterden  —  7 years ago Best Answer

Look,

You have create the migrations, then when you runs Streams Platform generates models depending on your migrations. If you set up in your migration of releases (for example) and set to it field tags, which is anomaly.field_type.multiple, then you should see that tags() method is reveal itself inside generated model

storage/streams/{app_reference}/models/{slug}{namespace}sEntryModel.php

But there's no method releases inside TagsEntryModel. If you want to add such, you have to create this method manually, but not in the model, generating by the Streams. You'd better not edit that files anyway) But you'd create method inside your model which extends automatic generated.

So what we have at the finish:

  1. Field config in fields section of module migration here we sets just a listing of all fields you going to use in all streams of exactly this addon, together

        //.....
        'tags' => [
            "type"   => "anomaly.field_type.multiple", // never use mixed quotes, just in case)
            "config" => [
                'related' => TagModel::class,
                'mode' => 'lookup', // you should try this...
            ]
        ],
        //.....

    also we need to set up each stream migration with assignments and stream config: columns in tables depends exactly on the assignments array, but this is not straight depending, don't forget it

    protected $stream = [
        'slug'         => 'releases',
        'title_column' => 'name',
        'translatable' => true,
        'searchable'   => true,
        'trashable'    => true,
        'sortable'     => true,
    ];
    
    protected $assignments = [
        'name'      => [
            'required' => true,
            'unique'   => true,
        ],
        'tags', // actually you need only name here, but you may set config too, if you use same field for different streams
    ];

    then, after installing module you need to open Anomaly/TagsModule/Tags/TagsModel and add to it:

    public function releases()
    {
        return $this->belongsToMany(ReleaseModel::class, 'default_pivot_table_name', 'enty_id', 'related_id'); // not 100% sure, but like this - look at docs of Laravel
    }
piterden  —  7 years ago

PS Please, try to remember, that if anybody ask you to show migrations, he will expect FULL contains of migrations folder or folders. Because, only one first file from there don't contains any useful information))) Sorry about right times in my post)

endder  —  7 years ago

@piterden the relations is now work! 😄. Thanks for your feedback. But the only way I could get them to work was to add public function releases to TagsTagsEntryModel class (which i know you said was a big no no), but it seems on the frontend when I foreach my tags, they are an instance of TagsTagsEntryModel and not 'TagModel`. Is this a bug or am I retrieving them wrong?

how i am retrieving my tags:

 {% set tags = entries('tags').get() %}
    <ul>
        {% for tag in tags  %}
            {{ dd(tag) }}  
{#  the dd function dumps a class of EntryPresenter which contains an object of TagsTagsEntryModel not (TagsModel)#}
    </ul>

Sorry for all the questions

piterden  —  7 years ago
/* @var TagsTagsEntryModel $tags */
$tags->getBoundModelNamespace(); // returns full namespace of TagModel which implements TagInterface

/* @var TagInterface $tag */
$tags->getEntry(); // returns TagsTagsEntryModel instance
piterden  —  7 years ago

You need to add getter functions, also. Like there:

class ProductTypeModel extends CatalogProductTypesEntryModel implements ProductTypeInterface {
    protected $products; // If table do not have products column!!!
     /**
     * Get related products.
     *
     * @return ProductCollection
     */
    public function getProducts()
    {
        return $this->products;
    }

    /**
     * Return the products relationship.
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function products()
    {
        return $this->hasMany(ProductModel::class, 'type_id');
    }
}
class ProductModel extends CatalogProductsEntryModel implements ProductInterface {
    /**
     * Get the type.
     *
     * @return null|ProductTypeInterface
     */
    public function getType()
    {
        return $this->type;
    }
}
piterden  —  7 years ago

Look, I'm not Ryan, and can't solve all of your problems.Did you read a documentation and comments in the source code?

ryanthompson  —  7 years ago

@endder one quirk of generated models is that it's a good idea to bind the generated model to the model in your addon service provider.

protected $bindings = [
    TagsTagsEntryModel::class => TagModel::class,
];

This way when models are resolved YOUR model is used. Then you can put the relation methods there instead of on the generated models which should be considered hands-off.

Hope this helps!

PS @piterden outstanding advice 😊

piterden  —  7 years ago

@ryanthompson Sorry, I've been too much overclocked:/

ryanthompson  —  7 years ago

@piterden I meant good job!

piterden  —  7 years ago

Oh, sorry, I've accidentally learned part of English with Trump's glossary))