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.
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. TheUser
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
andupdated_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 PostModel
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 PostModel
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.
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.
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.
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.
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:
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.