This article is part one of a new series on building MVC (Model View Controller) applications in PHP Laravel. In this first part, I'll start by looking at what MVC means and its concepts. Then, I'll tackle the M in MVC by explaining how PHP Laravel implements the Model in MVC applications.

MVC in a Nutshell

Model-View-Controller (MVC) is both a design pattern and architecture pattern. It's seen as more of an architectural pattern, as it tries to solve these problems in the application and affects the application entirely. Design patterns are limited to solving a specific technical problem.

MVC divides an application into three major logical sections:

  • Model
  • View
  • Controller

The Model component governs and controls the application database(s). It's the only component in MVC that can interact with the database, execute queries, retrieve, update, delete, and create data. Not only that, but it's also responsible for guaranteeing the evolution of the database structure from one stage to another by maintaining a set of database migrations. The Model responds to instructions coming from the Controller to perform certain actions in the database.

The View component generates and renders the user interface (UI) of the application. It's made up of HTML/CSS and possibly JavaScript. It receives the data from the Controller, which has received the data from the Model. It merges the data with the HTML structure to generate the UI.

The Controller component acts as a mediator between the View and Model components. It receives a request from the client for a specific View, it coordinates with the Model component to query for data (or update data), then it decides which View component to return, and finally, it packages the View together with the related data into a single response.

One component often overlooked or perceived as part of the Controller is the Routing Engine. It's the brains behind the MVC pattern and one of the most important components in MVC that initially receives the request from the client and allocates which Controller is going to handle the request.

Figure 1 shows all of the components, together with their relationships, that make up the MVC pattern.

Figure 1: MVC Architecture Diagram
Figure 1: MVC Architecture Diagram

I can explain Figure 1 as follows:

  • The browser (client) requests a page (view).
  • The router engine, living inside the application, receives the request.
  • The router engine runs an algorithm to pick up a single Controller to handle the request.
  • The Controller decides on the View to return and communicates with the Model to retrieve/store any data and sends a response back to the browser.
  • The Model communicates with the database, as needed.
  • The View renders the page (view) requested by the browser.

Now that you know how MVC works, let's see how PHP Laravel implements MVC.

How PHP Laravel Implements MVC

Laravel is a PHP-based Web framework that's fully based on the MVC architecture and much more. The goal is to get started building PHP projects easily using modern tools and techniques.

To understand how PHP Laravel implements MVC, I'll go through a typical Laravel project structure and show you how the Laravel team bakes the MVC concepts into the framework.

Let's get started by creating a new PHP Laravel project locally. To avoid repetition, I'll point you to a recent article I published in CODE Magazine Nov/Dec 2021, where I show you a step-by-step guide on creating a Laravel application. You can follow this article here: Beginner's Guide to Deploying PHP Laravel on the Google Cloud Platform.

The latest official version of Laravel is v9.x.

Model

The first component is the Model or M of the MVC. The Model plays the main role of allowing the application to communicate with the back-end database. Laravel includes Eloquent. Eloquent is an object-relational mapper (ORM) that makes it easy to communicate with the back-end database.

In Laravel, you create a Model class for each and every database table. The Model class allows you to interact with the database table to create, update, query, and delete data in the database.

By default, when you create a new Laravel application, it includes a User model. This model maps to the Users database table. It stores all user records in the application.

By default, Laravel creates the User model class. The User model represents a single user record in the application.

Let's create your first Model class to represent a database table of Posts. You'll create a Laravel Migration as well. (see https://laravel.com/docs/9.x/migrations for the documentation).

A migration file gives you the chance to declaratively decide what columns the database table should have. Laravel uses this migration file to create/update the database table. By default, Laravel comes with a few migration files to create and configure user database tables.

You'll also be creating a Model Factory class. You'll use this to easily create model records in the database. It's helpful when you want to seed some initial data into the database tables. Also, you'll rely heavily on factories when writing unit or feature tests.

Create a Model

Run the following command to create a Model, Migration, and Model Factory for the Posts database table.

sail artisan make:model Post -mf

Or

php artisan make:model Post -mf

Laravel creates three files at this stage:

  • The \app\Models\Post.php Model class
  • An anonymous migration file for the Posts table located under \database\migrations\ directory
  • The \database\factories\PostFactory.php Factory class

The command generates the Post.php model file:

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;
}

