How to create a database and data scaffolding of dictionary system

For this dictionary system scaffolding we are going to use of 11 tables and they are mentioned below

1. roles [ Registered users will have role of admin, editor and normal user ]
2. permissions [ set of resources of the system ]
3. permission_role [ which users have what resource permission ]
4. users [ application users ]
5. parts_of_speeches [ a collection of category to which words are assigned ]
6. words [ the actual words ]
7. parts_of_speech_word [ breezing table between parts_of_speeches and words ]
8. synonyms [ collection references of synonym words from words table ]
9. antonyms [ collection references of antonym words from words table ]
10. sentences [ potential sentences that can be made up referencing words ]
11. user_favorites [ references to words that user makes favorite ]

Now for all those tables above we can proceed to making migrations, models, factories and seeders

1. roles table

// migration 
Schema::create('roles', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

// model 
class Role extends Model
{
    use HasFactory;

    protected $fillable = ['name'];

    public function permissions()
    {
        return $this->belongsToMany(Permission::class);
    }
}

// seeder 
class RoleSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        $admin = Role::create(['name' => 'Administrator']);
        $admin->permissions()->attach(Permission::pluck('id'));

        $editor = Role::create(['name' => 'User']);
        $editor->permissions()->attach(
            Permission::where('name', '=', 'dictionary.view')->pluck('id')
        );
    }
}

2. permissions table

// migration
Schema::create('permissions', function (Blueprint $table) {
    $table->id();
    $table->string('name');
    $table->timestamps();
});

// model 
class Permission extends Model
{
    use HasFactory;

    protected $fillable = ['name'];

    public function roles(){
        return $this->belongsToMany(Role::class);
    }
}

//migration for breezing table permission_role since there is a belongsToMany 
// relationship between roles and permissions table.
This would also help to create seeding data on RoleSeeder.php for this breezing table with relationship. Schema::create('permission_role', function (Blueprint $table) { $table->id(); $table->foreignId('permission_id')->constrained(); $table->foreignId('role_id')->constrained(); $table->timestamps(); });

3. parts_of_speeches table

//migration 
Schema::create('parts_of_speech_word', function (Blueprint $table) {
    $table->id();
    $table->unsignedBigInteger('word_id');
    $table->unsignedBigInteger('parts_of_speech_id');

    $table->foreign('word_id')->references('id')->on('words')->onDelete('cascade');
    $table->foreign('parts_of_speech_id')->references('id')->on('parts_of_speeches')->onDelete('cascade');
    $table->timestamps();
});

//model PartsOfSpeech.php
class PartsOfSpeech extends Model
{
    use HasFactory;

    protected $fillable = ['name'];

    public function words()
    {
        return $this->belongsToMany(Word::class);
    }
}

//factory PartsOfSpeechFacrory.php
public function definition(): array
{
    return [
        'name' => fake()->name(),
    ];
}

//seeder PartsOfSpeechSeeder.php
public function run(): void
{
    PartsOfSpeech::factory(5)->create();
}


4. words table

//migration 
Schema::create('words', function (Blueprint $table) {
    $table->id();
    $table->string('word');
    $table->text('definition');
    $table->text('definition_en');
    $table->timestamps();
});

//model Word.php 
class Word extends Model
{
    use HasFactory;

    protected $fillable = ['word', 'definition', 'definition_en'];

    public function parts_of_speeches()
    {
        return $this->belongsToMany(PartsOfSpeech::class);
    }

    public function synonyms(){
        return $this->hasMany(Synonym::class, 'word_id');
    }

    public function antonyms(){
        return $this->hasMany(Antonym::class, 'word_id');
    }

    public function sentences() {
        return $this->hasMany(Sentence::class );
    }
}

//factory WordFactory.php 
public function definition(): array
{
    return [
        'word' => fake()->name(),
        'definition' => fake()->name(),
        'definition_en' => '',
    ];
}

//seeder WordSeeder.php 
public function run(): void
{
    //
    Word::factory(5)->create()->each( function ($word) {
        $word->parts_of_speeches()->attach( PartsOfSpeech::pluck('id')->random(3) );
        for( $i = 0; $i < 2; $i++ ) {
            $word->sentences()->save( Sentence::factory()->create() );
        }
    });
}

5. synonyms table

// migration 
public function up(): void
{
    Schema::create('synonyms', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('word_id');
        $table->unsignedBigInteger('synonym_word_id');

        $table->foreign('word_id')->references('id')->on('words')->onDelete('cascade');
        $table->foreign('synonym_word_id')->references('id')->on('words')->onDelete('cascade');
        $table->timestamps();
    });
}

