PHP MVC Architecture
Introduction to MVC Architecture
Model-View-Controller (MVC) is a software architectural pattern that separates an application into three interconnected components. This separation promotes organized code, better maintainability, and enables multiple developers to work on different aspects of the application simultaneously. In PHP, MVC has become the foundation for most modern frameworks and large-scale applications.
Understanding MVC architecture is crucial for building scalable, maintainable web applications that follow established design principles and industry best practices.
Why MVC Matters
Separation of Concerns: MVC clearly separates business logic (Model), presentation (View), and user interaction (Controller) into distinct components.
Code Reusability: Models can be reused across different views and controllers, while views can display data from different models.
Maintainability: Changes to one component typically don't affect others, making maintenance and updates easier.
Testability: Each component can be tested independently, enabling comprehensive unit testing and better code quality.
Team Development: Different developers can work on models, views, and controllers simultaneously without conflicts.
Scalability: MVC architecture supports application growth and complexity management through organized code structure.
MVC Components
Model: Represents data and business logic, handling database operations, data validation, and business rules.
View: Handles the presentation layer, displaying data to users and collecting user input.
Controller: Acts as an intermediary between Model and View, processing user requests and coordinating responses.
Router: Determines which controller and action to execute based on the incoming request URL.
Front Controller: Single entry point that handles all requests and delegates to appropriate controllers.
Basic MVC Implementation
Simple MVC Framework
<?php
/**
* Basic MVC Framework Implementation
*
* Demonstrates fundamental MVC concepts with a simple
* but functional framework structure.
*/
/**
* Application entry point and front controller
*/
class Application
{
private Router $router;
private array $config;
private Container $container;
public function __construct(array $config = [])
{
$this->config = $config;
$this->container = new Container();
$this->router = new Router();
$this->registerServices();
$this->setupRoutes();
}
/**
* Register application services
*/
private function registerServices(): void
{
// Database connection
$this->container->register('database', function() {
$dsn = "mysql:host={$this->config['db_host']};dbname={$this->config['db_name']}";
return new PDO($dsn, $this->config['db_user'], $this->config['db_pass'], [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
});
// Template engine
$this->container->register('view', function() {
return new ViewEngine('views/', 'cache/');
});
// Session manager
$this->container->register('session', function() {
return new SessionManager();
});
}
/**
* Setup application routes
*/
private function setupRoutes(): void
{
// Home routes
$this->router->get('/', 'HomeController@index');
$this->router->get('/about', 'HomeController@about');
// User routes
$this->router->get('/users', 'UserController@index');
$this->router->get('/users/{id}', 'UserController@show');
$this->router->post('/users', 'UserController@store');
$this->router->put('/users/{id}', 'UserController@update');
$this->router->delete('/users/{id}', 'UserController@destroy');
// Authentication routes
$this->router->get('/login', 'AuthController@showLogin');
$this->router->post('/login', 'AuthController@login');
$this->router->post('/logout', 'AuthController@logout');
}
/**
* Run the application
*/
public function run(): void
{
try {
$request = new Request();
$response = $this->router->dispatch($request, $this->container);
$response->send();
} catch (Exception $e) {
$this->handleError($e);
}
}
/**
* Handle application errors
*/
private function handleError(Exception $e): void
{
$errorController = new ErrorController($this->container);
$response = $errorController->handle($e);
$response->send();
}
}
/**
* Simple dependency injection container
*/
class Container
{
private array $services = [];
private array $instances = [];
/**
* Register a service
*/
public function register(string $name, callable $factory): void
{
$this->services[$name] = $factory;
}
/**
* Get service instance
*/
public function get(string $name)
{
if (!isset($this->instances[$name])) {
if (!isset($this->services[$name])) {
throw new InvalidArgumentException("Service '$name' not found");
}
$this->instances[$name] = call_user_func($this->services[$name]);
}
return $this->instances[$name];
}
/**
* Check if service exists
*/
public function has(string $name): bool
{
return isset($this->services[$name]);
}
}
/**
* HTTP Request handler
*/
class Request
{
private string $method;
private string $uri;
private array $params;
private array $body;
public function __construct()
{
$this->method = $_SERVER['REQUEST_METHOD'];
$this->uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$this->params = $_GET;
// Parse request body
$input = file_get_contents('php://input');
$this->body = json_decode($input, true) ?? $_POST;
}
public function getMethod(): string
{
return $this->method;
}
public function getUri(): string
{
return $this->uri;
}
public function getParams(): array
{
return $this->params;
}
public function getBody(): array
{
return $this->body;
}
public function get(string $key, $default = null)
{
return $this->params[$key] ?? $default;
}
public function post(string $key, $default = null)
{
return $this->body[$key] ?? $default;
}
}
/**
* HTTP Response handler
*/
class Response
{
private string $content = '';
private int $statusCode = 200;
private array $headers = [];
public function __construct(string $content = '', int $statusCode = 200, array $headers = [])
{
$this->content = $content;
$this->statusCode = $statusCode;
$this->headers = $headers;
}
public function setContent(string $content): self
{
$this->content = $content;
return $this;
}
public function setStatusCode(int $statusCode): self
{
$this->statusCode = $statusCode;
return $this;
}
public function setHeader(string $name, string $value): self
{
$this->headers[$name] = $value;
return $this;
}
public function json(array $data): self
{
$this->content = json_encode($data);
$this->headers['Content-Type'] = 'application/json';
return $this;
}
public function redirect(string $url): self
{
$this->statusCode = 302;
$this->headers['Location'] = $url;
return $this;
}
public function send(): void
{
http_response_code($this->statusCode);
foreach ($this->headers as $name => $value) {
header("$name: $value");
}
echo $this->content;
}
}
/**
* URL Router
*/
class Router
{
private array $routes = [];
/**
* Add route for different HTTP methods
*/
public function get(string $pattern, string $handler): void
{
$this->addRoute('GET', $pattern, $handler);
}
public function post(string $pattern, string $handler): void
{
$this->addRoute('POST', $pattern, $handler);
}
public function put(string $pattern, string $handler): void
{
$this->addRoute('PUT', $pattern, $handler);
}
public function delete(string $pattern, string $handler): void
{
$this->addRoute('DELETE', $pattern, $handler);
}
/**
* Add route to collection
*/
private function addRoute(string $method, string $pattern, string $handler): void
{
$this->routes[] = [
'method' => $method,
'pattern' => $pattern,
'handler' => $handler,
'regex' => $this->convertPatternToRegex($pattern),
'params' => $this->extractParamNames($pattern)
];
}
/**
* Convert URL pattern to regex
*/
private function convertPatternToRegex(string $pattern): string
{
$pattern = preg_quote($pattern, '/');
$pattern = preg_replace('/\\\{([^}]+)\\\}/', '(?P<$1>[^/]+)', $pattern);
return '/^' . $pattern . '$/';
}
/**
* Extract parameter names from pattern
*/
private function extractParamNames(string $pattern): array
{
preg_match_all('/\{([^}]+)\}/', $pattern, $matches);
return $matches[1];
}
/**
* Dispatch request to controller
*/
public function dispatch(Request $request, Container $container): Response
{
$method = $request->getMethod();
$uri = $request->getUri();
foreach ($this->routes as $route) {
if ($route['method'] === $method && preg_match($route['regex'], $uri, $matches)) {
// Extract route parameters
$params = [];
foreach ($route['params'] as $param) {
$params[$param] = $matches[$param] ?? null;
}
// Parse controller and action
[$controllerName, $action] = explode('@', $route['handler']);
// Create controller instance
$controller = $this->createController($controllerName, $container);
// Call controller action
return $controller->$action($request, $params);
}
}
throw new RuntimeException('Route not found', 404);
}
/**
* Create controller instance
*/
private function createController(string $controllerName, Container $container): BaseController
{
if (!class_exists($controllerName)) {
throw new RuntimeException("Controller '$controllerName' not found");
}
return new $controllerName($container);
}
}
/**
* Base controller class
*/
abstract class BaseController
{
protected Container $container;
protected ViewEngine $view;
protected PDO $database;
protected SessionManager $session;
public function __construct(Container $container)
{
$this->container = $container;
$this->view = $container->get('view');
$this->database = $container->get('database');
$this->session = $container->get('session');
}
/**
* Render view with data
*/
protected function render(string $template, array $data = []): Response
{
$content = $this->view->render($template, $data);
return new Response($content);
}
/**
* Return JSON response
*/
protected function json(array $data, int $statusCode = 200): Response
{
return (new Response())->json($data)->setStatusCode($statusCode);
}
/**
* Redirect to URL
*/
protected function redirect(string $url): Response
{
return (new Response())->redirect($url);
}
/**
* Get authenticated user
*/
protected function getUser(): ?array
{
return $this->session->get('user');
}
/**
* Check if user is authenticated
*/
protected function isAuthenticated(): bool
{
return $this->getUser() !== null;
}
}
/**
* Simple view engine
*/
class ViewEngine
{
private string $viewsDir;
private string $cacheDir;
private array $globals = [];
public function __construct(string $viewsDir, string $cacheDir)
{
$this->viewsDir = rtrim($viewsDir, '/') . '/';
$this->cacheDir = rtrim($cacheDir, '/') . '/';
if (!is_dir($this->cacheDir)) {
mkdir($this->cacheDir, 0755, true);
}
}
/**
* Render template
*/
public function render(string $template, array $data = []): string
{
$templateFile = $this->viewsDir . $template . '.php';
if (!file_exists($templateFile)) {
throw new RuntimeException("Template '$template' not found");
}
$allData = array_merge($this->globals, $data);
extract($allData, EXTR_SKIP);
ob_start();
include $templateFile;
return ob_get_clean();
}
/**
* Add global variable
*/
public function addGlobal(string $name, $value): void
{
$this->globals[$name] = $value;
}
/**
* Escape HTML
*/
public function escape($value): string
{
return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}
}
/**
* Session manager
*/
class SessionManager
{
public function __construct()
{
if (session_status() === PHP_SESSION_NONE) {
session_start();
}
}
public function get(string $key, $default = null)
{
return $_SESSION[$key] ?? $default;
}
public function set(string $key, $value): void
{
$_SESSION[$key] = $value;
}
public function has(string $key): bool
{
return isset($_SESSION[$key]);
}
public function remove(string $key): void
{
unset($_SESSION[$key]);
}
public function clear(): void
{
session_destroy();
}
public function flash(string $key, $value = null)
{
if ($value === null) {
$data = $this->get("flash_$key");
$this->remove("flash_$key");
return $data;
}
$this->set("flash_$key", $value);
}
}
// Example configuration
$config = [
'db_host' => 'localhost',
'db_name' => 'mvc_app',
'db_user' => 'root',
'db_pass' => ''
];
// Create and run application
$app = new Application($config);
$app->run();
?>
Model Implementation
Data Models and Business Logic
<?php
/**
* Model Layer Implementation
*
* Demonstrates data models, database operations,
* and business logic encapsulation.
*/
/**
* Base model class with common functionality
*/
abstract class BaseModel
{
protected PDO $database;
protected string $table;
protected string $primaryKey = 'id';
protected array $fillable = [];
protected array $guarded = ['id', 'created_at', 'updated_at'];
public function __construct(PDO $database)
{
$this->database = $database;
}
/**
* Find record by ID
*/
public function find(int $id): ?array
{
$sql = "SELECT * FROM {$this->table} WHERE {$this->primaryKey} = :id";
$stmt = $this->database->prepare($sql);
$stmt->execute(['id' => $id]);
$result = $stmt->fetch();
return $result ?: null;
}
/**
* Find all records
*/
public function findAll(array $conditions = [], string $orderBy = null, int $limit = null): array
{
$sql = "SELECT * FROM {$this->table}";
$params = [];
if (!empty($conditions)) {
$where = [];
foreach ($conditions as $column => $value) {
$where[] = "$column = :$column";
$params[$column] = $value;
}
$sql .= ' WHERE ' . implode(' AND ', $where);
}
if ($orderBy) {
$sql .= " ORDER BY $orderBy";
}
if ($limit) {
$sql .= " LIMIT $limit";
}
$stmt = $this->database->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
/**
* Create new record
*/
public function create(array $data): int
{
$data = $this->filterFillable($data);
$data['created_at'] = date('Y-m-d H:i:s');
$data['updated_at'] = date('Y-m-d H:i:s');
$columns = implode(',', array_keys($data));
$placeholders = ':' . implode(', :', array_keys($data));
$sql = "INSERT INTO {$this->table} ($columns) VALUES ($placeholders)";
$stmt = $this->database->prepare($sql);
$stmt->execute($data);
return (int)$this->database->lastInsertId();
}
/**
* Update record
*/
public function update(int $id, array $data): bool
{
$data = $this->filterFillable($data);
$data['updated_at'] = date('Y-m-d H:i:s');
$set = [];
foreach ($data as $column => $value) {
$set[] = "$column = :$column";
}
$data['id'] = $id;
$sql = "UPDATE {$this->table} SET " . implode(', ', $set) . " WHERE {$this->primaryKey} = :id";
$stmt = $this->database->prepare($sql);
return $stmt->execute($data);
}
/**
* Delete record
*/
public function delete(int $id): bool
{
$sql = "DELETE FROM {$this->table} WHERE {$this->primaryKey} = :id";
$stmt = $this->database->prepare($sql);
return $stmt->execute(['id' => $id]);
}
/**
* Count records
*/
public function count(array $conditions = []): int
{
$sql = "SELECT COUNT(*) FROM {$this->table}";
$params = [];
if (!empty($conditions)) {
$where = [];
foreach ($conditions as $column => $value) {
$where[] = "$column = :$column";
$params[$column] = $value;
}
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$stmt = $this->database->prepare($sql);
$stmt->execute($params);
return (int)$stmt->fetchColumn();
}
/**
* Filter data based on fillable fields
*/
private function filterFillable(array $data): array
{
if (empty($this->fillable)) {
// If no fillable defined, exclude guarded fields
return array_diff_key($data, array_flip($this->guarded));
}
return array_intersect_key($data, array_flip($this->fillable));
}
/**
* Execute raw query
*/
protected function query(string $sql, array $params = []): PDOStatement
{
$stmt = $this->database->prepare($sql);
$stmt->execute($params);
return $stmt;
}
}
/**
* User model with specific business logic
*/
class UserModel extends BaseModel
{
protected string $table = 'users';
protected array $fillable = ['name', 'email', 'password', 'role', 'active'];
/**
* Find user by email
*/
public function findByEmail(string $email): ?array
{
$sql = "SELECT * FROM {$this->table} WHERE email = :email";
$stmt = $this->database->prepare($sql);
$stmt->execute(['email' => $email]);
$result = $stmt->fetch();
return $result ?: null;
}
/**
* Create user with password hashing
*/
public function createUser(array $data): int
{
if (isset($data['password'])) {
$data['password'] = $this->hashPassword($data['password']);
}
$data['role'] = $data['role'] ?? 'user';
$data['active'] = $data['active'] ?? true;
return $this->create($data);
}
/**
* Verify user password
*/
public function verifyPassword(string $password, string $hash): bool
{
return password_verify($password, $hash);
}
/**
* Hash password
*/
private function hashPassword(string $password): string
{
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* Get active users
*/
public function getActiveUsers(): array
{
return $this->findAll(['active' => 1], 'name ASC');
}
/**
* Get users by role
*/
public function getUsersByRole(string $role): array
{
return $this->findAll(['role' => $role], 'name ASC');
}
/**
* Update user password
*/
public function updatePassword(int $userId, string $newPassword): bool
{
return $this->update($userId, ['password' => $this->hashPassword($newPassword)]);
}
/**
* Deactivate user
*/
public function deactivateUser(int $userId): bool
{
return $this->update($userId, ['active' => false]);
}
/**
* Get user statistics
*/
public function getStatistics(): array
{
$totalUsers = $this->count();
$activeUsers = $this->count(['active' => 1]);
$adminUsers = $this->count(['role' => 'admin']);
return [
'total' => $totalUsers,
'active' => $activeUsers,
'inactive' => $totalUsers - $activeUsers,
'admins' => $adminUsers
];
}
}
/**
* Post model for blog functionality
*/
class PostModel extends BaseModel
{
protected string $table = 'posts';
protected array $fillable = ['title', 'content', 'slug', 'author_id', 'status', 'published_at'];
/**
* Find post by slug
*/
public function findBySlug(string $slug): ?array
{
$sql = "SELECT p.*, u.name as author_name
FROM {$this->table} p
LEFT JOIN users u ON p.author_id = u.id
WHERE p.slug = :slug";
$stmt = $this->database->prepare($sql);
$stmt->execute(['slug' => $slug]);
$result = $stmt->fetch();
return $result ?: null;
}
/**
* Get published posts
*/
public function getPublishedPosts(int $limit = 10, int $offset = 0): array
{
$sql = "SELECT p.*, u.name as author_name
FROM {$this->table} p
LEFT JOIN users u ON p.author_id = u.id
WHERE p.status = 'published'
ORDER BY p.published_at DESC
LIMIT :limit OFFSET :offset";
$stmt = $this->database->prepare($sql);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
/**
* Get posts by author
*/
public function getPostsByAuthor(int $authorId): array
{
return $this->findAll(['author_id' => $authorId], 'created_at DESC');
}
/**
* Create post with slug generation
*/
public function createPost(array $data): int
{
if (empty($data['slug']) && !empty($data['title'])) {
$data['slug'] = $this->generateSlug($data['title']);
}
if ($data['status'] === 'published' && empty($data['published_at'])) {
$data['published_at'] = date('Y-m-d H:i:s');
}
return $this->create($data);
}
/**
* Generate URL slug from title
*/
private function generateSlug(string $title): string
{
$slug = strtolower($title);
$slug = preg_replace('/[^a-z0-9\s-]/', '', $slug);
$slug = preg_replace('/\s+/', '-', $slug);
$slug = trim($slug, '-');
// Ensure uniqueness
$originalSlug = $slug;
$counter = 1;
while ($this->findBySlug($slug)) {
$slug = $originalSlug . '-' . $counter;
$counter++;
}
return $slug;
}
/**
* Publish post
*/
public function publishPost(int $postId): bool
{
return $this->update($postId, [
'status' => 'published',
'published_at' => date('Y-m-d H:i:s')
]);
}
/**
* Get post statistics
*/
public function getStatistics(): array
{
$total = $this->count();
$published = $this->count(['status' => 'published']);
$drafts = $this->count(['status' => 'draft']);
return [
'total' => $total,
'published' => $published,
'drafts' => $drafts
];
}
}
?>
Controller Implementation
Controllers and Business Logic
<?php
/**
* Controller Layer Implementation
*
* Demonstrates controllers handling user requests,
* coordinating with models, and returning responses.
*/
/**
* Home controller
*/
class HomeController extends BaseController
{
/**
* Display homepage
*/
public function index(Request $request, array $params): Response
{
$userModel = new UserModel($this->database);
$postModel = new PostModel($this->database);
$data = [
'title' => 'Welcome to MVC Application',
'user_stats' => $userModel->getStatistics(),
'post_stats' => $postModel->getStatistics(),
'recent_posts' => $postModel->getPublishedPosts(5),
'user' => $this->getUser()
];
return $this->render('home/index', $data);
}
/**
* Display about page
*/
public function about(Request $request, array $params): Response
{
$data = [
'title' => 'About Us',
'user' => $this->getUser()
];
return $this->render('home/about', $data);
}
}
/**
* User controller with CRUD operations
*/
class UserController extends BaseController
{
private UserModel $userModel;
public function __construct(Container $container)
{
parent::__construct($container);
$this->userModel = new UserModel($this->database);
}
/**
* Display all users
*/
public function index(Request $request, array $params): Response
{
if (!$this->isAuthenticated()) {
return $this->redirect('/login');
}
$page = (int)$request->get('page', 1);
$limit = 10;
$offset = ($page - 1) * $limit;
$users = $this->userModel->findAll([], 'name ASC', $limit);
$totalUsers = $this->userModel->count();
$totalPages = ceil($totalUsers / $limit);
$data = [
'title' => 'Users',
'users' => $users,
'current_page' => $page,
'total_pages' => $totalPages,
'user' => $this->getUser()
];
return $this->render('users/index', $data);
}
/**
* Display single user
*/
public function show(Request $request, array $params): Response
{
$userId = (int)$params['id'];
$user = $this->userModel->find($userId);
if (!$user) {
return new Response('User not found', 404);
}
$postModel = new PostModel($this->database);
$userPosts = $postModel->getPostsByAuthor($userId);
$data = [
'title' => 'User: ' . $user['name'],
'profile_user' => $user,
'posts' => $userPosts,
'user' => $this->getUser()
];
return $this->render('users/show', $data);
}
/**
* Create new user
*/
public function store(Request $request, array $params): Response
{
$data = $request->getBody();
// Validation
$errors = $this->validateUserData($data);
if (!empty($errors)) {
return $this->json(['errors' => $errors], 422);
}
// Check if email already exists
if ($this->userModel->findByEmail($data['email'])) {
return $this->json(['errors' => ['email' => 'Email already exists']], 422);
}
try {
$userId = $this->userModel->createUser($data);
$user = $this->userModel->find($userId);
return $this->json([
'message' => 'User created successfully',
'user' => $user
], 201);
} catch (Exception $e) {
return $this->json(['error' => 'Failed to create user'], 500);
}
}
/**
* Update user
*/
public function update(Request $request, array $params): Response
{
$userId = (int)$params['id'];
$data = $request->getBody();
$user = $this->userModel->find($userId);
if (!$user) {
return $this->json(['error' => 'User not found'], 404);
}
// Validation
$errors = $this->validateUserData($data, $userId);
if (!empty($errors)) {
return $this->json(['errors' => $errors], 422);
}
try {
$this->userModel->update($userId, $data);
$updatedUser = $this->userModel->find($userId);
return $this->json([
'message' => 'User updated successfully',
'user' => $updatedUser
]);
} catch (Exception $e) {
return $this->json(['error' => 'Failed to update user'], 500);
}
}
/**
* Delete user
*/
public function destroy(Request $request, array $params): Response
{
$userId = (int)$params['id'];
$user = $this->userModel->find($userId);
if (!$user) {
return $this->json(['error' => 'User not found'], 404);
}
try {
$this->userModel->delete($userId);
return $this->json([
'message' => 'User deleted successfully'
]);
} catch (Exception $e) {
return $this->json(['error' => 'Failed to delete user'], 500);
}
}
/**
* Validate user data
*/
private function validateUserData(array $data, int $userId = null): array
{
$errors = [];
if (empty($data['name'])) {
$errors['name'] = 'Name is required';
}
if (empty($data['email'])) {
$errors['email'] = 'Email is required';
} elseif (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Invalid email format';
} else {
// Check for email uniqueness
$existingUser = $this->userModel->findByEmail($data['email']);
if ($existingUser && $existingUser['id'] != $userId) {
$errors['email'] = 'Email already exists';
}
}
if (isset($data['password']) && strlen($data['password']) < 6) {
$errors['password'] = 'Password must be at least 6 characters';
}
if (isset($data['role']) && !in_array($data['role'], ['user', 'admin'])) {
$errors['role'] = 'Invalid role';
}
return $errors;
}
}
/**
* Authentication controller
*/
class AuthController extends BaseController
{
private UserModel $userModel;
public function __construct(Container $container)
{
parent::__construct($container);
$this->userModel = new UserModel($this->database);
}
/**
* Show login form
*/
public function showLogin(Request $request, array $params): Response
{
if ($this->isAuthenticated()) {
return $this->redirect('/');
}
$data = [
'title' => 'Login',
'error' => $this->session->flash('error')
];
return $this->render('auth/login', $data);
}
/**
* Process login
*/
public function login(Request $request, array $params): Response
{
$email = $request->post('email');
$password = $request->post('password');
if (empty($email) || empty($password)) {
$this->session->flash('error', 'Email and password are required');
return $this->redirect('/login');
}
$user = $this->userModel->findByEmail($email);
if (!$user || !$this->userModel->verifyPassword($password, $user['password'])) {
$this->session->flash('error', 'Invalid email or password');
return $this->redirect('/login');
}
if (!$user['active']) {
$this->session->flash('error', 'Account is deactivated');
return $this->redirect('/login');
}
// Store user in session
unset($user['password']); // Don't store password in session
$this->session->set('user', $user);
return $this->redirect('/');
}
/**
* Logout
*/
public function logout(Request $request, array $params): Response
{
$this->session->clear();
return $this->redirect('/');
}
}
/**
* Error controller
*/
class ErrorController extends BaseController
{
/**
* Handle application errors
*/
public function handle(Exception $e): Response
{
$statusCode = $e->getCode() ?: 500;
$message = $e->getMessage();
$data = [
'title' => 'Error',
'status_code' => $statusCode,
'message' => $message,
'user' => $this->getUser()
];
$content = $this->view->render('errors/error', $data);
return new Response($content, $statusCode);
}
}
?>
<!-- Example view file: views/home/index.php -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= $this->escape($title) ?></title>
<style>
body { font-family: Arial, sans-serif; max-width: 1000px; margin: 0 auto; padding: 20px; }
.header { background: #f4f4f4; padding: 20px; margin-bottom: 20px; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; }
.stat-card { background: #fff; border: 1px solid #ddd; padding: 20px; text-align: center; }
.posts { margin-top: 20px; }
.post { border-bottom: 1px solid #eee; padding: 20px 0; }
.nav { margin-bottom: 20px; }
.nav a { margin-right: 15px; text-decoration: none; color: #007cba; }
</style>
</head>
<body>
<div class="header">
<h1><?= $this->escape($title) ?></h1>
<nav class="nav">
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/users">Users</a>
<?php if ($user): ?>
<span>Welcome, <?= $this->escape($user['name']) ?>!</span>
<a href="/logout" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">Logout</a>
<form id="logout-form" method="POST" action="/logout" style="display: none;"></form>
<?php else: ?>
<a href="/login">Login</a>
<?php endif; ?>
</nav>
</div>
<div class="stats">
<div class="stat-card">
<h3>Total Users</h3>
<div class="stat-number"><?= $user_stats['total'] ?></div>
</div>
<div class="stat-card">
<h3>Active Users</h3>
<div class="stat-number"><?= $user_stats['active'] ?></div>
</div>
<div class="stat-card">
<h3>Total Posts</h3>
<div class="stat-number"><?= $post_stats['total'] ?></div>
</div>
<div class="stat-card">
<h3>Published Posts</h3>
<div class="stat-number"><?= $post_stats['published'] ?></div>
</div>
</div>
<div class="posts">
<h2>Recent Posts</h2>
<?php if (!empty($recent_posts)): ?>
<?php foreach ($recent_posts as $post): ?>
<article class="post">
<h3><?= $this->escape($post['title']) ?></h3>
<p><?= $this->escape(substr($post['content'], 0, 200)) ?>...</p>
<small>By <?= $this->escape($post['author_name']) ?> on <?= date('M j, Y', strtotime($post['published_at'])) ?></small>
</article>
<?php endforeach; ?>
<?php else: ?>
<p>No posts available.</p>
<?php endif; ?>
</div>
</body>
</html>
Related Topics
For more PHP web development topics:
- PHP URL Routing - URL routing systems
- PHP Forms Processing - Form handling
- PHP Database Integration - Database operations
- PHP Object-Oriented Programming - OOP concepts
Summary
MVC architecture provides essential structure for scalable PHP applications:
Separation of Concerns: Clear division between data (Model), presentation (View), and logic (Controller) improves maintainability and enables parallel development.
Model Layer: Encapsulates data access, business logic, and validation, providing a consistent interface for data operations across the application.
View Layer: Handles presentation logic and user interface rendering, keeping display concerns separate from business logic.
Controller Layer: Coordinates between models and views, processing user input and orchestrating application flow.
Dependency Injection: Container-based dependency management enables testable, loosely coupled components.
Routing System: Maps URLs to controllers and actions, providing clean, RESTful interfaces for application endpoints.
Framework Structure: MVC provides the foundation for PHP frameworks and large-scale applications with organized, predictable architecture.
Mastering MVC architecture enables you to build professional, maintainable PHP applications that scale effectively and follow industry best practices.