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.