درس 13 من 17
قاعدة البيانات — المقدمة
MySQL + phpMyAdmin + PDO — كيف تُنشئ وتتصل بقاعدة البيانات
ما هي قاعدة البيانات ولماذا نحتاجها؟
قاعدة البيانات هي مكان منظم لتخزين البيانات بشكل دائم. بدونها، ستُفقد كل البيانات عند إغلاق المتصفح أو إعادة تشغيل السيرفر.
متى تستخدم قاعدة البيانات؟
متى تستخدم قاعدة البيانات؟
- تسجيل وتسجيل دخول المستخدمين
- المنتجات والطلبات في متجر إلكتروني
- المقالات والتعليقات في مدونة
- الرسائل في تطبيق محادثة
- أي بيانات تحتاج للاستمرارية
| مصطلح | المعنى | مثال |
|---|---|---|
| Database | قاعدة البيانات الكاملة | my_shop_db |
| Table | جدول بيانات محدد | users, products |
| Row (Record) | سجل واحد في الجدول | بيانات مستخدم واحد |
| Column (Field) | عمود — نوع بيانات محدد | id, name, email |
| Primary Key | معرّف فريد لكل صف | id AUTO_INCREMENT |
| Foreign Key | ربط جدول بجدول آخر | user_id في جدول orders |
| Index | تسريع البحث | INDEX على عمود email |
| Query | استعلام لقراءة/تعديل البيانات | SELECT * FROM users |
إنشاء قاعدة البيانات في phpMyAdmin
- افتح phpMyAdmin
روح علىlocalhost/phpmyadminفي المتصفح - أنشئ قاعدة بيانات جديدة
اضغط علىNewفي الشريط الجانبي، اكتب اسم قاعدة البياناتphp_course_db، اخترutf8mb4_unicode_ciكـ Collation، ثم اضغط Create - أنشئ جدول users
اضغط على قاعدة البيانات الجديدة ثم اضغطSQLوالصق الكود أدناه
SQL — إنشاء جدول users
-- إنشاء قاعدة البيانات
CREATE DATABASE IF NOT EXISTS php_course_db
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE php_course_db;
-- ─── جدول المستخدمين ───────────────────────────────────────
CREATE TABLE IF NOT EXISTS users (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL,
role ENUM('user','admin','moderator') DEFAULT 'user',
is_active TINYINT(1) DEFAULT 1,
avatar VARCHAR(255) NULL,
bio TEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- Indexes لتسريع البحث
INDEX idx_email (email),
INDEX idx_username (username),
INDEX idx_role (role)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- ─── جدول المنتجات ────────────────────────────────────────
CREATE TABLE IF NOT EXISTS products (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
stock INT UNSIGNED DEFAULT 0,
category VARCHAR(100),
image VARCHAR(255),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_category (category),
INDEX idx_price (price)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ─── جدول الطلبات ─────────────────────────────────────────
CREATE TABLE IF NOT EXISTS orders (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
total DECIMAL(10,2) NOT NULL,
status ENUM('pending','processing','shipped','delivered','cancelled') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Foreign Key
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ─── بيانات تجريبية ────────────────────────────────────────
INSERT INTO users (username, email, password, role) VALUES
('admin', 'admin@example.com', '$2y$10$example_hash_1', 'admin'),
('ahmed', 'ahmed@example.com', '$2y$10$example_hash_2', 'user'),
('fatima', 'fatima@example.com', '$2y$10$example_hash_3', 'user');
INSERT INTO products (name, price, stock, category) VALUES
('كتاب PHP المتقدم', 89.99, 50, 'كتب'),
('كورس تصميم الويب', 299.00, 999, 'كورسات'),
('قميص المبرمجين', 149.50, 30, 'ملابس');
الاتصال بـ MySQL من PHP — PDO
PHP لديها طريقتان للاتصال بـ MySQL:
مزايا PDO على mysqli:
mysqli و PDO. نستخدم PDO لأنه أكثر أماناً وأكثر مرونة.
مزايا PDO على mysqli:
- يدعم 12+ نوع من قواعد البيانات (MySQL, PostgreSQL, SQLite...)
- Prepared Statements أسهل وأأمن من SQL Injection
- معالجة الأخطاء بـ Exceptions أنظف
- Named Parameters أكثر قراءةً:
:nameبدل?
PHP — db.php (ملف الاتصال)
<?php
// ─── db.php — ملف الاتصال بقاعدة البيانات ────────────────
// يُضمَّن في كل ملف يحتاج DB بـ: require_once 'db.php';
// إعدادات الاتصال — يُفضَّل وضعها في .env في المشاريع الحقيقية
define('DB_HOST', 'localhost');
define('DB_NAME', 'php_course_db');
define('DB_USER', 'root');
define('DB_PASS', ''); // كلمة مرور XAMPP فارغة
define('DB_CHARSET', 'utf8mb4');
// ─── بناء DSN ──────────────────────────────────────────────
$dsn = sprintf(
"mysql:host=%s;dbname=%s;charset=%s",
DB_HOST, DB_NAME, DB_CHARSET
);
// ─── خيارات PDO ────────────────────────────────────────────
$pdo_options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // رمي exceptions
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // array مترابط
PDO::ATTR_EMULATE_PREPARES => false, // prepared حقيقي
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4", // ترميز صحيح
];
// ─── الاتصال ───────────────────────────────────────────────
try {
$pdo = new PDO($dsn, DB_USER, DB_PASS, $pdo_options);
// $pdo الآن جاهز للاستخدام في أي مكان في الملف
} catch (PDOException $e) {
// في Production: سجّل الخطأ ولا تعرضه للمستخدم
error_log("DB Error: " . $e->getMessage());
// رسالة عامة للمستخدم
die(json_encode([
'error' => true,
'message' => 'Database connection failed. Please try again later.'
]));
}
?>
✅ استخدام db.php في ملفات أخرى
في أي ملف PHP تحتاج فيه قاعدة البيانات، أضف في البداية:
require_once 'db.php'; وستجد $pdo جاهزاً للاستخدام.
أنواع بيانات MySQL الأساسية
اختيار نوع البيانات الصحيح مهم جداً للأداء والتخزين الأمثل.
SQL — أنواع البيانات
-- ─── أنواع الأرقام ──────────────────────────────────────────
TINYINT -- -128 إلى 127 (1 byte) → مثالي لـ is_active, is_admin
SMALLINT -- -32,768 إلى 32,767 (2 bytes)
INT -- -2.1B إلى 2.1B (4 bytes) → الأكثر استخداماً
BIGINT -- أرقام ضخمة جداً (8 bytes)
DECIMAL(10,2) -- أرقام عشرية دقيقة → للأسعار دائماً!
FLOAT -- عشري تقريبي (لا تستخدم للمال)
-- ─── النصوص ─────────────────────────────────────────────────
CHAR(n) -- نص بطول ثابت n → مثالي لـ codes, country codes
VARCHAR(n) -- نص بطول متغير حتى n حرف → الاسم، البريد
TEXT -- نص طويل حتى 65,535 حرف → المقالات، الوصف
MEDIUMTEXT -- حتى 16MB
LONGTEXT -- حتى 4GB
-- ─── التواريخ والأوقات ───────────────────────────────────────
DATE -- YYYY-MM-DD فقط → تاريخ الميلاد
TIME -- HH:MM:SS فقط
DATETIME -- YYYY-MM-DD HH:MM:SS → لا يتأثر بـ timezone
TIMESTAMP -- مثل DATETIME + يتحدث تلقائياً + يتأثر بـ timezone
YEAR -- 1901 - 2155
-- ─── أنواع خاصة ────────────────────────────────────────────
ENUM('val1','val2') -- قيمة من قائمة محددة → status, role, gender
SET('a','b','c') -- قيم متعددة من قائمة
BOOLEAN -- مثل TINYINT(1) → true/false
JSON -- بيانات JSON كاملة (MySQL 5.7+)
BLOB -- بيانات ثنائية (صور في DB - لا يُنصح)
-- ─── مثال على جدول محسّن ───────────────────────────────────
CREATE TABLE posts (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
user_id INT UNSIGNED NOT NULL,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) NOT NULL UNIQUE, -- للـ URL
content TEXT NOT NULL,
excerpt VARCHAR(500), -- ملخص قصير
status ENUM('draft','published','archived') DEFAULT 'draft',
views INT UNSIGNED DEFAULT 0,
likes INT UNSIGNED DEFAULT 0,
is_featured BOOLEAN DEFAULT FALSE,
published_at DATETIME NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id),
INDEX idx_status (status),
INDEX idx_slug (slug),
FULLTEXT INDEX ft_search (title, content) -- للبحث النصي
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
أساسيات SQL — الاستعلامات الأساسية
SQL (Structured Query Language) هي اللغة التي نتحدث بها مع قاعدة البيانات. دعنا نتعلم الأساسيات:
SQL — الاستعلامات الأساسية
-- ─── SELECT — قراءة البيانات ───────────────────────────────
SELECT * FROM users; -- كل الأعمدة
SELECT id, username, email FROM users; -- أعمدة محددة
SELECT * FROM users WHERE role = 'admin'; -- مع شرط
SELECT * FROM users ORDER BY created_at DESC; -- ترتيب تنازلي
SELECT * FROM users LIMIT 10 OFFSET 0; -- أول 10 صفوف
SELECT * FROM users WHERE email LIKE '%@gmail%'; -- البحث بـ LIKE
-- ─── الدوال التجميعية ────────────────────────────────────────
SELECT COUNT(*) AS total_users FROM users;
SELECT AVG(price) AS avg_price FROM products;
SELECT SUM(total) AS revenue FROM orders WHERE status='delivered';
SELECT MAX(price), MIN(price) FROM products;
SELECT category, COUNT(*) AS count FROM products GROUP BY category;
-- ─── INSERT — إضافة بيانات ────────────────────────────────
INSERT INTO users (username, email, password)
VALUES ('new_user', 'new@example.com', 'hashed_password');
-- إضافة أكثر من صف
INSERT INTO products (name, price, stock) VALUES
('منتج 1', 99.99, 10),
('منتج 2', 149.99, 5);
-- ─── UPDATE — تعديل بيانات ───────────────────────────────
UPDATE users SET role = 'admin' WHERE id = 5;
UPDATE products SET stock = stock - 1 WHERE id = 3;
UPDATE users SET is_active = 0, updated_at = NOW() WHERE last_login < '2024-01-01';
-- ─── DELETE — حذف بيانات ─────────────────────────────────
DELETE FROM users WHERE id = 10;
DELETE FROM orders WHERE status = 'cancelled' AND created_at < '2024-01-01';
-- ─── JOIN — ربط الجداول ──────────────────────────────────
-- المستخدمين مع طلباتهم
SELECT
u.username,
u.email,
COUNT(o.id) AS total_orders,
SUM(o.total) AS total_spent
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id
ORDER BY total_spent DESC;
-- ─── Subquery ─────────────────────────────────────────────
SELECT * FROM users
WHERE id IN (
SELECT user_id FROM orders WHERE total > 500
);
أول استعلام PHP مع PDO
الآن نجمع كل شيء معاً — الاتصال بـ DB وتنفيذ استعلامات:
PHP — أول استعلام مع PDO
<?php
require_once 'db.php'; // $pdo جاهز
// ─── 1. استعلام بسيط بدون معاملات ───────────────────────
$stmt = $pdo->query("SELECT * FROM users ORDER BY created_at DESC");
$users = $stmt->fetchAll(); // مصفوفة من كل الصفوف
foreach ($users as $user) {
echo $user['username'] . " — " . $user['email'] . "<br>";
}
// ─── 2. Prepared Statement مع معامل واحد ─────────────────
$id = 1;
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$id]);
$user = $stmt->fetch(); // صف واحد فقط
if ($user) {
echo "المستخدم: " . htmlspecialchars($user['username']);
} else {
echo "لم يُوجد المستخدم";
}
// ─── 3. Named Parameters (الأوضح) ────────────────────────
$stmt = $pdo->prepare("
SELECT * FROM users
WHERE role = :role AND is_active = :active
ORDER BY created_at DESC
LIMIT :limit
");
$stmt->execute([
':role' => 'admin',
':active' => 1,
':limit' => 10,
]);
$admins = $stmt->fetchAll();
echo "عدد الأدمنز: " . count($admins);
// ─── 4. bindParam vs bindValue ────────────────────────────
$stmt = $pdo->prepare("SELECT * FROM products WHERE price <= :max_price AND category = :cat");
$stmt->bindValue(':max_price', 200.00, PDO::PARAM_STR);
$stmt->bindValue(':cat', 'كتب');
$stmt->execute();
$products = $stmt->fetchAll();
// ─── 5. rowCount — عدد الصفوف المتأثرة ──────────────────
$stmt = $pdo->prepare("UPDATE users SET is_active = 1 WHERE id = ?");
$stmt->execute([5]);
echo "صفوف معدّلة: " . $stmt->rowCount();
// ─── 6. lastInsertId — معرف آخر صف مُضاف ─────────────────
$stmt = $pdo->prepare("INSERT INTO users (username, email, password) VALUES (?, ?, ?)");
$stmt->execute(['new_user', 'new@email.com', password_hash('pass123', PASSWORD_BCRYPT)]);
$new_id = $pdo->lastInsertId();
echo "المعرف الجديد: " . $new_id;
?>
الناتج
ahmed — ahmed@example.com
fatima — fatima@example.com
admin — admin@example.com
المستخدم: admin
عدد الأدمنز: 1
صفوف معدّلة: 1
المعرف الجديد: 4🧪 اختبر فهمك
5 أسئلةسؤال 1
ما الفرق بين VARCHAR(255) و TEXT في MySQL؟
سؤال 2
ما نوع البيانات المناسب لتخزين أسعار المنتجات في MySQL؟
سؤال 3
لماذا نستخدم PDO بدلاً من mysqli في PHP؟
سؤال 4
ما الدالة التي تُعيد معرّف آخر صف تم إضافته في PDO؟
سؤال 5
ما الفرق بين fetch() و fetchAll() في PDO؟