Final Project: Task Management System
Project Objectives
- Build a complete PHP application with MySQL backend
- Implement full CRUD (Create, Read, Update, Delete) functionality
- Apply database normalization principles
- Use prepared statements for security
- Handle errors gracefully
- Create a user-friendly interface
- Implement user authentication and authorization
Project Overview
You will build a complete Task Management System that allows users to register, log in, and manage their personal tasks. This project combines everything you've learned about PHP and MySQL into a practical, real-world application.
Project Scope
System Requirements
Functional Requirements
graph TD
A[Task Management System] --> B[User Management]
A --> C[Task Operations]
A --> D[Category Management]
A --> E[Dashboard]
B --> B1[Registration]
B --> B2[Login/Logout]
B --> B3[Profile Management]
B --> B4[Password Reset]
C --> C1[Create Tasks]
C --> C2[View Tasks]
C --> C3[Update Tasks]
C --> C4[Delete Tasks]
C --> C5[Mark Complete]
D --> D1[Create Categories]
D --> D2[Assign Categories]
D --> D3[Filter by Category]
E --> E1[Statistics]
E --> E2[Recent Tasks]
E --> E3[Upcoming Deadlines]
style A fill:#0073aa,color:#fff
style B fill:#21759b,color:#fff
style C fill:#00a0d2,color:#fff
Technical Requirements
- PHP Version:7.4 or higher
- MySQL Version:5.7 or higher
- Architecture:MVC pattern (simplified)
- Security:Prepared statements, password hashing, CSRF protection
- Frontend:HTML5, CSS3, JavaScript (optional)
- Responsive:Mobile-friendly design
Database Design
Entity Relationship Diagram
erDiagram
USERS ||--o{ TASKS : creates
USERS ||--o{ CATEGORIES : creates
CATEGORIES ||--o{ TASKS : contains
USERS {
int user_id PK
string username UK
string email UK
string password_hash
datetime created_at
datetime last_login
boolean is_active
}
CATEGORIES {
int category_id PK
int user_id FK
string name
string color
string icon
datetime created_at
}
TASKS {
int task_id PK
int user_id FK
int category_id FK
string title
text description
date due_date
enum priority
enum status
datetime created_at
datetime updated_at
datetime completed_at
}
Database Schema
-- Create database
CREATE DATABASE IF NOT EXISTS task_manager
CHARACTER SET utf8mb4
COLLATE utf8mb4_general_ci;
USE task_manager;
-- Users table
CREATE TABLE users (
user_id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_login TIMESTAMP NULL,
is_active BOOLEAN DEFAULT TRUE,
reset_token VARCHAR(100) NULL,
reset_expires TIMESTAMP NULL,
INDEX idx_username (username),
INDEX idx_email (email)
) ENGINE=InnoDB;
-- Categories table
CREATE TABLE categories (
category_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
name VARCHAR(50) NOT NULL,
color VARCHAR(7) DEFAULT '#0073aa',
icon VARCHAR(50) DEFAULT 'folder',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
UNIQUE KEY unique_user_category (user_id, name),
INDEX idx_user_categories (user_id)
) ENGINE=InnoDB;
-- Tasks table
CREATE TABLE tasks (
task_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
category_id INT,
title VARCHAR(255) NOT NULL,
description TEXT,
due_date DATE,
priority ENUM('low', 'medium', 'high', 'urgent') DEFAULT 'medium',
status ENUM('pending', 'in_progress', 'completed', 'cancelled') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
completed_at TIMESTAMP NULL,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE SET NULL,
INDEX idx_user_tasks (user_id),
INDEX idx_status (status),
INDEX idx_due_date (due_date),
INDEX idx_priority (priority)
) ENGINE=InnoDB;
-- Session table for remember me functionality
CREATE TABLE user_sessions (
session_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
session_token VARCHAR(255) NOT NULL UNIQUE,
ip_address VARCHAR(45),
user_agent VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
expires_at TIMESTAMP NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
INDEX idx_session_token (session_token),
INDEX idx_expires (expires_at)
) ENGINE=InnoDB;
-- Activity log table
CREATE TABLE activity_logs (
log_id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
action VARCHAR(50) NOT NULL,
entity_type VARCHAR(50),
entity_id INT,
details JSON,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
INDEX idx_user_activity (user_id),
INDEX idx_action (action),
INDEX idx_created (created_at)
) ENGINE=InnoDB;
Project Structure
Directory Structure
task-manager/
├── index.php
├── config/
│ ├── database.php
│ └── config.php
├── includes/
│ ├── functions.php
│ ├── validation.php
│ └── session.php
├── classes/
│ ├── Database.php
│ ├── User.php
│ ├── Task.php
│ └── Category.php
├── public/
│ ├── css/
│ │ └── style.css
│ ├── js/
│ │ └── app.js
│ └── images/
├── templates/
│ ├── header.php
│ ├── footer.php
│ └── nav.php
├── auth/
│ ├── login.php
│ ├── register.php
│ ├── logout.php
│ └── reset-password.php
├── dashboard/
│ └── index.php
├── tasks/
│ ├── index.php
│ ├── create.php
│ ├── edit.php
│ ├── delete.php
│ └── view.php
├── categories/
│ ├── index.php
│ ├── create.php
│ └── delete.php
├── profile/
│ ├── index.php
│ └── edit.php
└── .htaccess
Key Components Visualization
Core Implementation
1. Database Connection Class
<?php
// classes/Database.php
class Database {
private $host = 'localhost';
private $db_name = 'task_manager';
private $username = 'root';
private $password = '';
private $conn;
public function connect() {
$this->conn = null;
try {
$this->conn = new PDO(
"mysql:host=" . $this->host . ";dbname=" . $this->db_name,
$this->username,
$this->password
);
$this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$this->conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
} catch(PDOException $e) {
error_log("Connection Error: " . $e->getMessage());
die("Connection failed. Please try again later.");
}
return $this->conn;
}
}
?>
2. User Authentication Class
<?php
// classes/User.php
class User {
private $conn;
private $table = 'users';
public $user_id;
public $username;
public $email;
public $password;
public $first_name;
public $last_name;
public function __construct($db) {
$this->conn = $db;
}
public function register() {
$query = "INSERT INTO " . $this->table . "
(username, email, password_hash, first_name, last_name)
VALUES (:username, :email, :password, :first_name, :last_name)";
$stmt = $this->conn->prepare($query);
// Hash password
$password_hash = password_hash($this->password, PASSWORD_DEFAULT);
// Bind values
$stmt->bindParam(':username', $this->username);
$stmt->bindParam(':email', $this->email);
$stmt->bindParam(':password', $password_hash);
$stmt->bindParam(':first_name', $this->first_name);
$stmt->bindParam(':last_name', $this->last_name);
if($stmt->execute()) {
$this->user_id = $this->conn->lastInsertId();
return true;
}
return false;
}
public function login() {
$query = "SELECT user_id, username, email, password_hash
FROM " . $this->table . "
WHERE (username = :login OR email = :login)
AND is_active = 1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':login', $this->username);
$stmt->execute();
if($row = $stmt->fetch()) {
if(password_verify($this->password, $row['password_hash'])) {
$this->user_id = $row['user_id'];
$this->email = $row['email'];
// Update last login
$this->updateLastLogin();
return true;
}
}
return false;
}
private function updateLastLogin() {
$query = "UPDATE " . $this->table . "
SET last_login = NOW()
WHERE user_id = :user_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->user_id);
$stmt->execute();
}
}
?>
3. Task Management Class
<?php
// classes/Task.php
class Task {
private $conn;
private $table = 'tasks';
public $task_id;
public $user_id;
public $category_id;
public $title;
public $description;
public $due_date;
public $priority;
public $status;
public function __construct($db) {
$this->conn = $db;
}
public function create() {
$query = "INSERT INTO " . $this->table . "
(user_id, category_id, title, description, due_date, priority)
VALUES (:user_id, :category_id, :title, :description, :due_date, :priority)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $this->user_id);
$stmt->bindParam(':category_id', $this->category_id);
$stmt->bindParam(':title', $this->title);
$stmt->bindParam(':description', $this->description);
$stmt->bindParam(':due_date', $this->due_date);
$stmt->bindParam(':priority', $this->priority);
if($stmt->execute()) {
$this->task_id = $this->conn->lastInsertId();
return true;
}
return false;
}
public function getUserTasks($user_id, $filters = []) {
$query = "SELECT t.*, c.name as category_name, c.color as category_color
FROM " . $this->table . " t
LEFT JOIN categories c ON t.category_id = c.category_id
WHERE t.user_id = :user_id";
// Apply filters
if(!empty($filters['status'])) {
$query .= " AND t.status = :status";
}
if(!empty($filters['category'])) {
$query .= " AND t.category_id = :category";
}
if(!empty($filters['priority'])) {
$query .= " AND t.priority = :priority";
}
$query .= " ORDER BY
CASE WHEN t.status = 'completed' THEN 1 ELSE 0 END,
t.due_date ASC,
t.priority DESC";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':user_id', $user_id);
if(!empty($filters['status'])) {
$stmt->bindParam(':status', $filters['status']);
}
if(!empty($filters['category'])) {
$stmt->bindParam(':category', $filters['category']);
}
if(!empty($filters['priority'])) {
$stmt->bindParam(':priority', $filters['priority']);
}
$stmt->execute();
return $stmt->fetchAll();
}
public function update() {
$query = "UPDATE " . $this->table . "
SET title = :title,
description = :description,
category_id = :category_id,
due_date = :due_date,
priority = :priority,
status = :status
WHERE task_id = :task_id AND user_id = :user_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':title', $this->title);
$stmt->bindParam(':description', $this->description);
$stmt->bindParam(':category_id', $this->category_id);
$stmt->bindParam(':due_date', $this->due_date);
$stmt->bindParam(':priority', $this->priority);
$stmt->bindParam(':status', $this->status);
$stmt->bindParam(':task_id', $this->task_id);
$stmt->bindParam(':user_id', $this->user_id);
return $stmt->execute();
}
public function delete() {
$query = "DELETE FROM " . $this->table . "
WHERE task_id = :task_id AND user_id = :user_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':task_id', $this->task_id);
$stmt->bindParam(':user_id', $this->user_id);
return $stmt->execute();
}
public function markComplete() {
$query = "UPDATE " . $this->table . "
SET status = 'completed',
completed_at = NOW()
WHERE task_id = :task_id AND user_id = :user_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':task_id', $this->task_id);
$stmt->bindParam(':user_id', $this->user_id);
return $stmt->execute();
}
}
?>
User Interface
Dashboard Statistics
Sample Dashboard HTML
<!-- dashboard/index.php -->
<?php
session_start();
require_once '../config/database.php';
require_once '../classes/Database.php';
require_once '../classes/Task.php';
// Check if user is logged in
if(!isset($_SESSION['user_id'])) {
header('Location: ../auth/login.php');
exit();
}
$database = new Database();
$db = $database->connect();
$task = new Task($db);
// Get user tasks
$tasks = $task->getUserTasks($_SESSION['user_id']);
// Calculate statistics
$total_tasks = count($tasks);
$completed = count(array_filter($tasks, function($t) { return $t['status'] == 'completed'; }));
$in_progress = count(array_filter($tasks, function($t) { return $t['status'] == 'in_progress'; }));
$overdue = count(array_filter($tasks, function($t) {
return $t['status'] != 'completed' && $t['due_date'] < date('Y-m-d');
}));
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard - Task Manager</title>
<link rel="stylesheet" href="../public/css/style.css">
</head>
<body>
<?php include '../templates/nav.php'; ?>
<div class="container">
<h1>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!</h1>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value"><?php echo $total_tasks; ?></div>
<div class="stat-label">Total Tasks</div>
</div>
<div class="stat-card success">
<div class="stat-value"><?php echo $completed; ?></div>
<div class="stat-label">Completed</div>
</div>
<div class="stat-card warning">
<div class="stat-value"><?php echo $in_progress; ?></div>
<div class="stat-label">In Progress</div>
</div>
<div class="stat-card danger">
<div class="stat-value"><?php echo $overdue; ?></div>
<div class="stat-label">Overdue</div>
</div>
</div>
<div class="dashboard-grid">
<div class="recent-tasks">
<h2>Recent Tasks</h2>
<ul class="task-list">
<?php
$recent = array_slice($tasks, 0, 5);
foreach($recent as $task):
?>
<li class="task-item">
<span class="task-title">
<?php echo htmlspecialchars($task['title']); ?>
</span>
<span class="task-priority priority-<?php echo $task['priority']; ?>">
<?php echo ucfirst($task['priority']); ?>
</span>
</li>
<?php endforeach; ?>
</ul>
</div>
<div class="upcoming-deadlines">
<h2>Upcoming Deadlines</h2>
<ul class="deadline-list">
<?php
$upcoming = array_filter($tasks, function($t) {
return $t['status'] != 'completed' && $t['due_date'] != null;
});
usort($upcoming, function($a, $b) {
return $a['due_date'] <=> $b['due_date'];
});
$upcoming = array_slice($upcoming, 0, 5);
foreach($upcoming as $task):
?>
<li class="deadline-item">
<span class="task-title">
<?php echo htmlspecialchars($task['title']); ?>
</span>
<span class="task-date">
<?php echo date('M d, Y', strtotime($task['due_date'])); ?>
</span>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
<div class="quick-actions">
<a href="../tasks/create.php" class="btn btn-primary">+ New Task</a>
<a href="../tasks/index.php" class="btn btn-secondary">View All Tasks</a>
<a href="../categories/index.php" class="btn btn-secondary">Manage Categories</a>
</div>
</div>
<?php include '../templates/footer.php'; ?>
</body>
</html>
Security Implementation
Security Measures
Required Security Features
- Password Hashing:Use password_hash() with PASSWORD_DEFAULT
- SQL Injection Prevention:Always use prepared statements
- XSS Protection:htmlspecialchars() for all output
- CSRF Protection:Token validation for forms
- Session Security:Regenerate session ID on login
- Input Validation:Server-side validation for all inputs
- Error Handling:Log errors, don't display to users
CSRF Token Implementation
<?php
// includes/csrf.php
function generateCSRFToken() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function verifyCSRFToken($token) {
if (!isset($_SESSION['csrf_token']) || $token !== $_SESSION['csrf_token']) {
die('CSRF token validation failed');
}
return true;
}
// In forms
?>
<form method="POST" action="process.php">
<input type="hidden" name="csrf_token" value="<?php echo generateCSRFToken(); ?>">
<!-- other form fields -->
</form>
<?php
// In form processing
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
verifyCSRFToken($_POST['csrf_token']);
// Process form
}
?>
Testing Requirements
Test Cases
| Feature | Test Case | Expected Result |
|---|---|---|
| Registration | Register with valid data | Account created, auto-login |
| Registration | Register with existing username | Error message displayed |
| Login | Valid credentials | Redirect to dashboard |
| Login | Invalid credentials | Error message, stay on login |
| Task Creation | Create task with all fields | Task saved, appears in list |
| Task Update | Edit existing task | Changes saved successfully |
| Task Delete | Delete task | Task removed from database |
| Categories | Create category | Category available in dropdown |
| Filtering | Filter by status | Only matching tasks shown |
| Security | Access without login | Redirect to login page |
Project Deliverables
Complete the Task Management System with the following requirements:
Minimum Requirements (70% grade)
- ☐ Database setup with all required tables
- ☐ User registration and login functionality
- ☐ CRUD operations for tasks
- ☐ Basic category management
- ☐ Session management
- ☐ Input validation
- ☐ Prepared statements for all queries
- ☐ Basic CSS styling
Standard Requirements (85% grade)
- ☐ All minimum requirements
- ☐ Dashboard with statistics
- ☐ Task filtering and sorting
- ☐ Priority levels
- ☐ Due date tracking
- ☐ Password reset functionality
- ☐ CSRF protection
- ☐ Responsive design
Advanced Requirements (100% grade)
- ☐ All standard requirements
- ☐ Activity logging
- ☐ Remember me functionality
- ☐ Task search
- ☐ Export tasks to CSV/PDF
- ☐ Email notifications for deadlines
- ☐ Ajax for dynamic updates
- ☐ Dark mode toggle
- ☐ Unit tests for critical functions
Submission Requirements
- Source Code:Complete project folder in ZIP format
- Database:SQL export file with sample data
-
Documentation:
- README.md with installation instructions
- Database schema diagram
- User manual (PDF)
- Demo Video:5-minute screen recording showing all features
- Test Report:Document showing test cases and results
Sample Project Structure
Here's how a professional would organize this project:
# Project Organization
/task-manager
│
├── /src # Source code
│ ├── /classes # PHP classes
│ ├── /controllers # Request handlers
│ └── /models # Data models
│
├── /public # Publicly accessible files
│ ├── index.php # Entry point
│ ├── /css # Stylesheets
│ ├── /js # JavaScript
│ └── /images # Images
│
├── /templates # View templates
│ ├── /layouts # Page layouts
│ ├── /partials # Reusable components
│ └── /pages # Page templates
│
├── /config # Configuration files
├── /database # Database migrations and seeds
├── /tests # Unit and integration tests
├── /docs # Documentation
│
├── .gitignore # Git ignore file
├── composer.json # PHP dependencies
├── README.md # Project documentation
└── LICENSE # License file
Project Resources
Project Tips