Notice how the Post class extends the base class Model. This is how the Post class inherits all methods and properties from the base class and allows your application to interact with the database table posts. Also, the Post class uses the HasFactory trait. This is needed to link Post and PostFactory classes.

Traits in PHP are one way to reuse and share a single chunk of code. You can read more about PHP traits here: https://www.php.net/manual/en/language.oop5.traits.php

You can read more about Laravel Eloquent Model here (https://laravel.com/docs/9.x/eloquent#generating-model-classe).

Configure Model Migration

Let's have a look at the migration file that the command created. Listing 1 shows the entire source code for this migration.

Listing 1: The Post Model migration file

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
    * Run the migrations.
    *
    * @return void
    */
    public function up()
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
        Schema::dropIfExists('posts');
    }
};

Laravel uses the down() method when rolling back a migration(s), and it uses the up() method when running the migration and setting up the database structure.

When you run this migration, Laravel creates a Posts database table with three columns:

  • ID (unsigned big integer)
  • created_at (timestamp)
  • updated_at (timestamp)

Let's add a few columns to the Posts database table. Adjust the up() method to look similar to the one in Listing 2.

Listing 2: Adjusted up() method

public function up()
{
    Schema::create('posts',
    static function (Blueprint $table) {
        $table->id();
        $table->string('slug');
        $table->string('title');
        $table->string('body');
        $table->unsignedBigInteger('user_id');
        $table->date('published_at')->useCurrent();
        $table->timestamps();

        $table->foreign('user_id')
              ->references('id')
              ->on('users');
    });
}

I've added the following columns:

  • Slug: The slug of the blog post. A slug is a user-friendly and URL valid name of a Post. You can read more about it here (What is: Post Slug).
  • Title: The title of the blog post.
  • Body: The blog post content.
  • user_id: The user who created this blog post.
  • published_at: A timestamp column referring to the date the blog post is published on. By default, it gets the date the post was created on.
  • Timestamps: Adds two new columns on the database table named: created_at and updated_at.

Finally, I make the user_id column to be a foreign key referring to the User ID column in the Users table.

