Laravel Tutorial #6: Create Models and Setup Admin Panel

Please note that this post may contain affiliate links, and for every purchase you make, at no extra cost to you, a commission fee will be rewarded to me.

You can download the source code of this tutorial here.

Design Database Structure

For a simple blogging system, we need at least 4 database tables: UsersCategoriesTags, and Posts. If you want other functions for your blog, comments, for example, you can add other tables yourself. To keep this tutorial short and easy to understand, these four are all we need.

Users Table

idintegerauto increment
namestringcannot be empty
emailstringunique, cannot be empty
passwordstring

The users table is already included in Laravel and we don’t need to do anything about it.

Categories Table

namestringcannot be empty
slugstringunique, cannot be empty
descriptiontextcan be empty

Tags Table

namestringcannot be empty
slugstringunique, cannot be empty
descriptiontextcan be empty

Posts Table

titlestringcannot be empty
slugstringunique, cannot be empty
featured imagestring or textcan be empty
contenttextcannot be empty
publishedboolean
featuredboolean

Relations

Here, I’d like to introduce some basic relationships. I only picked the ones we need to use to build a simple blogging system since some relationships, like polymorphic relationships, might be too hard to understand for beginners.

One to One

This is the most basic relation. For example, each User is associated with one Phone. To define this relationship, we need to place a phone method on the User model.

class User extends Model
{
    public function phone()
    {
        return $this->hasOne('App\Phone');
    }
}

One to One (Inverse)

The inverse of “has one” would be “belongs to one”. For example, each Phone would belong to one User. In order to define the inverse of the one to one relationship. We place a user method on the Phone model.

class Phone extends Model
{
    public function user()
    {
        return $this->belongsTo('App\User');
    }
}

One to Many

A one-to-many relationship is used to define relationships where a single model owns any amount of other models. For example, one Category could have many Posts. Just like one-to-one relation, it can be defined by putting a posts method in the Category model.

class Category extends Model
{
    public function posts()
    {
        return $this->hasMany('App\Post');
    }
}

One to Many (Inverse)

However, sometimes we need to find the category through the post. The inverse of “has many” would be “belongs to”. In order to define the inverse of the one-to-many relationship. We place a category method on the Post model.

class Post extends Model
{
    public function category()
    {
        return $this->belongsTo('App\Category');
    }
}

Many to Many

Many-to-many relations are slightly more complicated than hasOne and hasMany relationships. An example of such a relationship is that one post has many tags, where each tag is also shared by other posts. We’ll talk about this later.

Design Relationships

For our blog website project. There are six relationships we need to take care of.

  • Each user has multiple posts
  • Each category has many posts
  • Each tag belongs to many posts
  • Each post belongs to one user
  • Each post belongs to one category
  • Each post belongs to many tags

Create Models

Now, it’s time for us to implement that design. The first thing we need to do is to generate the necessary models and migration files for our project with artisan commands.

php artisan make:model Category -m

php artisan make:model Tag -m

php artisan make:model Post -m

Category Model

database/migrations/create_categories_table.php

    public function up()
    {
        Schema::create('categories', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

Line 6, unique() means each record in the column slug is unique.

Line 7, nullable() means the record in the column can be empty.

Line 8, timestamps() creates two columns that store the time when the record is created and when it is updated.

app/Models/Category.php

class Category extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'description',
        'slug',
    ];
}

Tag Model

database/migrations/create_tags_table.php

    public function up()
    {
        Schema::create('tags', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

app/Models/Tag.php

class Tag extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'name',
        'description',
        'slug',
    ];
}

Post Model

database/migrations/create_posts_table.php

    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->bigInteger('category_id');
            $table->bigInteger('user_id');
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('content');
            $table->string('featured_image')->nullable();
            $table->boolean('is_featured')->default(false);
            $table->boolean('is_published')->default(false);
            $table->timestamps();
        });
    }

app/Models/Post.php

class Post extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'category_id',
        'user_id',
        "title",
        'content',
        'slug',
        'featured_image',
        'is_featured',
        'is_published'
    ];
}

Relationship

Between User and Post (One to Many)

If you followed the previous section, we already added a user_id column in the posts table.

database/migrations/create_posts_table.php

$table->bigInteger('user_id');

This column will store the id of the user that this post belongs to. Now we can define the relationships in the models. Remember the new independent directory for the models!

app/Models/User.php

/**
 * Get the posts for the user.
 */
public function posts()
{
    return $this->hasMany('App\Models\Post');
}

app/Models/Post.php

/**
 * Get the user that owns the post.
 */
public function user()
{
    return $this->belongsTo('App\Models\User');
}

Between Category and Post (One to Many)

Again, we need to have a category_id column in the posts table, which stores the id of the category that has this post.

database/migrations/create_posts_table.php

$table->bigInteger('category_id');

Define the relationships in the models.

app/Models/Category.php

/**
 * Get the posts for the user.
 */
public function posts()
{
    return $this->hasMany('App\Models\Post');
}

app/Models/Post.php:

/**
 * Get the category that owns the post.
 */
public function category()
{
    return $this->belongsTo('App\Models\Category');
}

Between Tag and Post (Many to Many)

This one is a bit more complicated, it requires a Many To Many Relationship and an extra database table post_tag. This table is called a pivot table.

First, create a new migration file:

php artisan make:migration create_post_tag_table