//model Synonym.php 
class Synonym extends Model
{
    use HasFactory;

    public function real_word(){
        return $this->belongsTo(Word::class, 'word_id');
    }

    public function synonym_word(){
        return $this->belongsTo(Word::class, 'synonym_word_id');
    }
}

//seeder SynosynSeeder.php 
public function run(): void
{
    for( $i=0; $i < 20 ; $i++ ) {
        \App\Models\Synonym::firstOrCreate([
            'word_id' => Word::all()->random()->id,
            'synonym_word_id' => Word::all()->random()->id,
        ]);
    }
}

6. antonyms table

//migration 
public function up(): void
{
    Schema::create('antonyms', function (Blueprint $table) {
        $table->id();
        $table->unsignedBigInteger('word_id');
        $table->unsignedBigInteger('antonym_word_id');

        $table->foreign('word_id')->references('id')->on('words')->onDelete('cascade');
        $table->foreign('antonym_word_id')->references('id')->on('words')->onDelete('cascade');
        $table->timestamps();
    });
}

//model 
class Antonym extends Model
{
    use HasFactory;

    public function real_word(){
        return $this->belongsTo(Word::class, 'word_id');
    }

    public function antonym_word(){
        return $this->belongsTo(Word::class, 'antonym_word_id');
    }
}

//seeder
public function run(): void
{
    for( $i=0; $i < 20 ; $i++ ) {
        \App\Models\Antonym::firstOrCreate([
            'word_id' => Word::all()->random()->id,
            'antonym_word_id' => Word::all()->random()->id,
        ]);
    }
}

7. sentences table

//migration 
public function up(): void
{
    Schema::create('sentences', function (Blueprint $table) {
        $table->id();
        $table->string('sentence');
        $table->unsignedBigInteger('word_id');

        $table->foreign('word_id')->references('id')->on('words')->onDelete('cascade');
        $table->timestamps();
    });
}

//model 
class Sentence extends Model
{
    use HasFactory;

    protected $fillable = [
        'sentence', 'word_id'
    ];

    public function word()
    {
        return $this->belongsTo(Word::class);
    }
}

//seeder
//For seeding data in sentences table we have used WordSeeder.php file to do this using relationship 
//between words and sentences table which is hasMany.

8. user_favorites table

<?php
//migration 
public function up(): void
{
    Schema::create('user_favorites', function (Blueprint $table) {
        $table->id();

        $table->unsignedBigInteger('word_id');
        $table->unsignedBigInteger('user_id');

        $table->foreign('word_id')->references('id')->on('words')->onDelete('cascade');
        $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');

        $table->timestamps();
    });
}
    
//model 
class UserFavorite extends Model
{
    use HasFactory;

    protected $fillable = [
        'word_id',
        'user_id',
    ];

    public function word(){
        return $this->belongsTo(Word::class,);
    }

    public function user(){
        return $this->belongsTo(User::class);
    }

}

//seeder 
//For seeding data to this table we have used UserSeeder.php to do this

9. users table

//migration 
public function up(): void
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->foreignId('role_id')->constrained();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });
}

//model 
class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
    ];

    public function role()
    {
        return $this->belongsTo(Role::class);
    }

    public function favoriteWords(){
        return $this->hasMany( UserFavorite::class );
    }
}

//factory
public function definition(): array
{
    return [
        'name' => fake()->name(),
        'email' => fake()->unique()->safeEmail(),
        'email_verified_at' => now(),
        'role_id' => Role::all()->random()->id,
        'password' => static::$password ??= Hash::make('password'),
        'remember_token' => Str::random(10),
    ];
}

//seeder 
public function run(): void
{
    //
    User::factory(5)->create()->each( function($user) {
        for( $i = 0; $i < 3; $i++ ) {
            $user->favoriteWords()->firstOrCreate(['word_id' => Word::pluck('id')->random() ]);
        }
    });
}

At last we need to setup DatabaseSeeder.php to finally run the seeding

public function run(): void
{
    $this->call(PermissionSeeder::class);
    $this->call(RoleSeeder::class);
    $this->call(PartsOfSpeechSeeder::class);
    $this->call(WordSeeder::class);
    $this->call(SynonymSeeder::class);
    $this->call(AntonymSeeder::class);
    $this->call(UserSeeder::class);

}

Now we will have both database tables and data created with scaffolding if we run laravel artisan command

php artisan migrate:fresh --seed