You can read more about Eloquent Migrations here (https://laravel.com/docs/9.x/migrations).

Model Relationships

One of the remarkable features of a relational database is to connect database tables together through database relationships. There are a handful of relationships such as one-to-many, many-to-many, and others. Here's a detailed guide explaining all types of relationships in a relational database (https://condor.depaul.edu/gandrus/240IT/accesspages/relationships.htm).

The Eloquent ORM offers you the same experience that any relational database offers. With Eloquent, you can define relationships and link models to each other.

In the Create Model Migration section, you created a migration file to create the Posts database table. That table had the user_id as foreign key. It's this field that creates a one-to-many relationship between the User and Post models. Every user can have one or more Posts.

To define a relationship in Laravel Eloquent, two steps are required:

  • At the Migration level, add all necessary fields that are used to link the database tables together. This is done already in this example.
  • Define a relationship function at the model level. This is the topic of this section.

Locate and open the Post.php file and add the following relationship:

public function user():\Illuminate\Database\Eloquent\Relations\BelongsTo
{
    return $this->belongsTo(User::class);
}

The function user() represents the relationship between the two models. A Post belongsTo a User.

The inverse of this relationship goes inside the User.php file. Let's add the following relationship:

public function posts():\Illuminate\Database\Eloquent\Relations\HasMany
{
    return $this->hasMany(Post::class);
}

A User hasMany Posts.

I'll keep it at this level with regard to defining and exploring relationships in Laravel Eloquent. I advise you to check the official documentation to learn all about Eloquent Relationships (https://laravel.com/docs/9.x/eloquent-relationships#one-to-many).

Mass Assignment

Mass assignment (https://laravel.com/docs/9.x/eloquent#mass-assignment) is a concept in the Laravel framework that allows you to create a new Model object in one statement instead of multiple statements.

Instead of creating a new Post object as follows:

$post             = new Post();
$post->title      = 'My First Post';
$post->body       = 'Body of the blog post ...';
$post->slug       = 'my-first-post';
$post->user_id    = 1;

You can create the same object using mass assignment:

$post = App\Models\Post::create([
    'title'    => 'My First Post',
    'body'     => 'Body of the blog post ...',
    'slug'     => 'my-first-post',
    'user_id'  => 1
]);

To enable mass assignment, you need to use one of the two methods listed here:

  • Specify the allowed columns that can be used inside the create() method. You can define the $fillable property on the Post Model class and include the allowed columns to fill with the mass assignment.
protected $fillable = [
    'title',
    'body',
    'slug',
    'user_id',
    'published_at'
];
  • Specify the columns that are not allowed to be used with the mass assignment. You can define the $guarded property on the Post Model class and include the not-allowed columns to fill with the mass assignment.
protected $guarded = [
    'slug',
    'user_id'
];

I prefer setting the allowed columns to know exactly what columns I'm setting via the mass assignment. I prefer the $fillable property.

You can read more about mass assignment here (https://laravel.com/docs/9.x/eloquent#mass-assignment).

Model Factories

A model factory in Laravel allows you to have fake models. You can extensively use this feature:

  • At the initial phases of the app while you're still building it, without having enough UIs to cover all features and aspects of the app. You use model factories to generate dummy data to use and display on the screens while building them. In Laravel, use database seeders to generate dummy data for all the models in the application. This way, you can populate all screens with dummy data while still developing them. You can read more about database seeders here (https://laravel.com/docs/9.x/seeding).
  • When writing unit and feature tests for your application, you will depend solely on model factories to generate test data.

When you create the Post Model, you append the -f flag into the artisan command to create a model. This flag instructs the command to generate an empty Factory class corresponding to the model being created.

Listing 3 shows the entire source code for the Post Factory class.

Listing 3: PostFactory class

class PostFactory extends Factory
{
    /**
    * Define the model's default state.
    *
    * @return array<string, mixed>
    */
    public function definition()
    {
        return [
            //
        ];
    }
}

The factory class is empty, and you need to fill it with some fake data. To do so, locate the definition() method and construct a valid Post Model. Listing 4 shows the source code for constructing a fake Post Model.

Listing 4: Post model factory

public function definition()
{
    $title = $this->faker->realText(20);
    $title = $this->faker->realText(50);
    return array(
        'title'         => $title,
        'body'          => $this->faker->text(100),
        'slug'          => Str::slug($title),
        'user_id'       => User::factory()->create()->id,
        'published_at'  => Carbon::now(),
    );
}

The Factory class defines the $faker property. Laravel makes use of the FakerPHP Faker package (https://github.com/FakerPHP/Faker).

The definition() method constructs and returns a fake instance of the Post Model using the $faker property to generate things like Post title, body, and others.

In addition, the user_id field is assigned to the ID of a newly created User object.

You can do crazy stuff with Model Factories. Check the full documentation for more information here (https://laravel.com/docs/9.x/database-testing#generating-factories).

Configure App with SQLite

Let's run the migration you have and set up the back-end database. Before running the migration, you need to configure the database connection string.

Laravel can work with multiple database engines including and not limited to MySQL, Microsoft SQL Server, PostgreSQL, and SQLite.

For now, let's use SQLite to get started (https://www.sqlite.org/index.html). You can switch anytime.

To get started, open the . env environment file and remove the following section:

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel_mvc_app
DB_USERNAME=sail
DB_PASSWORD=password

Add the following section instead:

DB_CONNECTION=sqlite
DB_DATABASE=/var/www/html/database/database.sqlite

The reason for this /var/www/html is that you're using Laravel Sail (https://laravel.com/docs/9.x/sail).

Now, inside the /database folder at the root of the application, create a new empty file named database.sqlite.

And that's it!

Eloquent Migrations

Switch to the terminal and run the following command to migrate the database.

sail artisan migrate

Or

php artisan migrate

The command runs all of the migration files and creates the necessary database tables and objects.

In this case, two tables in the database are created: Users and Posts. Figure 2 shows the Posts table structure.

Figure 2: Posts database table structure
Figure 2: Posts database table structure

Now that you've created the tables in the database, you can test creating and retrieving Posts' data using Laravel Tinker (https://laravel.com/docs/9.x/artisan#tinker).

Laravel Tinker

Laravel Tinker is a powerful REPL for the Laravel framework, powered by the PsySH package. This package comes with every Laravel application, so there's no need to install anything additional.

To connect to Laravel Tinker, run the following command:

sail artisan tinker

Or

php artisan tinker

Figure 3 shows the tinker up and running.

Figure 3: Tinker running
Figure 3: Tinker running

Tinker allows you to run Laravel code inside a CLI (command line interface).

Let's create a User and Post model.

Run the following command to create five users:

User::factory()->count(5)->create()

You're using the User Factory (that ships with any new Laravel application) to create five User records using fake data. Figure 4 shows the result of running the command inside Tinker.

Figure 4: Creating User records inside Tinker
Figure 4: Creating User records inside Tinker

The statement runs and displays the results of creating the five User records.

Let's create a new Post record, again using the Post Factory.

Run the following command:

Post::factory()->create()

Figure 5 shows the result of running the command inside Tinker.

Figure 5: Creating a Post record inside Tinker
Figure 5: Creating a Post record inside Tinker

Inside Tinker, you can run any Eloquent statement that you'd usually run inside a Controller, as you'll see soon.

For that, let's try to query for all Post records in the database using the following Eloquent query:

Post::query()
    ->with('user')
    ->where('id', '>', 1)
    ->get()

The query retrieves all Post records with an ID > 1. It also makes use of another Eloquent feature, eager loading, to load the related User record and not only the user_id column.

Figure 6 shows the query results inside Tinker:

Figure 6: Query results inside Tinker
Figure 6: Query results inside Tinker

Notice that not only the user_id is returned but also another property named user that contains the entire user record. You can learn more about the powerful Eloquent eager loading here (https://laravel.com/docs/9.x/eloquent-relationships#eager-loading).

That's all for the Artisan Tinker for now!

Casting Columns

Eloquent has powerful and hidden gems that are baked into the framework. For instance, by default, Eloquent converts the timestamps columns created_at and updated_at to instances of Carbon (https://carbon.nesbot.com/docs/).

Laravel defines a property named $dates on the base Model class that specifies which columns should be handled as dates.

protected $dates = [
    'created_at',
    'updated_at',
    'deleted_at'
];

You can extend this property by adding more columns to your Model.

However, Eloquent offers a more generic way of defining such conversions. It allows you to define the $casts property on your Models and decide on the conversion. For example, here, you're defining a new cast to date:

protected $casts = [
    'published_at' => 'date'
];

This works great! You can read more about casts in Laravel here (https://laravel.com/docs/9.x/eloquent-mutators#attribute-casting).

I tend to enjoy the flexibility and more control that accessors and mutators in Laravel give me. Let's have a look.

Let's define an accessor and mutator, in the Post.php, to store the published_at column in a specific date format and retrieve it in the same or some other date format.

public function publishedAt(): Attribute
{
    return Attribute::make(
        get: static fn ($value) => Carbon::parse($value)?->format('Y-m-d'),
        set: static fn ($value) => Carbon::parse($value)?->format('Y-m-d')
    );
}

This is the new format for writing accessors and mutators in Laravel 9. You define a new function using the camelCase version of the original column name. This function should return an Attribute instance.

An accessor and mutator can define only the accessor, only the mutator, or both. An accessor is defined by the get() function and the mutator is defined by the set() function.

The Attribute::make() function takes two parameters: the get() and set() functions. You can pass one of them, or both of them, depending on the use case.

The Get function is called the accessor (https://laravel.com/docs/9.x/eloquent-mutators#accessors-and-mutators). You use this function to decide what the value of this column will look like when retrieved and accessed.

The Set function is called the mutator (https://laravel.com/docs/9.x/eloquent-mutators#accessors-and-mutators). You can use this function to do your conversion logic before Laravel saves this model.

In this case, you're parsing the published_at field to a Carbon instance and then formatting it as YYYY-MM-DD. Eventually, that's how it will be stored in the database without the Time factor of the Date field. Similarly, when the value is retrieved, it will maintain its format. In this case, the get() accessor is redundant. I use it when I want to display the field in a different format than the one stored in the database. For the sake of this demonstration, I use both to let you know that both accessors and mutators exist in Laravel.

Laravel makes use of Carbon for dates everywhere in the framework source code.

That was a brief overview of the models in Laravel MVC. You can see that the Eloquent ORM is big and powerful.

Conclusion

PHP Laravel not only supports MVC architecture, but it also adds many productivity tools and concepts that make Web development in Laravel a breeze!

This is just the beginning of a detailed series of articles covering Web development with PHP Laravel. Now that you know the M of MVC, next time you will learn about the C and V! Stay tuned to discover more with PHP Laravel.