database/migrations/create_post_tag_table.php

Schema::create('post_tag', function (Blueprint $table) {
    $table->bigInteger('tag_id');
    $table->bigInteger('post_id');
});

Now we can define the relationships between tags and posts.

app/Models/Tag.php

public function posts()
{
    return $this->belongsToMany('App\Models\Post');
}

app/Models/Post.php

public function tags()
{
    return $this->belongsToMany('App\Models\Tag');
}

Here Laravel assumes there is a post_tag table and there are two columns post_id and tag_id in it. The name of the table needs to be in alphabetical order. If you named them differently, tag_post, for example, you need to specify them like this.

public function tags()
{
    return $this->belongsToMany('App\Models\Tag', 'tag_post');
}

Remember to apply the migration files using php artisan migrate.

Setup Admin Panel

Laravel Nova

If you are using Laravel Nova, please follow this part.

Generate resources with artisan commands:

php artisan nova:resource Post

php artisan nova:resource Category

php artisan nova:resource Tag

app/Nova/Post.php

<?php

namespace App\Nova;

use Illuminate\Http\Request;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\Text;
use Laravel\Nova\Fields\Trix;
use Laravel\Nova\Fields\Slug;
use Laravel\Nova\Fields\Image;
use Laravel\Nova\Fields\Boolean;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\BelongsToMany;
use Laravel\Nova\Http\Requests\NovaRequest;

class Post extends Resource
{
    /**
     * The model the resource corresponds to.
     *
     * @var string
     */
    public static $model = \App\Models\Post::class;

    /**
     * The single value that should be used to represent the resource when being displayed.
     *
     * @var string
     */
    public static $title = 'title';

    /**
     * The columns that should be searched.
     *
     * @var array
     */
    public static $search = [
        'id', 'title', 'content'
    ];

    /**
     * Get the fields displayed by the resource.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function fields(Request $request)
    {
        return [
            ID::make(__('ID'), 'id')->sortable(),
            Text::make('Title'),
            Slug::make('Slug')->from('Title'),
            Trix::make('Content'),
            Image::make('Featured Image')->path('featured_image'),
            Boolean::make('Is Published'),
            Boolean::make('Is Featured'),

            BelongsTo::make('User'),
            BelongsTo::make('Category'),
            BelongsToMany::make('Tags'),
        ];
    }

    /**
     * Get the cards available for the request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function cards(Request $request)
    {
        return [];
    }

    /**
     * Get the filters available for the resource.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function filters(Request $request)
    {
        return [];
    }

    /**
     * Get the lenses available for the resource.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function lenses(Request $request)
    {
        return [];
    }

    /**
     * Get the actions available for the resource.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function actions(Request $request)
    {
        return [];
    }
}

Line 6 – 13, first import all the necessary packages.

Line 23, remember to change the path to the corresponding model.

Line 52, the from() method will automatically turn the title field into a slug. If you wish to use a different separator, then attach a separator() method like this: Slug::make('Slug')->from('Title')->separator('_'),.

Line 54, the path() method will create a new folder, featured_image, for all the featured images uploaded by the user.

Line 58 – 60, we also need to define the relationships here. The BelongsTo and BelongsToMany fields each corresponds to the belongsTo and belongsToMany Eloquent relationship. Here is a video tutorial on this topic: https://laracasts.com/series/laravel-nova-mastery/episodes/5

app/Nova/Category.php

    public function fields(Request $request)
    {
        return [
            ID::make(__('ID'), 'id')->sortable(),
            Text::make('Name'),
            Slug::make('Slug')->from('Name'),
            Textarea::make('Description'),

            HasMany::make('Posts'),
        ];
    }

app/Nova/Tag.php

    public function fields(Request $request)
    {
        return [
            ID::make(__('ID'), 'id')->sortable(),
            Text::make('Name'),
            Slug::make('Slug')->from('Name'),
            Textarea::make('Description'),

            BelongsToMany::make('Posts'),
        ];
    }

Voyager

If you are using Voyager, please follow this part.

Unlike Nova, to setup Voyager, we don’t need to write any code. All we need to do is to add BREAD (browse, read, edit, add and delete) for each database table (not including post_tag).

Go to Tools->BREAD:

img

Edit the BREAD for categories

First, we need to make sure Voyager can find the corresponding model. In the “Categories BREAD info” section, find the “Model Name”, and change it to App\Models\Category.

After that, scroll down to the next section, and change the input type for “description” to “Text Area”. This will give you a bigger text box for description. Remember to save the changes before proceeding.

img

We also need to add the relationship in Voyager, as we designed, each category has many posts:

Edit the BREAD for tags

Do the same thing for tags.

img

Add relationships:

Edit the BREAD for posts

img
img

Add relationships:

Edit the BREAD for users

We only need to add one more relationship for users and posts:

Now, you should be able to see the menu items “Categories”, “Tags” and “Posts”.

img
img
img
img

Error When Editing Posts

img

This error is because Voyager already has a Post built-in, and the Post we created is conflicting with it.

To solve this problem, we change the URL Slug for posts into something other than posts.

img

Then go to Menu Builder (This step is optional, if it already works for you, there is no need to change anything):

img

Click on Builder, and edit the menu for Posts

img
img
Tags:

1 thought on “Laravel Tutorial #6: Create Models and Setup Admin Panel”

Leave a Reply

Your email address will not be published. Required fields are marked *