Building the Controller Layer: Managing MVC Requests in PHP
Learn to build an MVC Controller in PHP. We'll show you how to centralize request management, coordinate your models, and cleanly pass data to your views.

Previously in this course, we explored Integrating Routing Logic: A Custom PHP Router for Your MVC App to direct incoming traffic to specific files. In this lesson, we stop writing "script soup" and move that logic into a structured MVC Controller.
The MVC Controller First Principles
In a Model-View-Controller (MVC) architecture, the controller acts as the "middleman" or traffic cop. Its primary responsibilities are:
- Handling the Request: Accepting input (via
$_GETor$_POST). - Coordinating Logic: Communicating with your Model to fetch or persist data.
- Preparing the View: Formatting the data and passing it to the display layer.
By centralizing these tasks, you ensure that your routing logic doesn't become cluttered with database queries or HTML rendering.
Structuring Your First Controller
Instead of having a standalone file for every page, we create a class where methods represent specific user actions. Let's build a UserController to manage user profiles.
First, ensure your directory structure follows the plan we laid out in our Project Structure Strategy.
PHP#6A9955">// src/Controllers/UserController.php namespace App\Controllers; use App\Models\User; class UserController { public function showProfile(int $id): void { #6A9955">// 1. Coordinate: Fetch data from the model $userModel = new User(); $userData = $userModel->find($id); #6A9955">// 2. Prepare: "Pass" data to the view #6A9955">// For now, we simulate this by including the view file require_once __DIR__ . '/../Views/user_profile.php'; } }
Passing Data to the View
The controller's job is to ensure the view has exactly what it needs. A common pitfall is passing too much data or "fat" objects that expose internal database structures. Keep it lean by passing associative arrays.
If you are rendering a profile, your controller should look like this:
PHP#6A9955">// In UserController.php public function showProfile(int $id): void { $userModel = new User(); $user = $userModel->find($id); if (!$user) { #6A9955">// Handle missing users gracefully http_response_code(404); echo "User not found."; return; } #6A9955">// We make $user available to the included view file #6A9955">// The view will simply use the variable $user require_once __DIR__ . '/../Views/user_profile.php'; }
Hands-on Exercise: Create a PostController
To move our project forward, perform the following steps:
- Create
src/Controllers/PostController.php. - Define a method
index()that fetches all posts using your existing Post model. - Update your router to instantiate
PostControllerand callindex()when the user visits/posts. - Ensure the
index.phpview file can access the$postsvariable created in the controller.
Common Pitfalls
- Doing Too Much: Never write SQL queries directly inside your controller. If you find yourself writing
SELECT * FROM...inside a controller method, you've broken the MVC separation. Keep that in the Model. - Echoing in Controllers: Avoid
echostatements directly in your controller. Controllers should coordinate data, not output HTML. Therequirestatement is the exception as it hands off control to the View. - Hard-coded Dependencies: Notice we manually instantiated
new User()inside the method. As your application grows, you will eventually want to use Dependency Injection to make your controllers testable.
FAQ: Controller Best Practices
Q: Should I have one huge Controller or many small ones?
A: Many small ones. Group them by resource (e.g., UserController, PostController, CommentController). This keeps files readable and maintainable.
Q: How do I handle form submissions?
A: Use separate methods. showForm() handles the GET request to display the form, and store() handles the POST request to save the data.
Recap
We have successfully shifted from procedural scripts to object-oriented controllers. By grouping related actions into classes, we ensure our code is modular and predictable. Your controller now handles the "what" (business logic orchestration) while the view handles the "how" (presentation).
Up next: Implementing the View Layer, where we formalize how to render our templates cleanly.
Work with me

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs โ the backend engine for your app, mobile client, or SaaS. Built by an API specialist.

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app โ built with FilamentPHP so you can manage everything without touching the database.


