Question
I am learning the MVC pattern and I am unsure how much code should belong in the model layer.
I currently use a data access class with methods like this:
public function checkUsername($connection, $username)
{
try {
$data = [];
$data['Username'] = $username;
$sql = "SELECT Username FROM " . $this->usersTableName . " WHERE Username = :Username";
return $this->executeObject($connection, $sql, $data);
} catch (Exception $e) {
throw $e;
}
}
My models are usually entity classes mapped to database tables.
Should a model contain both:
- the database-mapped properties, and
- methods like the database access code above
Or is it acceptable to separate the database access code into another class that performs the actual database work?
If I separate these responsibilities, does that effectively mean I now have four layers instead of three in MVC?
Short Answer
By the end of this page, you will understand what the Model means in MVC, how it differs from a plain database entity, and why many PHP applications separate domain objects, business logic, and data access into different classes. You will also see why adding repository or service classes does not break MVC—it usually makes the code easier to test, maintain, and extend.
Concept
In MVC, the Model is not just a single class and it is not only a database row. The model represents the data and business rules of the application.
For beginners, this is the key idea:
- View: shows data to the user
- Controller: handles requests and coordinates work
- Model: contains application data, rules, and operations related to that data
A common source of confusion is that many tutorials treat the model as either:
- a class that maps directly to a database table, or
- a class that only runs SQL queries
In real projects, the model layer is often made of multiple parts:
- Entity / domain object: represents data like
User - Repository / data access object: loads and saves data from the database
- Service / business logic class: performs rules such as registration, validation, or permission checks
That still fits MVC. These classes are all part of the model layer because they represent the application's data and logic, not the user interface.
Your checkUsername() example is really about data access. It asks the database whether a username exists. That logic often belongs in a repository or data access class, not directly inside a plain entity object.
Why this matters:
- It keeps entities simple and focused
- It prevents business objects from being tightly coupled to SQL
- It makes testing easier
- It allows you to change the database code without rewriting the rest of the application
So yes, separating database work from the object that represents the data is a common and healthy design choice.
Mental Model
Think of MVC like running a library:
- The View is the front desk display that shows information to visitors.
- The Controller is the librarian taking requests like "find this book".
- The Model is everything about the library's information and rules.
Inside the model layer, you may have different roles:
- An entity is the book card: title, author, ISBN
- A repository is the storage clerk who knows where and how to fetch books
- A service is the policy expert who knows the rules, like who may borrow a rare book
Even though there are multiple roles, they all belong to the library's internal system. That does not mean the library stopped being a library. In the same way, adding classes inside the model layer does not mean MVC has become something else.
Syntax and Examples
In PHP, a clean MVC-style structure often separates the parts of the model layer.
1. Entity
An entity holds the data for a user.
class User
{
public int $id;
public string $username;
public string $email;
public function __construct(int $id, string $username, string $email)
{
$this->id = $id;
$this->username = $username;
$this->email = $email;
}
}
This class represents a user. It does not need to know SQL.
2. Repository
A repository handles database queries.
class UserRepository
{
private PDO ;
{
->pdo = ;
}
{
= ;
= ->pdo->();
->([ => ]);
() ->();
}
}
Step by Step Execution
Consider this example:
$repository = new UserRepository($pdo);
$service = new UserService($repository);
$controller = new UserController($service);
$controller->checkUsernameAction('alice');
Now trace what happens:
Step 1: Create the repository
$repository = new UserRepository($pdo);
- The repository receives a
PDOdatabase connection. - It is now ready to run SQL queries.
Step 2: Create the service
$service = new UserService($repository);
- The service receives the repository.
- It can now ask whether usernames exist.
Step 3: Create the controller
Real World Use Cases
Here are common places where this structure helps.
User registration
- Entity:
User - Repository: find existing users, save new users
- Service: check username uniqueness, hash passwords, enforce rules
E-commerce
- Entity:
Order,Product,Customer - Repository: load orders and products from the database
- Service: calculate totals, apply discounts, validate stock
Blog or CMS
- Entity:
Post,Comment - Repository: fetch posts by slug or author
- Service: publish/unpublish posts, validate content, moderate comments
APIs
- Repository: read and write records
- Service: apply validation and business rules before returning JSON
- Controller: convert request input into service calls and responses
Reporting or data processing
- Repository: fetch raw records
- Service: group, transform, or summarize data
- View/API response: present the final result
In all these cases, putting everything directly into one model class quickly becomes hard to manage.
Real Codebase Usage
In real PHP codebases, developers often treat the model layer as a group of related classes, not a single file.
Common patterns
Guard clauses
Developers reject invalid input early.
public function canRegisterUsername(string $username): bool
{
if (trim($username) === '') {
return false;
}
return !$this->userRepository->usernameExists($username);
}
This avoids deeply nested if statements.
Repositories for persistence
Database logic is often grouped in repository classes.
$user = $userRepository->findById(10);
$userRepository->save($user);
This keeps SQL in one place.
Services for business logic
Common Mistakes
Beginners often confuse model with database table class. Here are common mistakes.
1. Putting SQL directly inside the controller
Broken example:
class UserController
{
public function checkUsernameAction(PDO $pdo, string $username): void
{
$stmt = $pdo->prepare("SELECT 1 FROM users WHERE username = :username");
$stmt->execute(['username' => $username]);
echo $stmt->fetchColumn() ? 'Taken' : 'Available';
}
}
Why this is a problem:
- The controller now knows database details
- It becomes hard to test
- Reusing the logic elsewhere is harder
Better: move database work to a repository.
2. Making an entity responsible for database access
Broken example:
{
;
{
= ->();
->([ => ->username]);
() ->();
}
}
Comparisons
Here is a practical comparison of common model-layer parts.
| Part | Main purpose | Usually contains | Usually should not contain |
|---|---|---|---|
| Entity / Domain Object | Represent application data | Properties, simple domain behavior | Raw SQL, HTTP request handling |
| Repository / DAO | Load and save data | SQL, PDO calls, persistence logic | HTML output, request handling |
| Service | Business rules and workflows | Validation, coordination, use-case logic | Direct view rendering |
| Controller | Handle incoming request | Call services, choose response/view | SQL-heavy logic, domain rules scattered everywhere |
Fat model vs separated model layer
| Approach | Pros |
|---|
Cheat Sheet
Quick reference
- In MVC, the Model is the application's data and business logic.
- The model layer can contain multiple classes.
- A model is not always a single class mapped directly to a table.
Good separation
- Entity: represents data
- Repository / DAO: performs database queries
- Service: applies business rules
- Controller: handles requests and delegates work
Good PHP example
class UserRepository
{
private PDO $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
public function usernameExists(string $username): bool
{
$stmt = $this->pdo->prepare(
"SELECT 1 FROM users WHERE username = :username LIMIT 1"
);
$stmt->execute(['username' => ]);
() ->();
}
}
FAQ
What should a model contain in MVC?
A model contains application data and logic related to that data. In practice, this may include entities, repositories, and services.
Is a model the same as a database table?
No. A database table is storage. A model represents application concepts and rules, which may be broader than a single table.
Should SQL go in the model or controller?
It should generally go in the model layer, usually in a repository or data access class, not in the controller.
Is it okay to separate database access into another class?
Yes. This is a common design choice and usually improves maintainability.
Do extra classes mean MVC becomes four layers?
Not necessarily. You still have Model, View, and Controller at the architectural level. You are just organizing the model layer internally.
Should an entity class talk directly to the database?
It can in some patterns, but many projects keep entities separate from persistence logic for cleaner design.
Why is catch and throw the same exception usually unnecessary?
Because it does not change the behavior. It only adds code unless you log, wrap, or add useful context.
What is a repository in PHP MVC?
A repository is a class responsible for fetching and saving data, often using PDO or an ORM.
Mini Project
Description
Build a small username availability checker in PHP that demonstrates a clean MVC-style model structure. The project should show how a controller can ask a service whether a username is available, while the repository handles the database lookup. This mirrors a real feature used in registration forms.
Goal
Create a simple PHP program where username validation and database access are separated into clear model-layer classes.
Requirements
- Create a
UserRepositoryclass that checks whether a username exists. - Create a
UserServiceclass that decides whether a username can be registered. - Create a
UserControllerclass that prints a user-friendly result. - Use
PDOwith a prepared statement. - Reject empty usernames before querying the database.
Keep learning
Related questions
Are PDO Prepared Statements Enough to Prevent SQL Injection in PHP?
Learn how PDO prepared statements prevent SQL injection in PHP, what they protect, and the mistakes that still leave MySQL apps vulnerable.
Can You Bind an Array to an IN Clause in PHP PDO?
Learn how PDO handles placeholders in IN() clauses, why arrays cannot be bound directly, and the safe PHP pattern to build dynamic queries.
Choosing the Right MySQL Collation for PHP and UTF-8
Learn how MySQL character sets and collations work with PHP, and how to choose a practical UTF-8 setup for web applications.