One to Many (Polymorphic) can be used to refer to a model that may not necessarily always be the same.
A great example of this would be having blogs and news models, and a comment model that references either the blog or news model.
We will use a similar setup to the example above to set this up, we will not go in depth on the other modules, but you can view the full code used in this example at: vendor/twill/examples/portfolio
On our database not much needs to happen. In our comments migration we have to add a nullableMorphs that we will use to store the relational data.
A morph automatically creates a {morphName}_id and {morphName}_type that Laravel understands.
The _id will hold the model id we are referencing. _type will hold the type of the model we are targeting.
File:
database/migrations/2022_04_06_070334_create_comments_tables.php
1<?php 2 3use Illuminate\Database\Migrations\Migration; 4use Illuminate\Database\Schema\Blueprint; 5use Illuminate\Support\Facades\Schema; 6 7return new class extends Migration 8{ 9 public function up(): void10 {11 Schema::create('comments', function (Blueprint $table) {12 createDefaultTableFields($table);13 $table->string('title', 200)->nullable();14 $table->text('comment')->nullable();15 $table->nullableMorphs('commentable');16 $table->boolean('approved')->default(false);17 });18 }19 20 public function down(): void21 {22 Schema::dropIfExists('comments');23 }24};
Now that our migration is in place we can move onward to our model setup. Here we will follow the Laravel documentation for the model structure.
File:
app/Models/Comment.php
1<?php 2 3namespace App\Models; 4 5use A17\Twill\Models\Model;
6use Illuminate\Database\Eloquent\Relations\MorphTo; 7 8class Comment extends Model 9{10 protected $fillable = [
11 'published',12 'title',13 'comment',14 ];15 16 public function commentable(): MorphTo17 {18 return $this->morphTo();19 }20}
As our database column is named the same as our method, we do not have to specify anything else in the ->morphTo method.
In the example we allow comments on Partners and Projects, so what we need to do now is define the inverse of this relation in each of these models:
File:
app/Models/Partner.php
1<?php 2 3namespace App\Models; 4 5use A17\Twill\Models\Behaviors\HasTranslation;
6use A17\Twill\Models\Behaviors\HasSlug; 7use A17\Twill\Models\Behaviors\HasMedias; 8use A17\Twill\Models\Behaviors\HasRevisions; 9use A17\Twill\Models\Model;10use Illuminate\Database\Eloquent\Relations\MorphMany;11 12class Partner extends Model13{14 use HasTranslation, HasSlug, HasMedias, HasRevisions;15 16 protected $fillable = [
17 'published',18 'title',19 'description',20 ];21 22 public $translatedAttributes = [
23 'title',24 'description',25 ];26 27 public $slugAttributes = [
28 'title',29 ];30 31 public function comments(): MorphMany32 {33 return $this->morphMany(Comment::class, 'commentable');34 }35}
File:
app/Models/Project.php
1<?php 2 3namespace App\Models; 4 5use A17\Twill\Models\Behaviors\HasBlocks;
6use A17\Twill\Models\Behaviors\HasTranslation; 7use A17\Twill\Models\Behaviors\HasSlug; 8use A17\Twill\Models\Behaviors\HasMedias; 9use A17\Twill\Models\Behaviors\HasRevisions;10use A17\Twill\Models\Model;11use Illuminate\Database\Eloquent\Relations\BelongsToMany;12use Illuminate\Database\Eloquent\Relations\HasMany;13use Illuminate\Database\Eloquent\Relations\MorphMany;14 15class Project extends Model16{17 use HasBlocks, HasTranslation, HasSlug, HasMedias, HasRevisions;18 19 protected $fillable = [
20 'published',21 'title',22 'description',23 ];24 25 public $translatedAttributes = [
26 'title',27 'description',28 ];29 30 public $slugAttributes = [
31 'title',32 ];33 34 public function links(): HasMany
35 {36 return $this->hasMany(Link::class)->orderBy('position');37 }38 39 public function partners(): BelongsToMany
40 {41 return $this->belongsToMany(Partner::class)->orderByPivot('position');42 }43 44 public function comments(): MorphMany45 {46 return $this->morphMany(Comment::class, 'commentable');47 }48}
Now that we have our inverse set up we can continue setting up our repository.
We want to be able to add comments from both our Partner and Project module.
First we will create a repeater that we can use to create new comments on any of these modules
File:
resources/views/twill/repeaters/comment.blade.php
1@twillRepeaterTitle('Comment') 2@twillRepeaterTitleField('title', ['hidePrefix' => false]) 3@twillRepeaterTrigger('Add comment') 4@twillRepeaterGroup('app') 5 6<x-twill::input 7 name="title" 8 label="Title" 9/>10 11<x-twill::input12 name="title"13 label="Title"14 :maxlength="100"15/>
Then in both resources/views/twill/partners/form.blade.php and resources/views/twill/projects/form.blade.php we add:
1<x-twill::repeater type="comment"/>
When you now visit either of the forms you should see the new Add comment button.
Next we have to update our repository so that when the form is submitted, the comment is saved properly.
For this we will use the method updateRepeaterMorphMany and getFormFieldsForRepeater.
File:
app/Repositories/PartnerRepository.php
1<?php 2 3namespace App\Repositories; 4 5use A17\Twill\Models\Contracts\TwillModelContract;
6use A17\Twill\Repositories\Behaviors\HandleTranslations; 7use A17\Twill\Repositories\Behaviors\HandleSlugs; 8use A17\Twill\Repositories\Behaviors\HandleMedias; 9use A17\Twill\Repositories\Behaviors\HandleRevisions;10use A17\Twill\Repositories\ModuleRepository;11use App\Models\Partner;12 13class PartnerRepository extends ModuleRepository14{15 use HandleTranslations, HandleSlugs, HandleMedias, HandleRevisions;16 17 public function __construct(Partner $model)
18 {19 $this->model = $model;20 }21 22 public function afterSave(TwillModelContract $model, array $fields): void23 {24 $this->updateRepeaterMorphMany(25 $model,26 $fields,27 'comments',28 'commentable',29 'Comment',30 'comment'31 );32 33 parent::afterSave($model, $fields);34 }35 36 public function getFormFields(TwillModelContract $object): array37 {38 $fields = parent::getFormFields($object);39 40 return $this->getFormFieldsForRepeater(41 $object,42 $fields,43 'comments',44 'Comment',45 'comment'46 );47 }48}
Do the same for app/Repositories/ProjectRepository.php and then everything should be fully functional.
Do not forget to run your migrations if you run into any issues.