Blog CMS Architecture
Build a content management system from scratch using PHP and MySQL. Implement MVC (Model-View-Controller) architecture, database CRUD operations, user authentication, and a rich admin panel.
40 min•By Priygop Team•Last updated: Feb 2026
MVC Structure
- Router — Parse URL to determine controller and action: /posts/edit/5 → PostController::edit(5)
- Models — Post, User, Category classes. Handle database queries with PDO prepared statements
- Views — PHP templates with HTML. Pass data from controllers. Use layouts for header/footer reuse
- Controllers — PostController, UserController, AdminController. Receive requests, call models, render views
- Database — PDO with prepared statements. Migration scripts to create tables. Seed data for development
- Authentication — Session-based login. Password hashing with password_hash() and password_verify()
Blog CMS Code
Example
<?php
// Database connection with PDO
class Database {
private static ?PDO $instance = null;
public static function connect(): PDO {
if (self::$instance === null) {
self::$instance = new PDO(
'mysql:host=localhost;dbname=blog_cms;charset=utf8mb4',
'root', '', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
}
return self::$instance;
}
}
// Post Model
class Post {
public static function findAll(int $limit = 10, int $offset = 0): array {
$db = Database::connect();
$stmt = $db->prepare(
'SELECT p.*, u.name AS author_name
FROM posts p JOIN users u ON p.author_id = u.id
WHERE p.status = "published"
ORDER BY p.created_at DESC LIMIT :limit OFFSET :offset'
);
$stmt->bindValue(':limit', $limit, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
public static function create(array $data): bool {
$db = Database::connect();
$stmt = $db->prepare(
'INSERT INTO posts (title, slug, content, author_id, status)
VALUES (:title, :slug, :content, :author_id, :status)'
);
return $stmt->execute([
':title' => htmlspecialchars($data['title']),
':slug' => self::createSlug($data['title']),
':content' => $data['content'],
':author_id' => $data['author_id'],
':status' => $data['status'] ?? 'draft'
]);
}
private static function createSlug(string $title): string {
return strtolower(preg_replace('/[^a-z0-9]+/i', '-', trim($title)));
}
}
// Authentication
class Auth {
public static function login(string $email, string $password): bool {
$db = Database::connect();
$stmt = $db->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute([':email' => $email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['name'];
$_SESSION['user_role'] = $user['role'];
return true;
}
return false;
}
public static function register(string $name, string $email, string $password): bool {
$hash = password_hash($password, PASSWORD_BCRYPT);
$db = Database::connect();
$stmt = $db->prepare('INSERT INTO users (name, email, password) VALUES (?, ?, ?)');
return $stmt->execute([$name, $email, $hash]);
}
}
?>