Session 5.6 – CRUD Operations in PHP
Module 5: Database Connectivity and Web Services | Duration: 1 hr
Learning Objectives
By the end of this session, students will be able to:
- Implement Create, Read, Update, and Delete operations
- Build a complete CRUD application with PHP and MySQL
- Apply data validation and error handling in CRUD operations
- Create user-friendly forms for data management
- Implement pagination and search functionality
- Follow best practices for database operations
Introduction to CRUD
CRUD (Create, Read, Update, Delete) represents the four basic operations for persistent storage. These operations form the foundation of most database-driven applications.
CRUD Operations Overview
CREATE
INSERT INTOREAD
SELECT FROMUPDATE
UPDATE SETDELETE
DELETE FROMDatabase Schema
For this session, we'll use a simple users table:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE Operation
Create Form (create.php)
<!DOCTYPE html>
<html>
<head>
<title>Add New User</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2>Add New User</h2>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success">User created successfully!</div>
<?php endif; ?>
<?php if (isset($_GET['error'])): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($_GET['error']); ?></div>
<?php endif; ?>
<form action="store.php" method="POST">
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" name="name" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control" required>
</div>
<div class="mb-3">
<label class="form-label">Phone</label>
<input type="tel" name="phone" class="form-control">
</div>
<button type="submit" class="btn btn-primary">Create User</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</body>
</html>
Store Handler (store.php)
<?php
// store.php
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
// Get form data
$name = trim($_POST['name']);
$email = trim($_POST['email']);
$phone = trim($_POST['phone']);
// Validate input
$errors = [];
if (empty($name)) {
$errors[] = "Name is required";
}
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Valid email is required";
}
if (!empty($errors)) {
$errorMsg = implode(', ', $errors);
header("Location: create.php?error=" . urlencode($errorMsg));
exit;
}
// Connect to database
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Insert data
$sql = "INSERT INTO users (name, email, phone) VALUES (:name, :email, :phone)";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':name' => $name,
':email' => $email,
':phone' => $phone
]);
// Redirect with success message
header("Location: index.php?success=created");
exit;
} catch (PDOException $e) {
// Check for duplicate email
if ($e->getCode() == 23000) {
header("Location: create.php?error=Email already exists");
} else {
header("Location: create.php?error=Database error occurred");
}
exit;
}
} else {
header("Location: create.php");
exit;
}
?>
READ Operation
List All Users (index.php)
<?php
// index.php
require_once 'config.php';
try {
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Pagination
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$perPage = 10;
$offset = ($page - 1) * $perPage;
// Search functionality
$search = isset($_GET['search']) ? trim($_GET['search']) : '';
if (!empty($search)) {
$sql = "SELECT * FROM users WHERE name LIKE :search OR email LIKE :search
ORDER BY id DESC LIMIT :offset, :perPage";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':search', "%$search%", PDO::PARAM_STR);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->bindValue(':perPage', $perPage, PDO::PARAM_INT);
} else {
$sql = "SELECT * FROM users ORDER BY id DESC LIMIT :offset, :perPage";
$stmt = $pdo->prepare($sql);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->bindValue(':perPage', $perPage, PDO::PARAM_INT);
}
$stmt->execute();
$users = $stmt->fetchAll();
// Get total count for pagination
$countSql = "SELECT COUNT(*) FROM users";
if (!empty($search)) {
$countSql .= " WHERE name LIKE :search OR email LIKE :search";
$countStmt = $pdo->prepare($countSql);
$countStmt->execute([':search' => "%$search%"]);
} else {
$countStmt = $pdo->query($countSql);
}
$totalUsers = $countStmt->fetchColumn();
$totalPages = ceil($totalUsers / $perPage);
} catch (PDOException $e) {
die("Error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html>
<head>
<title>User Management</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2>User Management</h2>
<?php if (isset($_GET['success'])): ?>
<div class="alert alert-success">
<?php
if ($_GET['success'] === 'created') echo 'User created successfully!';
if ($_GET['success'] === 'updated') echo 'User updated successfully!';
if ($_GET['success'] === 'deleted') echo 'User deleted successfully!';
?>
</div>
<?php endif; ?>
<div class="row mb-3">
<div class="col-md-6">
<a href="create.php" class="btn btn-primary">Add New User</a>
</div>
<div class="col-md-6">
<form method="GET" class="d-flex">
<input type="text" name="search" class="form-control me-2"
placeholder="Search by name or email"
value="<?php echo htmlspecialchars($search); ?>">
<button type="submit" class="btn btn-secondary">Search</button>
</form>
</div>
</div>
<table class="table table-striped">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Email</th>
<th>Phone</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $user): ?>
<tr>
<td><?php echo $user['id']; ?></td>
<td><?php echo htmlspecialchars($user['name']); ?></td>
<td><?php echo htmlspecialchars($user['email']); ?></td>
<td><?php echo htmlspecialchars($user['phone']); ?></td>
<td><?php echo date('Y-m-d', strtotime($user['created_at'])); ?></td>
<td>
<a href="view.php?id=<?php echo $user['id']; ?>" class="btn btn-sm btn-info">View</a>
<a href="edit.php?id=<?php echo $user['id']; ?>" class="btn btn-sm btn-warning">Edit</a>
<a href="delete.php?id=<?php echo $user['id']; ?>"
class="btn btn-sm btn-danger"
onclick="return confirm('Are you sure?')">Delete</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- Pagination -->
<?php if ($totalPages > 1): ?>
<nav>
<ul class="pagination">
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<li class="page-item <?php echo $i === $page ? 'active' : ''; ?>">
<a class="page-link" href="?page=<?php echo $i; ?>&search=<?php echo urlencode($search); ?>">
<?php echo $i; ?>
</a>
</li>
<?php endfor; ?>
</ul>
</nav>
<?php endif; ?>
</div>
</body>
</html>
View Single User (view.php)
<?php
// view.php
require_once 'config.php';
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
try {
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();
if (!$user) {
header("Location: index.php?error=User not found");
exit;
}
} catch (PDOException $e) {
die("Error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html>
<head>
<title>View User</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2>User Details</h2>
<div class="card">
<div class="card-body">
<p><strong>ID:</strong> <?php echo $user['id']; ?></p>
<p><strong>Name:</strong> <?php echo htmlspecialchars($user['name']); ?></p>
<p><strong>Email:</strong> <?php echo htmlspecialchars($user['email']); ?></p>
<p><strong>Phone:</strong> <?php echo htmlspecialchars($user['phone']); ?></p>
<p><strong>Created:</strong> <?php echo $user['created_at']; ?></p>
<p><strong>Updated:</strong> <?php echo $user['updated_at']; ?></p>
</div>
</div>
<div class="mt-3">
<a href="edit.php?id=<?php echo $user['id']; ?>" class="btn btn-warning">Edit</a>
<a href="index.php" class="btn btn-secondary">Back to List</a>
</div>
</div>
</body>
</html>
UPDATE Operation
Edit Form (edit.php)
<?php
// edit.php
require_once 'config.php';
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
try {
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch();
if (!$user) {
header("Location: index.php?error=User not found");
exit;
}
} catch (PDOException $e) {
die("Error: " . $e->getMessage());
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Edit User</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h2>Edit User</h2>
<?php if (isset($_GET['error'])): ?>
<div class="alert alert-danger"><?php echo htmlspecialchars($_GET['error']); ?></div>
<?php endif; ?>
<form action="update.php" method="POST">
<input type="hidden" name="id" value="<?php echo $user['id']; ?>">
<div class="mb-3">
<label class="form-label">Name</label>
<input type="text" name="name" class="form-control"
value="<?php echo htmlspecialchars($user['name']); ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Email</label>
<input type="email" name="email" class="form-control"
value="<?php echo htmlspecialchars($user['email']); ?>" required>
</div>
<div class="mb-3">
<label class="form-label">Phone</label>
<input type="tel" name="phone" class="form-control"
value="<?php echo htmlspecialchars($user['phone']); ?>">
</div>
<button type="submit" class="btn btn-primary">Update User</button>
<a href="index.php" class="btn btn-secondary">Cancel</a>
</form>
</div>
</body>
</html>
Update Handler (update.php)
<?php
// update.php
require_once 'config.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
// Get form data
$id = (int)$_POST['id'];
$name = trim($_POST['name']);
$email = trim($_POST['email']);
$phone = trim($_POST['phone']);
// Validate input
$errors = [];
if (empty($name)) {
$errors[] = "Name is required";
}
if (empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errors[] = "Valid email is required";
}
if (!empty($errors)) {
$errorMsg = implode(', ', $errors);
header("Location: edit.php?id=$id&error=" . urlencode($errorMsg));
exit;
}
// Connect to database
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Update data
$sql = "UPDATE users SET name = :name, email = :email, phone = :phone WHERE id = :id";
$stmt = $pdo->prepare($sql);
$stmt->execute([
':name' => $name,
':email' => $email,
':phone' => $phone,
':id' => $id
]);
// Redirect with success message
header("Location: index.php?success=updated");
exit;
} catch (PDOException $e) {
// Check for duplicate email
if ($e->getCode() == 23000) {
header("Location: edit.php?id=$id&error=Email already exists");
} else {
header("Location: edit.php?id=$id&error=Database error occurred");
}
exit;
}
} else {
header("Location: index.php");
exit;
}
?>
DELETE Operation
Delete Handler (delete.php)
<?php
// delete.php
require_once 'config.php';
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id > 0) {
try {
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Check if user exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE id = ?");
$stmt->execute([$id]);
if ($stmt->fetch()) {
// Delete user
$stmt = $pdo->prepare("DELETE FROM users WHERE id = ?");
$stmt->execute([$id]);
header("Location: index.php?success=deleted");
} else {
header("Location: index.php?error=User not found");
}
} catch (PDOException $e) {
header("Location: index.php?error=Error deleting user");
}
} else {
header("Location: index.php");
}
exit;
?>
Soft Delete (Alternative)
<?php
// Soft delete - add deleted_at column to users table
// ALTER TABLE users ADD COLUMN deleted_at TIMESTAMP NULL DEFAULT NULL;
// soft_delete.php
$id = (int)$_GET['id'];
try {
$pdo = new PDO("mysql:host=localhost;dbname=mydb", "root", "");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Soft delete
$stmt = $pdo->prepare("UPDATE users SET deleted_at = NOW() WHERE id = ?");
$stmt->execute([$id]);
header("Location: index.php?success=deleted");
} catch (PDOException $e) {
header("Location: index.php?error=Error deleting user");
}
// When querying, exclude deleted records
// SELECT * FROM users WHERE deleted_at IS NULL
// Restore deleted user
// UPDATE users SET deleted_at = NULL WHERE id = ?
?>
Complete CRUD Application
Database Configuration (config.php)
<?php
// config.php
define('DB_HOST', 'localhost');
define('DB_NAME', 'mydb');
define('DB_USER', 'root');
define('DB_PASS', '');
function getConnection() {
static $pdo = null;
if ($pdo === null) {
try {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
$pdo = new PDO($dsn, DB_USER, DB_PASS, $options);
} catch (PDOException $e) {
error_log("Database connection error: " . $e->getMessage());
die("Database connection failed");
}
}
return $pdo;
}
?>
User Model Class (User.php)
<?php
// User.php
class User {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function create($data) {
$sql = "INSERT INTO users (name, email, phone) VALUES (:name, :email, :phone)";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute($data);
}
public function getAll($page = 1, $perPage = 10, $search = '') {
$offset = ($page - 1) * $perPage;
if (!empty($search)) {
$sql = "SELECT * FROM users WHERE name LIKE :search OR email LIKE :search
ORDER BY id DESC LIMIT :offset, :perPage";
$stmt = $this->pdo->prepare($sql);
$stmt->bindValue(':search', "%$search%");
} else {
$sql = "SELECT * FROM users ORDER BY id DESC LIMIT :offset, :perPage";
$stmt = $this->pdo->prepare($sql);
}
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->bindValue(':perPage', $perPage, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
public function getById($id) {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
return $stmt->fetch();
}
public function update($id, $data) {
$sql = "UPDATE users SET name = :name, email = :email, phone = :phone WHERE id = :id";
$stmt = $this->pdo->prepare($sql);
$data[':id'] = $id;
return $stmt->execute($data);
}
public function delete($id) {
$stmt = $this->pdo->prepare("DELETE FROM users WHERE id = ?");
return $stmt->execute([$id]);
}
public function count($search = '') {
if (!empty($search)) {
$sql = "SELECT COUNT(*) FROM users WHERE name LIKE :search OR email LIKE :search";
$stmt = $this->pdo->prepare($sql);
$stmt->execute([':search' => "%$search%"]);
} else {
$sql = "SELECT COUNT(*) FROM users";
$stmt = $this->pdo->query($sql);
}
return $stmt->fetchColumn();
}
}
?>
Session Summary
Key Points
- CRUD operations form the foundation of database-driven applications
- CREATE: INSERT new records with proper validation
- READ: SELECT and display data with pagination and search
- UPDATE: Modify existing records with validation
- DELETE: Remove records (hard or soft delete)
- Always use prepared statements to prevent SQL injection
- Implement proper error handling and user feedback
- Validate and sanitize all user input
- Use MVC patterns for better code organization
Next Session Preview
In the next session, we will explore PHP XML Processing, learning how to read, parse, and generate XML documents in PHP applications.