This blog post is a personal recap of everything I learned while building a custom PHP MVC framework using Composer packages.
1️⃣ MVC Request Flow (End-to-End)
This is the complete lifecycle of a request in our custom MVC setup:
- Request comes in
- A user hits a URL in the browser.
- Router handles the request
- Matches the URL and HTTP method.
- Middleware runs
- Authentication, authorization, or validation checks.
- DI Container resolves dependencies
- Auto-wires controllers and services.
- Controller is executed
- Handles request and response logic.
- Service layer does the heavy work
- Business logic and rules.
- Model interacts with database
- Using Eloquent ORM.
- View renders the response
- Twig generates HTML.
- Final response is sent
- Browser displays the result.
👉 This flow shows how everything connects in a clean way.
2️⃣ How Dependency Injection Works Inside MVC
- Router decides which controller to call.
- Controller is created by the DI container, not manually.
- The container:
- Resolves dependencies
- Injects required services automatically
- Controllers receive services via constructor injection.
Example idea:
Controller → Service → Model
👉 This keeps controllers light and easy to maintain.
3️⃣ Router, Middleware & Group Middleware
Router
- Responsible for mapping URLs to controllers.
- Matches:
- HTTP method (GET, POST, etc.)
- URL path
Middleware
- Code that runs before or after a controller.
- Common uses:
- Authentication
- Authorization
- Request validation
- Logging
Group Middleware
- Middleware applied to a group of routes.
- Example:
- All
/admin/*routes require login.
- All
👉 Middleware helps separate concerns and avoid repeated logic.
4️⃣ Dependency Injection (DI) – Core Concept
- Dependency Injection means:
- Classes don’t create their own dependencies.
- Dependencies are injected from outside.
- Benefits:
- Loose coupling
- Easy testing
- Cleaner code
DI Container
- A container knows:
- How to create objects
- What dependencies each class needs
- Automatically resolves and injects them.
👉 DI is the backbone of scalable architecture.
5️⃣ PHP Packages We Tried (Using Composer)
We explored different packages to understand their roles.
🔹 Eloquent ORM
- Handles database interaction.
- Allows writing queries using PHP instead of SQL.
- Makes models clean and readable.
🔹 Twig Template Engine
- Used for rendering views.
- Keeps PHP logic out of HTML.
- Makes templates cleaner and safer.
🔹 FastRoute
- Lightweight and fast routing library.
- Maps URLs to controllers and methods.
🔹 PHP-DI
- Dependency Injection container.
- Handles auto-wiring and object creation.
👉 Trying each package separately helped understand their purpose clearly.
6️⃣ Custom MVC Structure (Big Picture)
- MVC stands for:
- Model → Business logic & database
- View → UI / HTML
- Controller → Handles requests
- Instead of a full framework, we built a custom MVC.
- It included:
- Router
- Middleware
- DI Container
- Controllers
- Services
- Views
- Composer managed external dependencies.
👉 Building it manually shows how frameworks work internally.
7️⃣ PHPStan – Static Code Analysis Tool
- PHPStan analyzes PHP code without executing it.
- Finds:
- Undefined variables
- Invalid method calls
- Type mismatches
- Logic issues
- Run before production to catch bugs early.
- Improves overall code quality.
👉 PHPStan is like a strict reviewer that protects you from future bugs.
✅ Final Takeaway
- Understanding request flow makes debugging easier.
- Dependency Injection keeps code flexible.
- Middleware separates responsibilities.
- Services make controllers thin.
- Composer packages save time and effort.
- Static analysis (PHPStan) improves confidence.
- Building everything yourself teaches real framework internals.
