One to Many

One to Many can be used for having child models that are referred to by your main model.

An example could be:

  • A blog having comments
  • An article having source links
  • ...

One to Many is one of the simpler relations to set up so let's get started:

Database setup

As with any relation we need to set up a database. In this example we are using our portfolio example where we will have multiple links on a project.

We will set up 2 models, one is a Project model, you can do this using php artisan twill:module Project

And afterwards a Link model: php artisan twill:module Link, As the Link model is used for our hasMany, we do not have to add it to the routes or navigation files, so you can ignore that suggestion.

In the Link migration we add a column to hold the project_id that we are creating it from.

File:

database/migrations/2022_05_30_074255_create_links_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(): void
10 {
11 Schema::create('links', function (Blueprint $table) {
12 createDefaultTableFields($table);
13 
14 $table->string('title');
15 $table->foreignIdFor(\App\Models\Project::class)->nullable();
16 $table->string('url');
17 
18 $table->integer('position')->unsigned()->nullable();
19 });
20 }
21 
22 public function down(): void
23 {
24 Schema::dropIfExists('links');
25 }
26};

Define the relation

Now with the migration setup we can set up our relation in the Project model:

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 Model
16{
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(): MorphMany...
45 {
46 return $this->morphMany(Comment::class, 'commentable');
47 }
48}

Set up the repeater form

In our project form we can now create an inline repeater.

File:

app/Http/Controllers/Twill/ProjectController.php

1<?php
2 
3namespace App\Http\Controllers\Twill;
4 
5use A17\Twill\Http\Controllers\Admin\ModuleController as BaseModuleController;
6use A17\Twill\Models\Contracts\TwillModelContract;
7use A17\Twill\Services\Forms\Fields\BlockEditor;
8use A17\Twill\Services\Forms\Fields\Input;
9use A17\Twill\Services\Forms\Fields\Repeater;
10use A17\Twill\Services\Forms\Form;
11use A17\Twill\Services\Forms\InlineRepeater;
12use App\Models\Link;
13use App\Models\Partner;
14 
15class ProjectController extends BaseModuleController
16{
17 protected function setUpController(): void...
18 {
19 $this->setModuleName('projects');
20 }
21 
22 public function getForm(TwillModelContract $model): Form
23 {
24 return Form::make([
25 Input::make()
26 ->translatable()
27 ->name('description'),
28 // Inline repeater that can select existing entries.
29 InlineRepeater::make()
30 ->label('Partners')
31 ->name('project_partner')
32 ->triggerText('Add partner') // Can be omitted as it generates this.
33 ->selectTriggerText('Select partner') // Can be omitted as it generates this.
34 ->allowBrowser()
35 ->relation(Partner::class)
36 ->fields([
37 Input::make()
38 ->name('title')
39 ->translatable(),
40 Input::make()
41 ->name('role')
42 ->translatable()
43 ->required(),
44 ]),
45 Repeater::make()->type('comment'), // Regular repeater using a view.//
46 // Regular repeater for creating items without a managed model.
47 InlineRepeater::make()
48 ->name('links')
49 ->fields([
50 Input::make()
51 ->name('title'),
52 Input::make()
53 ->name('url')
54 ]),
55 
56 BlockEditor::make()
57 ]);
58 }
59}

Update the repository

As a final step we have to update the repository to map the repeater field to the relation.

File:

app/Repositories/ProjectRepository.php

1<?php
2 
3namespace App\Repositories;
4 
5use A17\Twill\Repositories\Behaviors\HandleBlocks;...
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\Project;
12 
13class ProjectRepository extends ModuleRepository
14{
15 use HandleBlocks, HandleTranslations, HandleSlugs, HandleMedias, HandleRevisions;
16 
17 public function __construct(Project $model)...
18 {
19 $this->model = $model;
20 }
21 
22 public function afterSave($model, $fields): void
23 {
24 $this->updateRepeaterMorphMany(
25 $model,
26 $fields,
27 'comments',
28 'commentable',
29 'Comment',
30 'comment'
31 );
32 
33 $this->updateRepeater(
34 $model,
35 $fields,
36 'links',
37 );
38 
39 $this->updateRepeaterWithPivot(
40 $model,
41 $fields,
42 'partners',
43 ['role'],
44 'Partner',
45 'project_partner',
46 );
47 parent::afterSave($model, $fields);
48 }
49 
50 public function getFormFields($object): array
51 {
52 $fields = parent::getFormFields($object);
53 
54 $fields = $this->getFormFieldsForRepeater(
55 $object,
56 $fields,
57 'comments',
58 'Comment',
59 'comment'
60 );
61 
62 $fields = $this->getFormFieldsForRepeater(
63 $object,
64 $fields,
65 'links',
66 'Link',
67 'links'
68 );
69 
70 return $this->getFormFieldForRepeaterWithPivot(
71 $object,
72 $fields,
73 'partners',
74 ['role'],
75 'Partner',
76 'project_partner'
77 );
78 }
79}