أساسيات PHP
بناء تطبيق CRUD
ما هو CRUD؟
CRUD يرمز إلى الإنشاء والقراءة والتحديث والحذف - العمليات الأساسية الأربع للتخزين الدائم. في هذا الدرس، سنبني تطبيق إدارة مهام كامل يوضح جميع عمليات CRUD مع الأمان المناسب.
بنية المشروع
task-manager/ ├── config/ │ └── database.php # تكوين قاعدة البيانات ├── includes/ │ ├── header.php # رأس الصفحة المشترك │ └── footer.php # تذييل الصفحة المشترك ├── css/ │ └── style.css # الأنماط ├── index.php # قائمة المهام (القراءة) ├── create.php # إضافة مهمة جديدة (الإنشاء) ├── edit.php # تعديل المهمة (التحديث) ├── delete.php # حذف المهمة (الحذف) └── view.php # عرض مهمة واحدة (القراءة)
الخطوة 1: إعداد قاعدة البيانات
أولاً، أنشئ قاعدة البيانات وجدول المهام:
CREATE DATABASE task_manager;
USE task_manager;
CREATE TABLE tasks (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(200) NOT NULL,
description TEXT,
status ENUM('pending', 'in_progress', 'completed') DEFAULT 'pending',
priority ENUM('low', 'medium', 'high') DEFAULT 'medium',
due_date DATE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
الخطوة 2: تكوين قاعدة البيانات (config/database.php)
<?php
// تكوين قاعدة البيانات
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'task_manager');
// تفعيل الإبلاغ عن الأخطاء (عطّل في الإنتاج)
ini_set('display_errors', 1);
error_reporting(E_ALL);
// تفعيل وضع استثناء MySQLi
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
// إنشاء الاتصال
try {
$conn = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
$conn->set_charset("utf8mb4");
} catch (mysqli_sql_exception $e) {
die("فشل الاتصال بقاعدة البيانات: " . $e->getMessage());
}
?>
الخطوة 3: قالب الرأس (includes/header.php)
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $page_title ?? 'مدير المهام'; ?></title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<nav>
<div class="container">
<h1>مدير المهام</h1>
<ul>
<li><a href="index.php">جميع المهام</a></li>
<li><a href="create.php">إضافة مهمة جديدة</a></li>
</ul>
</div>
</nav>
<main class="container">
الخطوة 4: قالب التذييل (includes/footer.php)
</main>
<footer>
<div class="container">
<p>© <?php echo date('Y'); ?> مدير المهام</p>
</div>
</footer>
</body>
</html>
الخطوة 5: القراءة - قائمة جميع المهام (index.php)
<?php
require_once 'config/database.php';
$page_title = 'جميع المهام';
include 'includes/header.php';
// الحصول على الفلتر من URL
$filter = $_GET['status'] ?? 'all';
// بناء الاستعلام بناءً على الفلتر
if ($filter === 'all') {
$sql = "SELECT * FROM tasks ORDER BY created_at DESC";
$stmt = $conn->prepare($sql);
} else {
$sql = "SELECT * FROM tasks WHERE status = ? ORDER BY created_at DESC";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $filter);
}
$stmt->execute();
$result = $stmt->get_result();
?>
<div class="page-header">
<h2>جميع المهام</h2>
<a href="create.php" class="btn btn-primary">إضافة مهمة جديدة</a>
</div>
<!-- أزرار التصفية -->
<div class="filters">
<a href="?status=all" class="btn <?php echo $filter === 'all' ? 'active' : ''; ?>">الكل</a>
<a href="?status=pending" class="btn <?php echo $filter === 'pending' ? 'active' : ''; ?>">معلقة</a>
<a href="?status=in_progress" class="btn <?php echo $filter === 'in_progress' ? 'active' : ''; ?>">قيد التنفيذ</a>
<a href="?status=completed" class="btn <?php echo $filter === 'completed' ? 'active' : ''; ?>">مكتملة</a>
</div>
<?php if ($result->num_rows > 0): ?>
<table class="task-table">
<thead>
<tr>
<th>العنوان</th>
<th>الحالة</th>
<th>الأولوية</th>
<th>تاريخ الاستحقاق</th>
<th>الإجراءات</th>
</tr>
</thead>
<tbody>
<?php while ($task = $result->fetch_assoc()): ?>
<tr>
<td>
<a href="view.php?id=<?php echo $task['id']; ?>">
<?php echo htmlspecialchars($task['title']); ?>
</a>
</td>
<td>
<span class="badge badge-<?php echo $task['status']; ?>">
<?php echo ucfirst(str_replace('_', ' ', $task['status'])); ?>
</span>
</td>
<td>
<span class="badge badge-<?php echo $task['priority']; ?>">
<?php echo ucfirst($task['priority']); ?>
</span>
</td>
<td><?php echo $task['due_date'] ? date('M d, Y', strtotime($task['due_date'])) : 'غير محدد'; ?></td>
<td>
<a href="edit.php?id=<?php echo $task['id']; ?>" class="btn btn-sm">تعديل</a>
<a href="delete.php?id=<?php echo $task['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('حذف هذه المهمة؟')">حذف</a>
</td>
</tr>
<?php endwhile; ?>
</tbody>
</table>
<?php else: ?>
<p class="no-tasks">لم يتم العثور على مهام. <a href="create.php">أنشئ واحدة الآن</a></p>
<?php endif; ?>
<?php
$stmt->close();
include 'includes/footer.php';
?>
الخطوة 6: الإنشاء - إضافة مهمة جديدة (create.php)
<?php
require_once 'config/database.php';
$page_title = 'إضافة مهمة جديدة';
$errors = [];
$success = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// التحقق من صحة المدخلات
$title = trim($_POST['title'] ?? '');
$description = trim($_POST['description'] ?? '');
$status = $_POST['status'] ?? 'pending';
$priority = $_POST['priority'] ?? 'medium';
$due_date = $_POST['due_date'] ?? null;
// التحقق
if (empty($title)) {
$errors[] = "العنوان مطلوب";
} elseif (strlen($title) > 200) {
$errors[] = "يجب ألا يتجاوز العنوان 200 حرف";
}
if (!in_array($status, ['pending', 'in_progress', 'completed'])) {
$errors[] = "حالة غير صالحة";
}
if (!in_array($priority, ['low', 'medium', 'high'])) {
$errors[] = "أولوية غير صالحة";
}
// إذا لم تكن هناك أخطاء، أدرج المهمة
if (empty($errors)) {
$sql = "INSERT INTO tasks (title, description, status, priority, due_date)
VALUES (?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("sssss", $title, $description, $status, $priority, $due_date);
if ($stmt->execute()) {
$success = true;
$task_id = $stmt->insert_id;
header("Location: view.php?id=$task_id&created=1");
exit;
} else {
$errors[] = "فشل في إنشاء المهمة";
}
$stmt->close();
}
}
include 'includes/header.php';
?>
<h2>إضافة مهمة جديدة</h2>
<?php if (!empty($errors)): ?>
<div class="alert alert-danger">
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<form method="POST" action="" class="task-form">
<div class="form-group">
<label for="title">العنوان *</label>
<input type="text" id="title" name="title" required
value="<?php echo htmlspecialchars($_POST['title'] ?? ''); ?>">
</div>
<div class="form-group">
<label for="description">الوصف</label>
<textarea id="description" name="description" rows="5"><?php echo htmlspecialchars($_POST['description'] ?? ''); ?></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="status">الحالة</label>
<select id="status" name="status">
<option value="pending">معلقة</option>
<option value="in_progress">قيد التنفيذ</option>
<option value="completed">مكتملة</option>
</select>
</div>
<div class="form-group">
<label for="priority">الأولوية</label>
<select id="priority" name="priority">
<option value="low">منخفضة</option>
<option value="medium" selected>متوسطة</option>
<option value="high">عالية</option>
</select>
</div>
<div class="form-group">
<label for="due_date">تاريخ الاستحقاق</label>
<input type="date" id="due_date" name="due_date">
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">إنشاء المهمة</button>
<a href="index.php" class="btn">إلغاء</a>
</div>
</form>
<?php include 'includes/footer.php'; ?>
الخطوة 7: القراءة - عرض مهمة واحدة (view.php)
تعرض صفحة view.php تفاصيل المهمة الكاملة مع خيارات التعديل والحذف.
الخطوة 8: التحديث - تعديل المهمة (edit.php)
تشبه صفحة edit.php صفحة create.php ولكنها تملأ النموذج مسبقًا بالبيانات الحالية وتستخدم استعلام UPDATE بدلاً من INSERT.
الخطوة 9: الحذف - حذف المهمة (delete.php)
<?php
require_once 'config/database.php';
// الحصول على معرف المهمة
$task_id = $_GET['id'] ?? 0;
// التحقق من وجود المهمة
$stmt = $conn->prepare("SELECT id FROM tasks WHERE id = ?");
$stmt->bind_param("i", $task_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
header("Location: index.php");
exit;
}
// حذف المهمة
$stmt = $conn->prepare("DELETE FROM tasks WHERE id = ?");
$stmt->bind_param("i", $task_id);
if ($stmt->execute()) {
header("Location: index.php?deleted=1");
} else {
header("Location: index.php?error=1");
}
$stmt->close();
exit;
?>
نصيحة: لتطبيقات الإنتاج، نفّذ "الحذف الناعم" بإضافة عمود
deleted_at بدلاً من إزالة السجلات نهائيًا.
ملاحظات أمنية:
- يتم التحقق من صحة جميع مدخلات المستخدم وتعقيمها
- تمنع العبارات المحضرة حقن SQL
htmlspecialchars()يمنع هجمات XSS- طريقة HTTP POST لتعديل البيانات (الإنشاء والتحديث والحذف)
- مطالبات التأكيد قبل الإجراءات المدمرة
تمرين: تحسين مدير المهام
- أضف ترقيمًا لقائمة المهام (10 مهام في كل صفحة)
- نفّذ وظيفة البحث (البحث حسب العنوان أو الوصف)
- أضف خيارات الفرز (حسب التاريخ والأولوية والحالة)
- أنشئ زر إجراء سريع "وضع علامة كمكتمل"
- أضف فئات أو وسوم للمهام
- نفّذ مصادقة المستخدم (عدة مستخدمين بمهامهم الخاصة)
- أضف مرفقات ملفات للمهام
- أنشئ لوحة معلومات مع إحصائيات المهام
- أضف إشعارات البريد الإلكتروني لتواريخ الاستحقاق
- نفّذ نقطة نهاية API لتصدير المهام كـ JSON
أفضل ممارسات تطبيقات CRUD
- التحقق: تحقق دائمًا من مدخلات المستخدم على جانب الخادم
- التعقيم: هرب المخرجات بـ
htmlspecialchars() - العبارات المحضرة: لا تدمج أبدًا مدخلات المستخدم في استعلامات SQL
- معالجة الأخطاء: اعرض رسائل خطأ واضحة للمستخدم
- إعادة التوجيه: استخدم نمط POST-Redirect-GET لمنع التقديم المكرر
- التأكيد: اطلب التأكيد قبل حذف البيانات
- التفويض: تحقق من أن المستخدمين يمكنهم تعديل بياناتهم فقط
- التسجيل: سجّل الإجراءات المهمة للمراجعة
الملخص
- تطبيقات CRUD تنفذ عمليات الإنشاء والقراءة والتحديث والحذف
- استخدم العبارات المحضرة لجميع عمليات قاعدة البيانات
- تحقق من صحة جميع مدخلات المستخدم وعقّمها
- نفّذ معالجة الأخطاء المناسبة وتعليقات المستخدم
- استخدم نمط POST-Redirect-GET لإرسال النماذج
- هرب المخرجات دائمًا لمنع هجمات XSS
- هيكل كودك مع مكونات قابلة لإعادة الاستخدام (الرؤوس والتذييلات والتكوين)
- اختبر جميع عمليات CRUD بدقة