/ دورة PHP الشاملة
0/17 مكتملة
درس 13 من 17

قاعدة البيانات — المقدمة

MySQL + phpMyAdmin + PDO — كيف تُنشئ وتتصل بقاعدة البيانات

🕐 55 دقيقة 🗄️ MySQL + PDO 📝 5 أسئلة

ما هي قاعدة البيانات ولماذا نحتاجها؟

قاعدة البيانات هي مكان منظم لتخزين البيانات بشكل دائم. بدونها، ستُفقد كل البيانات عند إغلاق المتصفح أو إعادة تشغيل السيرفر.

متى تستخدم قاعدة البيانات؟
  • تسجيل وتسجيل دخول المستخدمين
  • المنتجات والطلبات في متجر إلكتروني
  • المقالات والتعليقات في مدونة
  • الرسائل في تطبيق محادثة
  • أي بيانات تحتاج للاستمرارية

مصطلحالمعنىمثال
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: 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؟