Building a Custom MVC Framework from Scratch with Core PHP: Architecture and BestPractices

Share this post on:

Understanding MVC Architecture

The Model-View-Controller (MVC) pattern separates application logic into three distinct components:

  • Model: Handles data logic and interactions with the database
  • View: Manages the presentation layer and user interface
  • Controller: Processes user input and coordinates between Model and View

Project Structure

Our framework will follow this directory structure:


project/

  • App
    • Controllers/
    • Models/
    • Views/
    • Config/
  • Core
    • Router.php
    • Controller.php
    • Model.php
    • View.php
    • Database.php
  • Public/
    • index.php
    • css/
    • js/
  • .htaccess

Core Components Implementation

  1. Router Class
    The Router will handle URL routing and dispatch requests to appropriate controllers:
class Router { private $routes = [];
public function addRoute($method, $path, $handler) {
$this->routes[] = [ 'method' => $method, 'path' => $path, 'handler' => $handler
];
}


public function dispatch($requestMethod, $requestUri) { foreach ($this->routes as $route) {
if ($route['method'] === $requestMethod CC $this->matchPath($route['path'],
$requestUri)) {
return call_user_func($route['handler']);
}
}
throw new Exception('Route not found');
}


private function matchPath($routePath, $requestUri) {
$routeRegex = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $routePath);
$routeRegex = "#^" . $routeRegex . "$#";
return preg_match($routeRegex, $requestUri, $matches);
}
}
  1. Base Controller Class
    The Controller class serves as the foundation for all controllers:
abstract class Controller { protected $view; protected $model;

public function  construct() {
$this->view = new View();
}


protected function loadModel($modelName) {
$modelClass = $modelName . 'Model';
$this->model = new $modelClass();
}


protected function response($data, $statusCode = 200) { http_response_code($statusCode);
header('Content-Type: application/json'); echo json_encode($data);
}
}
  1. Database Connection Class
    A robust database connection handler using PDO:
class Database {
private static $instance = null; private $connection;

private function  construct() {
$config = require '../app/Config/database.php';


try {
$this->connection = new PDO(
"mysql:host={$config['host']};dbname={$config['database']};charset=utf8mb4",

$config['username'],
$config['password']
);
$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->connection->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
throw new Exception("Connection failed: " . $e->getMessage());
}
}


public static function getInstance() { if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}


public function getConnection() { return $this->connection;
}
}

Best Practices and Design Patterns

  1. Dependency Injection
    Implement a simple dependency injection container:
class Container { 
private $services = [];

public function register($name, $callback) {
$this->services[$name] = $callback;
}
public function resolve($name) {
if (!isset($this->services[$name])) {
throw new Exception("Service not found: {$name}");
}

$callback = $this->services[$name];
 return $callback($this);
}
}
  1. Middleware Implementation
    Add middleware support for request/response handling:
class Middleware { private $next;

public function setNext(Middleware $middleware) {
$this->next = $middleware; return $middleware;
}


public function handle($request) { if ($this->next) {
return $this->next->handle($request);
}
return $request;
}
}

Security Considerations

  1. XSS Protection
function sanitizeOutput($output) {
return htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
}
  1. CSRF Protection
class CSRFProtection {

public static function generateToken() { if (empty($_SESSION['csrf_token'])) {

$_SESSION['csrf_token'] = bin2hex(random_bytes(32));

}

return $_SESSION['csrf_token'];

}

public static function validateToken($token) {

if (!isset($_SESSION['csrf_token']) || $token !== $_SESSION['csrf_token']) { throw new Exception('CSRF token validation failed');

}

}

}

Usage Example

Here’s how to use the framework in practice:

// routes.php
$router = new Router();


$router->addRoute('GET', '/users', function() {
$controller = new UsersController(); return $controller->index();
});


// UsersController.php
class UsersController extends Controller { public function  construct() {
parent:: construct();
$this->loadModel('User');
}


public function index() {
$users = $this->model->getAllUsers();
return $this->view->render('users/index', ['users' => $users]);
}
}

Conclusion

Building a custom MVC framework provides invaluable insights into web application architecture and design patterns. While this implementation is basic, it serves as a solid foundation that can be extended with additional features like:

  • Caching mechanisms
  • Authentication and authorization
  • Form validation
  • Database migrations
  • Template engine integration
  • API versioning
At 200OK Solutions, we excel in crafting custom MVC frameworks using core PHP tailored to meet your specific business requirements. With our deep expertise in PHP architecture and adherence to best practices, we build scalable, efficient, and high-performance web applications from scratch. Whether you’re starting a new venture or optimizing an existing platform, our experienced development team ensures robust design, clean code, and long-term maintainability.
Partner with 200OK Solutions for reliable and future-ready web solutions. Ready to build your next big idea? Contact us today!