Skip to content

12. استفاده از SGBD و MySQL

Image

اکنون اسکریپت‌های PHP را با استفاده از پایگاه داده MySQL می‌نویسیم:

Image

در معماری نشان داده شده در بالا، اسکریپت PHP (1) مستقیماً با SGBD (سیستم مدیریت پایگاه داده) (3) ارتباط برقرار نمی‌کند. این ارتباط از طریق واسطه‌ای به نام درایور SGBD یا درایور SGBD برقرار می‌شود. PHP یک رابط استاندارد برای این درایورها فراهم می‌کند، رابط PDO (ابجکت‌های داده PHP). این رابط توسط کلاس‌های مختلفی که برای هر SGBD متناسب‌سازی شده‌اند، پیاده‌سازی می‌شود: یک کلاس برای SGBD MySQL، کلاس دیگری برای SGBD PostgreSQL… برای تغییر به یک SGBD متفاوت، درایور را تغییر می‌دهید:

Image

راننده PDO اسکریپت PHP (1) را از اسکریپت SGBD (3، 6) جدا می‌کند. از آنجا که این درایورها یک رابط استاندارد را پیاده‌سازی می‌کنند، ممکن است انتظار رود که اسکریپت PHP (1) هنگام سوئیچ کردن از SGBD یا MySQL بدون تغییر باقی بماند (3) به SGBD و PostgreSQL (6). در واقع، این ایده‌آل وجود ندارد. در واقع، برای ارتباط با SGBD، اسکریپت PHP دستورات SQL (زبان پرس‌وجوی استاندارد) را ارسال می‌کند. این زبان توسط همه سیستم‌های SGBD پیاده‌سازی شده است، اما ناقص است. در نتیجه، سیستم‌های SGBD دستورات اختصاصی خود را به آن افزوده‌اند. این یکی از علل اصلی ناسازگاری بین سیستم‌های SGBD است. علاوه بر این، انواع داده‌ای که می‌توان در پایگاه‌های داده استفاده کرد ممکن است از یک SGBD به دیگری متفاوت باشد. به عنوان مثال، PostgreSQL از طیف بسیار گسترده‌تری از انواع داده‌ها نسبت به SGBD و MySQL پشتیبانی می‌کند. این عامل دوم ناسازگاری است. علت دیگر، نحوهٔ مدیریت کلیدهای اصلی خودکار (تولیدشده توسط SGBD) است: تقریباً هر SGBD سیاست خاص خود را دارد. و غیره… دلایل متعددی برای ناسازگاری وجود دارد.

اگر کسی بخواهد هنگام تغییر از MySQL (3) به PostgreSQL (6)، از بازنویسی اسکریپت PHP (1) اجتناب کند، ما عموماً نیاز داریم یک لایه جدید بین اسکریپت PHP (1) و درایور PDO (2، 5) قرار دهیم که وظیفه آن حل کردن ناسازگاری‌های بین دو نسخه SGBD خواهد بود. با این حال، در موارد ساده‌ای که با آن مواجه خواهیم شد، این لایه اضافی ضروری نخواهد بود.

اکنون از SGBD MySQL استفاده خواهیم کرد. این در بسته Laragon گنجانده شده است (به بخش پیوندها مراجعه کنید).

اگر خواننده با مفاهیم پایگاه‌های داده و زبان SQL آشنا نیست، ممکن است بخواهد سند [http://sergetahe.com/cours-tutoriels-de-programmation/cours-tutoriel-sql-avec-le-sgbd-firebird/] را مطالعه کند. این سند به جای MySQL از Firebird SGBD استفاده می‌کند، اما اصول پایه‌ای پایگاه‌های داده و زبان SQL را پوشش می‌دهد. مانند MySQL، فایربرد نسخه‌ای آزاد و رایگان دارد که ردپای حافظه کمی دارد.

12.1. ایجاد یک پایگاه داده

اکنون به شما نشان خواهیم داد که چگونه با استفاده از ابزار Laragon یک پایگاه داده و یک کاربر MySQL ایجاد کنید.

Image

  • پس از راه‌اندازی، Laragon [1] را می‌توان از طریق یک منوی [2] مدیریت کرد؛
  • در [3-5]، ابزار مدیریت [phpMyAdmin] برای MySQL نصب می‌شود، در صورتی که قبلاً نصب نشده باشد؛

Image

  • در [6]، سرور وب آپاچی به همراه SGBD و MySQL راه‌اندازی می‌شود؛
  • در [7]، سرور آپاچی راه‌اندازی می‌شود؛
  • در [8]، SBD و MySQL راه‌اندازی می‌شوند؛

Image

  • در [8-10]، یک پایگاه داده ایجاد می‌شود و [dbpersonnes] [11] نام‌گذاری می‌شود. ما قصد داریم یک پایگاه داده از افراد بسازیم؛

Image

  • در [11]، ما پایگاه‌داده‌ای را که همین حالا ایجاد کرده‌ایم مدیریت خواهیم کرد؛

Image

  • عملیات [Bases de données] یک درخواست وب را به URL [http://localhost/phpmyadmin] ارسال می‌کند. سرور وب آپاچی لارگون پاسخ می‌دهد. URL و [http://localhost/phpmyadmin]، URLِ ابزار [phpMyAdmin] هستند که قبلاً نصب کردیم، [5]. این ابزار به شما امکان مدیریت پایگاه‌های داده MySQL را می‌دهد؛
  • به طور پیش‌فرض، نام کاربری و رمز عبور مدیر پایگاه داده عبارتند از: root [13] بدون رمز عبور [14]؛

Image

  • در [16]، پایگاه‌داده‌ای که قبلاً ایجاد کردیم؛

Image

  • ما در حال حاضر یک پایگاه داده به نام [dbpersonnes]، [17] که خالی است، و [18] داریم؛

ما کاربری به نام [admpersonnes] با رمز عبور [nobody] ایجاد می‌کنیم که دسترسی کامل به پایگاه داده [dbpersonnes] خواهد داشت:

Image

  • در [19]، ما در پایگاه داده [dbpersonnes] قرار داریم؛
  • در [20]، زبانه [Privileges] را انتخاب کنید؛
  • در [21-22]، می‌توانیم ببینیم که کاربر [root] دارای حقوق کامل بر پایگاه داده [dbpersonnes] است؛
  • در [23]، یک کاربر جدید ایجاد کنید؛

Image

  • در [25-26]، کاربر دارای شناسه [admdbpersonnes] خواهد بود؛
  • در [27-29]، رمز عبور آنها [nobody] خواهد بود؛
  • در [30]، phpMyAdmin نشان می‌دهد که رمز عبور بسیار ضعیف (به راحتی قابل هک شدن) است. در یک محیط تولید، ترجیح داده می‌شود که با استفاده از [31] یک رمز عبور قوی ایجاد شود؛
  • در [32]، مشخص شده است که کاربر [admdbpersonnes] باید دارای حقوق کامل بر پایگاه داده [dbpersonnes] باشد؛
  • در [33]، اطلاعات ارائه‌شده اعتبارسنجی می‌شود؛

Image

  • در [35]، phpMyAdmin نشان می‌دهد که کاربر ایجاد شده است؛
  • در [36]، دستوری که در SQL به پایگاه داده صادر شده است؛
  • در [37]، کاربر [admpersonnes] دارای حقوق کامل بر پایگاه داده [dbpersonnes] است؛

اکنون داریم:

  • یک پایگاه داده به نام MySQL [dbpersonnes];
  • کاربری به نام [admpersonnes/nobody] که حقوق کامل بر این پایگاه داده دارد؛

ما قصد داریم اسکریپت‌های PHP را برای استفاده از پایگاه داده بنویسیم. PHP کتابخانه‌های مختلفی برای مدیریت پایگاه داده‌ها فراهم می‌کند. ما از کتابخانه PDO (PHP Data Objects) استفاده خواهیم کرد که بین کدهای PHP و SGBD قرار دارد:

Image

کتابخانه PDO به اسکریپت PHP اجازه می‌دهد تا از ماهیت دقیق SGBD مورد استفاده مستقل باشد. بنابراین، همانطور که در بالا نشان داده شده است، می‌توان SGBD MySQL را با SGBD PostgreSQL جایگزین کرد، با حداقل تأثیر بر کد اسکریپت PHP. این کتابخانه به‌طور پیش‌فرض در دسترس نیست. می‌توانید با روش زیر در دسترس بودن آن را بررسی کنید:

Image

  • در [1-4]، افزونه‌های فعال PDO را بررسی کنید؛
  • در [5]، می‌توانید ببینید که افزونه PDO برای SGBD و MySQL فعال است. سایر افزونه‌ها فعال نیستند. کافی است روی آن‌ها کلیک کنید تا فعال شوند؛

راه دیگر برای فعال‌سازی یک افزونه، ویرایش مستقیم فایل [php.ini] (بخش لینک‌ها) است که PHP را پیکربندی می‌کند:

Image

  • در [1]، افزونه PDO از MySQL فعال است؛
  • در [2]، افزونه Firebird PDO غیرفعال است؛

پس از ویرایش فایل [php.ini]، باید Laragon را مجدداً راه‌اندازی کنید تا تغییرات اعمال شوند.

12.2. اتصال به پایگاه داده MySQL

یک اتصال به SGBD با ایجاد یک شیء PDO برقرار می‌شود. سازنده پارامترهای مختلفی را می‌پذیرد:

$dbh=new PDO(string $dsn,string $user,string $passwd,array $driver_options)

پارامترها معانی زیر را دارند:

$dsn
(نام منبع داده) یک رشته است که ماهیت SGBD و مکان آن را در اینترنت مشخص می‌کند. رشته «mysql:host=localhost» نشان می‌دهد که این یک SGBD MySQL است که روی سرور محلی در حال اجرا است. این رشته ممکن است شامل پارامترهای دیگری نیز باشد، به‌ویژه پورت گوش‌دادن SGBD و نام پایگاه‌داده که می‌خواهیم به آن متصل شویم: «mysql:host=localhost:port=3306:dbname=dbpersonnes»؛
$user
نام کاربری کاربری که وارد می‌شود؛
$passwd
رمز عبور آنها؛
$driver_options
یک آرایه از گزینه‌ها برای درایور SGBD؛

تنها پارامتر اول الزامی است. ابجئتی که به این صورت ساخته می‌شود، سپس به‌عنوان مبنای تمام عملیات انجام‌شده بر روی پایگاه داده‌ای که اتصال به آن برقرار شده است، عمل خواهد کرد. اگر نتوان ابجکت PDO را ساخت، یک استثنای PDOException پرتاب می‌شود.

در اینجا مثالی از یک اتصال [mysql-01.php] آورده شده است:


<?php

//اتصال به یک پایگاه داده محلی MySql
// هویت کاربر (admpersonnes,nobody) است
const ID = "admpersonnes";
const PWD = "nobody";
const HOTE = "localhost";

try {
  //اتصال
  $dbh = new PDO("mysql:host=".HOTE, ID, PWD);
  print "Connexion réussie\n";
  //اتصال بسته شد
  $dbh = NULL;
} catch (PDOException $e) {
  print "Erreur : " . $e->getMessage() . "\n";
  exit();
}

نتایج:

Connexion réussie

نظرات

  • خط ۱۱: اتصال به SGBD با ایجاد یک شیء PDO برقرار می‌شود. سازنده در اینجا با پارامترهای زیر استفاده می‌شود:
    • یک رشته که نوع SGBD و مکان آن در اینترنت را مشخص می‌کند. رشته «mysql:host=localhost» نشان می‌دهد که این یک SGBD MySQL است که روی سرور محلی در حال اجرا است. پورت مشخص نشده است. بنابراین پورت 3306 به طور پیش‌فرض استفاده می‌شود. نام پایگاه داده نیز مشخص نشده است. سپس یک اتصال به SGBD MySQL برقرار خواهد شد و انتخاب یک پایگاه داده خاص بعداً انجام می‌شود؛
    • یک شناسه کاربری؛
    • رمز عبور آن؛
  • خط ۱۴: اتصال با حذف شیء PDO که در ابتدا ایجاد شده بود، بسته می‌شود؛
  • خط ۱۵: اتصال به SGBD ممکن است با شکست مواجه شود. در این صورت، یک استثنای PDOException پرتاب می‌شود. این از استثناهای PHP و [RuntimeException] مشتق شده است؛
  • خط 16: پیام خطای استثنا نمایش داده می‌شود؛

بیایید اسکریپت را دوباره اجرا کنیم و در خط ۶ یک رمز عبور نادرست وارد کنیم. نتیجه سپس به شرح زیر است:


Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes'@'localhost' (using password: YES)

12.3. ایجاد یک جدول

اسکریپت [mysql-02.php] نشان می‌دهد چگونه در یک پایگاه داده جدول ایجاد کنیم:


<?php

// هویت پایگاه داده
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// اطلاعات هویتی کاربر
const ID = "admpersonnes";
const PWD = "nobody";

try {
  //اتصال به پایگاه داده MySql
  $connexion = new PDO(DSN, ID, PWD);
  // جدول «people» را در صورت وجود حذف کنید
  $sql = "drop table personnes";
  $connexion->exec($sql);
  // جدول «people» را ایجاد کنید
  $sql = "create table personnes (prenom varchar(30) NOT NULL, nom varchar(30) NOT NULL, age integer NOT NULL, primary key(nom,prenom))";
  $connexion->exec($sql);
} catch (PDOException $ex) {
  //نمایش خطا
  print "Erreur : " . $ex->getMessage() . "\n";
} finally {
  // در صورت لزوم از سیستم خارج شوید
  $connexion = NULL;
}
// پایان
print "Terminé\n";
exit;

توضیحات

  • خط ۱۱: اتصال به پایگاه داده. این همیشه اولین کاری است که باید انجام داد. نتیجه اتصال یک شیء [PDO] است که عملیات پایگاه داده از طریق آن انجام خواهد شد؛
  • خط ۱۳: دستور SQL [drop table personnes] جدول [personnes] را از پایگاه داده [dbpersonnes] حذف خواهد کرد. اگر جدول [personnes] وجود نداشته باشد، این امر خطایی ایجاد نخواهد کرد؛
  • خط ۱۴: اجرای دستور قبلی SQL بر روی پایگاه داده [dbpersonnes]. این اجرا ممکن است یک [PDOException] را فعال کند، که در خط ۱۸ رهگیری خواهد شد؛
  • خط 16: این دستور SQL یک جدول [personnes] ایجاد می‌کند. یک جدول شامل سطرها و ستون‌ها است. ستون‌ها ساختار جدول را تشکیل می‌دهند. سطرها محتوای جدول را تشکیل می‌دهند. یک پایگاه داده ممکن است شامل یک یا چند جدول باشد. جدول [personnes] در اینجا سه ستون خواهد داشت:
    • نام: نام یک شخص به صورت یک رشته حداکثر ۳۰ کاراکتری؛
    • نام خانوادگی: نام خانوادگی همان شخص به صورت رشته‌ای با حداکثر ۳۰ کاراکتر؛
    • سن: سن شخص به صورت یک عدد صحیح؛
    • ویژگی NOT NULL در یک ستون ایجاب می‌کند که آن ستون دارای مقدار باشد. عدم ارائه مقدار منجر به یک [PDOException] می‌شود؛
    • [primary key(nom,prenom)] کلید اصلی را برای جدول [personnes] تعیین می‌کند. یک کلید اصلی برای هر سطر در جدول یک مقدار منحصربه‌فرد دارد. در اینجا، کلید اصلی با الحاق ستون‌های [nom] و [prenom] از سطر ایجاد می‌شود. این قید تضمین می‌کند که جدول نمی‌تواند شامل دو نفر با نام و نام خانوادگی یکسان باشد، یعنی دو نفر با نام مشابه. ایجاد فردی با نام مشابه فرد دیگر در جدول منجر به خطا می‌شود: [PDOException];
  • خط ۱۷: اجرای دستور SQL بر روی پایگاه داده [dbpersonnes];
  • خط ۲۰: اگر [PDOException] رخ دهد، پیام خطای مربوطه نمایش داده می‌شود؛
  • خطوط 21–24: عبارت [finally] در همه موارد، چه استثنائی رخ دهد یا نه، برای بستن اتصال به پایگاه داده اجرا می‌شود (خط 23);

نتایج:

اگر اسکریپت بدون خطا اجرا شود، وجود جدول در phpMyAdmin قابل مشاهده است:

Image

Image

  • در [3]، پایگاه داده؛
  • در [4]، جدول نمایش داده می‌شود؛
  • در [5]، ساختار جدول در برگه [Structure] نمایش داده می‌شود؛
  • در [6-8]، سه ستون جدول؛
  • در [9]، هیچ‌یک از سه ستون نباید خالی باشد؛

Image

  • در [10]، فهرست شاخص‌های جدول. یک شاخص امکان بازیابی سریع‌تر ردیف‌هایی را که دارای یک شاخص خاص هستند از جدول فراهم می‌کند، در مقایسه با زمانی که ردیف‌های جدول به صورت متوالی اسکن شوند. کلید اصلی همیشه بخشی از شاخص‌ها است، اما یک شاخص لزوماً کلید اصلی نیست؛
  • در [11]، ایندکس کلید اصلی است؛
  • در [12]، ایندکس شامل ستون‌های [nom, prenom] در هر ردیف است؛

اکنون، بیایید ببینیم اگر به ترتیب در نام پایگاه داده، نام کاربری و رمز عبور خطا وارد کنیم چه اتفاقی می‌افتد:

اگر نام پایگاه داده را اشتباه وارد کنیم:


Erreur : SQLSTATE[HY000] [1044] Access denied for user 'admpersonnes'@'%' to database 'dbpersonnes2'

اگر نام کاربری غیرموجود وارد کنیم:


Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes2'@'localhost' (using password: YES)

اگر رمز عبور نادرست وارد کنیم:


Erreur : SQLSTATE[HY000] [1045] Access denied for user 'admpersonnes'@'localhost' (using password: YES)

12.4. پر کردن یک جدول

ما قصد داریم یک اسکریپت PHP بنویسیم که دستورات SQL موجود در فایل متنی زیر [creation.txt] را اجرا کند:

drop table if exists personnes
SET NAMES 'utf8'
create table personnes (prenom varchar(30) not null, nom varchar(30) not null, age integer not null, primary key (nom,prenom))
insert into personnes (prenom, nom, age) values('Paul','Langevin',48)
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70)
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70)
insert into personnes (prenom, nom, age) values ('Pierre','Nicazou',35)
insert into personnes (prenom, nom, age) values ('Géraldine','Colou',26)
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56)
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56)

توضیحات

  • زبان SQL (زبان پرس‌وجوی ساخت‌یافته) برای دستورات SQL به بزرگی و کوچکی حروف (حروف بزرگ، حروف کوچک) حساس نیست؛
  • خط ۱: جدول [personnes] در صورت وجود حذف می‌شود؛
  • خط ۲: به سرور دستور داده می‌شود که کاراکترهای رمزگذاری‌شده در UTF-8 برای آن ارسال شوند. این دستور SQL، که مختص MySQL است، برای مثال در اینجا ضروری است تا اطمینان حاصل شود که خط ۷ – حرف «é» در Géraldine – در پایگاه داده گنجانده شود. اگر خط ۲ حذف شود، حرف «é» به دنباله‌ای از دو کاراکتر عجیب تبدیل خواهد شد. کلاینت، اسکریپت PHP است که در NetBeans نوشته شده است. این اسکریپت فایل‌ها را به صورت زیر به UTF-8 و [1-4] رمزگذاری می‌کند:

Image

  • خط ۳: ایجاد جدول [personnes] با سه ستون (نام، نام خانوادگی، سن) و کلید اصلی (نام خانوادگی، نام)؛
  • رده‌های ۴–۱۰: درج ۷ ردیف در جدول [personnes];
  • خط ۶: این دستور درج باید ناموفق باشد زیرا همان درج را مانند خط ۵ انجام می‌دهد. قید کلید اصلی باید مانع این درج شود: نمی‌توان دو نفر با نام و نام خانوادگی یکسان وجود داشته باشند؛
  • خط ۱۰: این عبارت درج باید ناموفق باشد زیرا تلاش می‌کند همان درج را مانند خط ۹ انجام دهد؛

اسکریپت PHP که مسئول اجرای دستورات SQL در این فایل متنی است، به شرح زیر است: [mysql-03.php]:


<?php

//
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// اطلاعات احراز هویت کاربر
const ID = "admpersonnes";
const PWD = "nobody";
//شناسهٔ فایل متنی حاوی دستورات SQL برای اجرا
const SQL_COMMANDS_FILENAME = "creation.txt";

// باز کردن یک اتصال به پایگاه داده MySql
try {
  $connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
  // نمایش خطا
  print "Erreur : " . $ex->getMessage() . "\n";
  exit;
}
// ما می‌خواهیم برای هر خطا در SGBD یک استثنا پرتاب شود
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//اجرای فایل فرمان SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// بستن اتصال
$connexion = NULL;
//نمایش تعداد خطاها
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
  print "$erreurs[$i]\n";
}

// انجام شد
print "Terminé\n";
exit;

// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE, bool $arrêt = TRUE): array {
//از اتصال $connexion استفاده می‌کند
//دستورات SQL را که در فایل متنی SQLFileName قرار دارند، اجرا می‌کند
//این فایل یک فایل فرمان SQL است که با نرخ یک فرمان در هر خط اجرا می‌شود
//اگر $suivi=1 باشد، هر بار که یک فرمان SQL اجرا می‌شود، پیامی نمایش داده می‌شود که نشان می‌دهد آیا موفق بوده یا ناموفق
//اگر $arrêt=1 باشد، تابع در اولین خطای یافت‌شده متوقف می‌شود؛ در غیر این صورت، تمام دستورات SQL را اجرا می‌کند
//تابع یک آرایه بازمی‌گرداند (تعداد خطاها، خطا۱، خطا۲…)
//بررسی می‌کند که آیا فایل SQLFileName وجود دارد یا خیر

  if (!file_exists($SQLFileName)) {
    return ["Le fichier [$SQLFileName] n'existe pas"];
  }

  //پرس‌وجوهای SQL موجود در SQLFileName را اجرا می‌کند
  // آنها را در یک آرایه قرار دهید
  $requêtes = file($SQLFileName);
  //خطا؟
  if ($requêtes === FALSE) {
    return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
  }
  // اجرای پرس‌وجوها به‌صورت یکی‌یکی – در ابتدا هیچ خطایی وجود ندارد
  $erreurs = [];
  $i = 0;
  $fini = FALSE;
  while ($i < count($requêtes) && !$fini) {
    //متن پرس‌وجو را بازیابی می‌کند
    // trim فاصله خطوط را حذف می‌کند
    $requête = trim($requêtes[$i]);
    // پرس‌وجوی خالی؟
    if (strlen($requête) == 0) {
      // پرس‌وجو نادیده گرفته می‌شود و به پرس‌وجوی بعدی می‌رویم
      $i++;
      continue;
    }
    try {
      // اجرای پرس‌وجو – ممکن است یک استثنا پرتاب شود
      $connexion->exec($requête);
      // نظارت بر صفحه نمایش یا خیر؟
      if ($suivi) {
        print "$requête : Exécution réussie\n";
      }
    } catch (PDOException $ex) {
      //خطایی رخ داده است
      addError($erreurs, $requête, $ex->getMessage(), $suivi);
      // آیا باید متوقف شویم؟
      $fini = $arrêt;
    }
    // پرس‌وجوی بعدی
    $i++;
  }
  // نتیجه
  return $erreurs;
}

function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
  // افزودن پیام خطا
  $msg = "$requête : Erreur (" . $msg . ")";
  $erreurs[] = $msg;
  // روی صفحه نمایش داده شود یا نه؟
  if ($suivi) {
    print "$msg\n";
  }
}

توضیحات

  • تابع [exécuterCommandes] (خطوط ۳۶–۸۹) مسئول اجرای دستورات SQL است که در فایل متنی [$SQLFileName] (پارامتر ۲) می‌یابد. برای اجرای آن‌ها، از اتصال باز [$connexion] (پارامتر ۱) با سرور MySQL استفاده می‌کند. پارامتر سوم، [$suiviیک مقدار بولی است که نمایش‌های صفحه را کنترل می‌کند: برای TRUE، فرمان اجرا شده SQL به همراه موفقیت یا شکست آن روی صفحه نمایش داده می‌شود؛ در غیر این صورت، اجرای فرمان SQL بی‌صدا است. پارامتر چهارم، [$arrêtکنترل می‌کند که وقتی یک فرمان SQL شکست می‌خورد چه اتفاقی باید بیفتد: در TRUE، مشخص می‌کند که اجرای دستورات SQL باید متوقف شود؛ در غیر این صورت، ادامه می‌یابد. تابع [exécuterCommandes] آرایه‌ای از پیام‌های خطا را برمی‌گرداند که اگر خطایی رخ نداده باشد، خالی است؛
  • خطوط ۱۱–۱۸: اتصال به پایگاه داده با استفاده از MySQL [dbpersonnes] باز می‌شود. اگر اتصال با شکست مواجه شود، یک پیام خطا نمایش داده می‌شود و برنامه متوقف می‌شود (خطوط ۱۴–۱۸);
  • خط ۲۲: بنابراین یک اتصال باز به تابع [exécuterCommandes] ارسال می‌شود. این اتصال هنگام بازگشت تابع بسته خواهد شد (خط ۲۴);
  • خط ۲۰: قبل از ارسال آن به تابع [exécuterCommandes]، اتصال پیکربندی می‌شود. در صورت بروز خطا، عملیات SQL که شامل یک شی [PDO] است، ممکن است یا مقدار بولی FALSE (مقدار پیش‌فرض) را بازگرداند یا یک استثنا پرتاب کند. خط ۲۰ مورد دوم را انتخاب می‌کند. در واقع، به راحتی می‌توان نتیجه بولی اجرای یک فرمان SQL را «فراموش» کرد. این امر در ادامه کد منجر به خطا خواهد شد و شناسایی منبع اصلی خطا را دشوارتر می‌کند. در صورت وقوع یک استثنای مدیریت‌نشده (جایی که catch وجود ندارد)، استثنا در کد به سمت بالا حرکت می‌کند تا اینکه به یک catch برخورد کند یا به تفسیرگر PHP برسد، که در این صورت استثنا را رهگیری خواهد کرد. در این حالت، ماهیت استثنا و نقطهٔ وقوع آن در کد نمایش داده می‌شود؛
  • خط ۲۲: تابع [exécuterCommandes] برای اجرای فایل فرمان SQL [$SQLFileName] فراخوانی می‌شود؛
  • خطوط ۴۵–۴۷: یک بررسی انجام می‌شود تا اطمینان حاصل شود که فایل فرمان SQL واقعاً وجود دارد. اگر این‌طور نباشد، خطا ثبت می‌شود و این نتیجه بازگردانده می‌شود؛
  • خط ۵۱: سفارش‌های SQL در یک آرایه [$requêtes] قرار می‌گیرند. خطوط ۵۳–۵۵: اگر عملیات با شکست مواجه شود، یک آرایه خطا حاوی یک پیام واحد بازگردانده می‌شود؛
  • خط ۵۷: ما خطاها را در آرایه [$erreurs] جمع‌آوری می‌کنیم؛
  • خط ۵۸: شماره درخواست؛
  • خط ۵۹: بولین [$fini] اجرای دستورات SQL در آرایه [$requêtes] را کنترل می‌کند. وقتی به TRUE می‌رسد، اجرا متوقف می‌شود؛
  • خط ۶۰: یک حلقه روی تمام پرس‌وجوها اجرا می‌شود؛
  • خط ۶۳: متن دستور SQL شماره i استخراج می‌شود. تابع [trim] فاصله‌های قبل و بعد از متن دستور SQL را حذف می‌کند. منظور از «فضاها» (spaces)، کاراکتر فضای سفید \b، بازگشت خط \r، ارسال خط \n، ارسال فرم \f، تب \t … است. نکته مهم در اینجا این است که ارسال خط در متن SQL حذف خواهد شد؛
  • خطوط ۶۵–۶۹: اگر متن SQL خالی باشد، درخواست نادیده گرفته می‌شود و به درخواست بعدی می‌رویم؛
  • خط ۷۲: فرمان SQL به سرور MySQL ارسال می‌شود. متد [PDO::exec] در صورت شکست اجرای برنامه، یک استثنا (exception) ایجاد خواهد کرد. توجه داشته باشید که این رفتار به دلیل پیکربندی مشخص‌شده در خط ۲۰ است؛
  • خط ۷۹: پیام خطا به آرایه خطا اضافه می‌شود؛
  • خط ۸۱: متغیر بولی [$fini] که کنترل حلقه را بر عهده دارد، مقداردهی می‌شود. اگر پارامتر [$arrêt] (خط ۳۶) روی TRUE تنظیم شود، حلقه باید متوقف شود؛
  • خطوط ۷۴–۷۶: اگر فرمان SQL با موفقیت اجرا شده باشد، در صورتی که پارامتر [$suivi] (خط ۳۶) روی TRUE تنظیم شده باشد، روی صفحه نمایش داده می‌شود؛
  • خط ۸۷: پس از اجرای تمام دستورات SQL، آرایه خطا [$erreurs] بازگردانده می‌شود؛

تابع [adError] در خطوط ۹۰–۹۷ امکان افزودن یک خطا به جدول خطا [$erreurs] را فراهم می‌کند:

  • خط ۹۰: این تابع ۴ پارامتر می‌گیرد:
    • پارامتر [$erreurs] به‌صورت مرجع ارسال می‌شود. این به این دلیل است که می‌خواهیم آرایه‌ای را که به‌عنوان پارامتر ارسال شده است، به‌جای کپی آن، تغییر دهیم؛
    • پارامتر [$requête] متنی است از SQL مربوط به سفارشی که با خطا مواجه شده است؛
    • پارامتر [$msg] پیام خطایی است که به سفارش ناموفق مربوط می‌شود؛
    • مقدار بولی [$suivi] نشان می‌دهد که آیا پیام خطا باید نمایش داده شود یا خیر ($suivi=TRUE) یا نه ($suivi=FALSE) روی کنسول؛

تابع [exécuterCommandes] در خطوط ۳–۳۳ توسط اسکریپت فراخوانی می‌شود:

  • خطوط ۱۱–۱۸: یک اتصال با پایگاه داده MySQL [dbpersonnes] برقرار می‌شود؛
  • خط ۲۰: اتصال پیکربندی می‌شود؛
  • خط ۲۲: سپس فایل فرمان SQL اجرا می‌شود؛
  • خط ۲۴: اتصال بسته می‌شود؛
  • خطوط ۲۶–۲۹: خطاهای بازگشتی تابع [exécuterCommandes] نمایش داده می‌شوند؛

خروجی صفحه:


drop table if exists personnes : Exécution réussie
SET NAMES 'utf8' : Exécution réussie
create table personnes (prenom varchar(30) not null, nom varchar(30) not null, age integer not null, primary key (nom,prenom)) : Exécution réussie
insert into personnes (prenom, nom, age) values('Paul','Langevin',48) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Lefur-Sylvie' for key 'PRIMARY')
insert into personnes (prenom, nom, age) values ('Pierre','Nicazou',35) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Géraldine','Colou',26) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Exécution réussie
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Girond-Paulette' for key 'PRIMARY')

-----------------------
Il y a eu 2 erreur(s)
insert into personnes (prenom, nom, age) values ('Sylvie','Lefur',70) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Lefur-Sylvie' for key 'PRIMARY')
insert into personnes (prenom, nom, age) values ('Paulette','Girond',56) : Erreur (SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry 'Girond-Paulette' for key 'PRIMARY')
Terminé

افزوده‌شده‌ها را می‌توان با استفاده از phpMyAdmin مشاهده کرد:

Image

12.5. اجرای هر یک از دستورات SQL

اسکریپت زیر اجرای دستورات SQL را از فایل متنی زیر، [sql.txt]، نشان می‌دهد:

1
2
3
4
5
6
7
8
9
select * from personnes
select nom,prenom from personnes order by nom asc, prenom desc
select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
insert into personnes values('Josette','Bruneau',46)
update personnes set age=47 where nom='Bruneau'
select * from personnes where nom='Bruneau'
delete from personnes where nom='Bruneau'
select * from personnes where nom='Bruneau'
xselect * from personnes where nom='Bruneau'

در میان این دستورات SQL، دستور SELECT که نتایج را از پایگاه داده بازمی‌گرداند؛ دستورات INSERT، UPDATE و DELETE که پایگاه داده را بدون بازگرداندن نتیجه تغییر می‌دهند؛ و در نهایت، دستورات نامعتبر مانند دستور آخر (xselect) وجود دارند. اسکریپت [mysql-04.php] به شرح زیر است:


<?php

//شناسهٔ پایگاه داده
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// اطلاعات هویتی کاربر
const ID = "admpersonnes";
const PWD = "nobody";
//شناسهٔ فایل متنی حاوی دستورات SQL برای اجرا
const SQL_COMMANDS_FILENAME = "sql.txt";

try {
  //اتصال به پایگاه داده MySql
  $connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
  //نمایش خطا
  print "Erreur : " . $ex->getMessage() . "\n";
  exit;
}
// ما می‌خواهیم برای هر خطا در SGBD یک استثنا پرتاب شود
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//اجرای فایل فرمان SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE, FALSE);
// بستن اتصال
$connexion = NULL;
//نمایش تعداد خطاها
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
  print "$erreurs[$i]\n";
}

// تمام شد
print "Terminé\n";
exit;

// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE, bool $arrêt = TRUE): array {
………………………………………………………….
  //اجرای پرس‌وجوها به‌صورت تک‌تک – در ابتدا بدون خطا
  $erreurs = [];
  $i = 0;
  $fini = FALSE;
  while ($i < count($requêtes) && !$fini) {
    //بازیابی متن پرس‌وجو
    // trim فاصله خطوط را حذف می‌کند
    $requête = trim($requêtes[$i]);
    // پرس‌وجوی خالی؟
    if (strlen($requête) == 0) {
      // پرس‌وجو نادیده گرفته می‌شود و به پرس‌وجوی بعدی می‌رویم
      $i++;
      continue;
    }
    // اجرای پرس‌وجو
    // استخراج نام آن
    $commande = "";
    if (preg_match("/^\s*(\S+)/", $requête, $champs)) {
      $commande = strtolower($champs[0]);
    }
    try {
      //آیا این یک فرمان SELECT است؟
      if ($commande === "select") {
        $résultat = $connexion->query($requête);
      } else {
        $résultat = $connexion->exec($requête);
      }
      // نظارت بر صفحه نمایش هست یا نه؟
      if ($suivi) {
        print "[$requête] : Exécution réussie\n";
      }
      // نمایش نتیجهٔ اجرا
      afficherInfos($commande, $résultat);
    } catch (PDOException $ex) {
      // یک خطا رخ داده است
      addError($erreurs, $requête, $ex->getMessage(), $suivi);
      // آیا باید متوقف شویم؟
      $fini = $arrêt;
    }
    // درخواست بعدی
    $i++;
  }
  // نتیجه
  return $erreurs;
}

function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
  
}

// ---------------------------------------------------------------------------------
function afficherInfos(string $commande, $résultat): void {
  //نتیجه پرس‌وجوی SQL را نمایش می‌دهد $résultat
  // آیا آن یک عبارت SELECT بود؟
  switch ($commande) {
    case "select" :
      //نام فیلدها نمایش داده می‌شوند
      $titre = "";
      $nbColonnes = $résultat->columnCount();
      for ($i = 0; $i < $nbColonnes; $i++) {
        $infos = $résultat->getColumnMeta($i);
        $titre .= $infos['name'] . ",";
      }
      // آخرین کاراکتر حذف می‌شود،
      $titre = substr($titre, 0, strlen($titre) - 1);
      //نمایش لیست فیلدها
      print "$titre\n";
      // خط جداکننده
      $séparateurs = "";
      for ($i = 0; $i < strlen($titre); $i++) {
        $séparateurs .= "-";
      }
      print "$séparateurs\n";
      // داده‌ها
      foreach ($résultat as $ligne) {
        $data = "";
        for ($i = 0; $i < $nbColonnes; $i++) {
          $data .= $ligne[$i] . ",";
        }
        // آخرین کاراکتر را حذف می‌کند،
        $data = substr($data, 0, strlen($data) - 1);
        // نمایش
        print "$data\n";
      }
      break;
    case "update":
    case "insert":
    case "delete";
      print " $résultat lignes(s) a (ont) été modifiée(s)\n";
      break;
  }
}

توضیحات

  • خطوط ۳۶–۸۳: تابع [exécuterCommandes] کمی تغییر کرده است: دستورات SQL و [select] به شیوه‌ای متفاوت از سایر دستورات SQL اجرا می‌شوند. این فرمان تنها فرمانی است که یک جدول را به عنوان نتیجه برمی‌گرداند، یعنی مجموعه‌ای از سطرها و ستون‌ها از پایگاه داده؛
  • خطوط ۵۵–۵۷: اولین کلمه دستور SQL با استفاده از یک عبارت منظم استخراج می‌شود؛
  • خطوط ۶۰–۶۴: اگر دستور SQL برابر با [select] باشد، از متد [PDO::query] استفاده می‌شود؛ در غیر این صورت، از متد [PDO::exec] برای اجرای دستور SQL استفاده می‌شود. در هر دو حالت، اگر اجرای دستور با شکست مواجه شود، یک استثنا پرتاب شده و در خطوط ۷۱–۷۷ گرفته می‌شود. اگر اجرای دستور با موفقیت انجام شود، خط ۷۰ نتیجه را نمایش می‌دهد؛
  • خطوط ۹۰–۱۳۰: تابع afficherInfos اطلاعاتی را در مورد نتیجه اجرای یک فرمان SQL نمایش می‌دهد؛
  • خط ۹۴: مورد [select] رسیدگی می‌شود. نتیجه آن یک شی از نوع [PDOStatement] است؛
  • خط ۹۶: متد [PDOStatement::getColumnCount()] تعداد ستون‌های جدول نتیجه select را برمی‌گرداند؛
  • خطوط ۹۸–۹۹: متد [PDOStatement::getMeta(i)] یک دیکشنری حاوی اطلاعات درباره ستون شماره i در جدول نتیجه select. را بازمی‌گرداند. در این دیکشنری، مقدار مرتبط با کلید 'name' نام ستون است؛
  • خطوط ۹۷–۱۰۲: نام ستون‌ها از جدول نتیجه select در یک رشته کاراکتری با هم متصل می‌شوند؛
  • خطوط ۱۰۵–۱۱۰: یک خط جداکننده به طول رشته کاراکتری ساخته‌شده قبلی ایجاد می‌شود؛
  • خطوط 112–121: یک شیء از نوع PDOStatement را می‌توان با استفاده از یک حلقه foreach به‌صورت تکراری پردازش کرد. در هر تکرار، نتیجه یک سطر از جدول نتایج select است که به شکل یک آرایه از مقادیری است که نمایانگر مقادیر ستون‌های مختلف آن سطر هستند. تمام این مقادیر با استفاده از یک حلقه for (خطوط ۱۱۴–۱۱۶) نمایش داده می‌شوند؛
  • خطوط ۱۲۳–۱۲۷: نتیجه اجرای دستورات insert، update، delete، تعداد خطوطی است که توسط این دستورات تغییر یافته‌اند؛

خروجی صفحه:


[set names 'utf8'] : Exécution réussie
[select * from personnes] : Exécution réussie
prenom,nom,age
--------------
Géraldine,Colou,26
Paulette,Girond,56
Paul,Langevin,48
Sylvie,Lefur,70
Pierre,Nicazou,35
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom,prenom
----------
Colou,Géraldine
Girond,Paulette
Langevin,Paul
Lefur,Sylvie
Nicazou,Pierre
[select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
prenom,nom,age
--------------
Pierre,Nicazou,35
Géraldine,Colou,26
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,47
[delete from personnes where nom='Bruneau'] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xselect * from personnes where nom='Bruneau'' at line 1)

-----------------------
Il y a eu 1 erreur(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xselect * from personnes where nom='Bruneau'' at line 1)
Terminé

12.6. استفاده از درخواست‌های از پیش آماده‌شده SQL

12.6.1. مثال ۱

بیایید اسکریپت زیر [mysql-05.php] را بررسی کنیم:


<?php

//شناسه پایگاه داده
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// اطلاعات کاربری
const ID = "admpersonnes";
const PWD = "nobody";

try {
  // اتصال به پایگاه داده MySql
  $connexion = new PDO(DSN, ID, PWD);
  // ما می‌خواهیم برای هر خطا در SGBD یک استثنا پرتاب شود
  $connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  // جدول «people» را پاک کنید
  $connexion->exec("delete from personnes");
  // فهرستی از افراد
  $personnes = [];
  $personnes[] = ["nom" => "Langevin", "prenom" => "Paul", "age" => 47];
  $personnes[] = ["nom" => "Lefur", "prenom" => "Sylvie", "age" => 28];
  // ما این افراد را به پایگاه داده اضافه خواهیم کرد
  $statement = $connexion->prepare("insert into personnes (nom, prenom, age) values (:nom, :prenom, :age)");
  for ($i = 0; $i < count($personnes); $i++) {
    $statement->execute($personnes[$i]);
  }
} catch (PDOException $ex) {
  //نمایش خطا
  print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// بستن اتصال
  $connexion = NULL;
}

// تمام
print "Terminé\n";
exit;

توضیحات

ما در اینجا به خطوط ۱۶–۲۴ علاقه‌مند هستیم که دو نفر را در جدول «people» پایگاه داده [dbpersonnes] وارد می‌کنند.

  • خط ۲۱: یک دستور SQL پیکربندی‌شده «آماده» می‌شود. پارامترها با کاراکتر : پیش‌فرض می‌شوند: :surname, :firstname, :age. برای «آماده‌سازی» یک دستور SQL، از متد [PDO::prepare] استفاده می‌شود. نتیجه از نوع [PDOStatement] است. «آماده‌سازی» یک اجرا نیست: هیچ چیزی اجرا نمی‌شود؛
  • خط ۲۳: اجرای دستور «آماده‌شده» با استفاده از متد [PDOStatement::execute]. برای این کار باید مقادیری به پارامترهای :surname، :first_name و :age اختصاص داده شود. راه‌های مختلفی برای انجام این کار وجود دارد. در اینجا، از یک دیکشنری استفاده می‌کنیم که کلیدهای آن پارامترهای دستور آماده‌شده هستند، و آن را به متد [PDOStatement::execute] پاس می‌دهیم. روش دیگر برای انجام این کار، تخصیص مقدار به پارامترها با استفاده از متد [PDOStatement::bindValue($paramètre,$valeur)] است. برای مثال، در اینجا:

$statement→bindValue(“nom”,”Langevin”);
$statement→bindValue(“prenom”,”Paul”);
$statement→bindValue(“age”,47);
$statement→execute();

نقطه ضعف این است که این دستور باید برای هر پارامتر تکرار شود. بنابراین، روش دیکشنری ممکن است راحت‌تر باشد. روش [PDOStatement::execute] در صورت عدم موفقیت، مقدار FALSE را برمی‌گرداند؛

  • روش مورد استفاده در اینجا برای انجام درج‌ها:
    • یک متد آماده؛
    • n اجرای عبارت آماده؛

از نظر زمان اجرا نسبت به اجرای n دستور مختلف SQL کارآمدتر است. بنابراین این روش ارجح است. این روش را می‌توان برای دستورات SQL SELECT، UPDATE، DELETE و INSERT استفاده کرد. در مورد دستورات SQL و select، پس از اجرای آنها با [PDOStatement::execute]، سطرهای نتیجه با استفاده از روش [PDOStatement::fetchAll] بازیابی می‌شوند؛

12.6.2. مثال ۲

اسکریپت زیر، [mysql-06.php استفاده از یک عبارت آماده (prepared statement) برای عملیات SELECT SQL و همچنین روش‌های مختلف بازیابی سطرهای بازگشتی این عملیات را نشان می‌دهد:


<?php

//شناسهٔ پایگاه داده
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// اطلاعات احراز هویت کاربر
const ID = "admpersonnes";
const PWD = "nobody";

try {
  // اتصال به پایگاه داده MySql
  $connexion = new PDO(DSN, ID, PWD);
  // ما می‌خواهیم هرگاه خطایی در SGBD رخ دهد، یک استثنا پرتاب شود.
  $connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  // جدول «people» را پاک می‌کنیم
  $connexion->exec("delete from personnes");
  // ما این افراد را در پایگاه داده وارد خواهیم کرد
  $statement = $connexion->prepare("insert into personnes (nom, prenom, age) values (:nom, :prenom, :age)");
  for ($i = 0; $i < 10; $i++) {
    $statement->execute(["nom" => "nom" . $i, "prenom" => "prenom" . $i, "age" => $i * 10]);
  }
  // ما از پایگاه داده پرس‌وجو می‌کنیم
  $statement = $connexion->prepare("select nom, prenom, age from personnes");
  $statement->execute();
  // ردیف اول
  $ligne = $statement->fetch();
  var_dump($ligne);
  // ردیف دوم
  $ligne = $statement->fetch(PDO::FETCH_ASSOC);
  var_dump($ligne);
  // ردیف سوم
  $ligne = $statement->fetch(PDO::FETCH_OBJ);
  var_dump($ligne);
  // خط چهارم
  $statement->setFetchMode(PDO::FETCH_CLASS, "Person");
  $ligne = $statement->fetch();
  var_dump($ligne);
  // خواندن متوالی تمام خطوط
  $statement = $connexion->prepare("select nom, prenom, age from personnes");
  $statement->execute();
  $statement->setFetchMode(PDO::FETCH_CLASS, "Person");
  while ($personne = $statement->fetch()) {
    print "$personne\n";
  }
} catch (PDOException $ex) {
  // نمایش خطا
  print "Erreur : " . $ex->getMessage() . "\n";
} finally {
// بستن اتصال
  $connexion = NULL;
}

// همین
print "Terminé\n";
exit;

class Person {
  private $nom;
  private $prenom;
  private $age;

  public function __toString() {
    return "Personne[$this->nom,$this->prenom,$this->age]";
  }

}

توضیحات

  • خطوط 17–20: 10 ردیف در جدول [personnes] در پایگاه داده [admpersonnes] درج می‌شوند:

Image

  • خط ۲۲: یک درخواست SQL [select] در خط ۲۳ «آماده» شده و اجرا می‌شود؛
  • خط ۲۵: روش [PDOStatement::fetch] برای بازیابی یک سطر از نتایج عملیات اجراشده SQL [select] استفاده می‌شود. روش [PDOStatement::fetch] می‌تواند سطرهای نتیجه را از عملیات آماده‌شده SQL [select] به روش‌های مختلف بازیابی کند. اسکریپت چند نمونه از این موارد را نشان می‌دهد. متد [PDOStatement::fetch] که هیچ پارامتری ندارد، سطر فعلی را از [select] به صورت یک دیکشنری بازگردانده که هم با شماره‌های ستون و هم با نام‌های ستون ایندکس شده است؛
  • خط ۲۶: نتیجه زیر را نمایش می‌دهد:

array(6) {
  ["nom"]=>
  string(4) "nom0"
  [0]=>
  string(4) "nom0"
  ["prenom"]=>
  string(7) "prenom0"
  [1]=>
  string(7) "prenom0"
  ["age"]=>
  string(1) "0"
  [2]=>
  string(1) "0"
}
  • خطوط ۲۸–۲۹: پارامتر [PDO::FETCH_ASSOC] تضمین می‌کند که سطر بازگشتی یک دیکشنری باشد که بر اساس نام ستون‌های جدول ایندکس شده است:
1
2
3
4
5
6
7
8
array(3) {
  ["nom"]=>
  string(4) "nom1"
  ["prenom"]=>
  string(7) "prenom1"
  ["age"]=>
  string(2) "10"
}
  • خطوط ۳۱–۳۲: پارامتر [PDO::FETCH_OBJ] تضمین می‌کند که سطر بازگشتی یک شیء از نوع [stdclass] باشد، که ویژگی‌های آن نام ستون‌های جدول هستند:
1
2
3
4
5
6
7
8
object(stdClass)#2 (3) {
  ["nom"]=>
  string(4) "nom2"
  ["prenom"]=>
  string(7) "prenom2"
  ["age"]=>
  string(2) "20"
}
  • خط ۳۴: حالت جستجو برای متد [fetch] با استفاده از متد [PDOStatement::setFetchMode] تنظیم می‌شود. این حالت سپس به حالت پیش‌فرض تبدیل می‌شود تا زمانی که یا با یک عملیات دیگر [PDOStatement::setFetchMode] یا با ارسال یک حالت به عنوان پارامتر به متد [PDOStatement::fetch] تغییر یابد، همانطور که قبلاً انجام شد. عملیات [setFetchMode(PDO::FETCH_CLASS, "Person")] مشخص می‌کند که سطر خوانده‌شده باید در یک شیء از نوع [Person] قرار گیرد. این کلاس باید در میان ویژگی‌های خود، ویژگی‌هایی را که نامشان برگرفته از ستون‌های خط در حال خواندن است، داشته باشد. این امر برای کلاس [Person] که در خطوط 56–63 تعریف شده است، صادق است؛
  • خط ۳۶ نتیجه زیر را نمایش می‌دهد:
1
2
3
4
5
6
7
8
object(Person)#4 (3) {
  ["nom":"Person":private]=>
  string(4) "nom3"
  ["prenom":"Person":private]=>
  string(7) "prenom3"
  ["age":"Person":private]=>
  string(2) "30"
}
  • خطوط ۳۸–۴۳: نحوه پردازش متوالی نتایج [select] را نشان می‌دهند؛
  • خط ۴۲: نمایش [$personne] از متد [__toString] کلاس [Person] استفاده خواهد کرد؛

12.7. استفاده از تراکنش‌ها

یک تراکنش اجازه می‌دهد یک دنباله از دستورات SQL در یک واحد اجرایی واحد گروه‌بندی شود: یا همه دستورات موفق می‌شوند، یا یکی از آنها شکست می‌خورد، که در این صورت همه دستورات قبلی SQL برگشت داده می‌شوند. به عبارت دیگر، هنگامی که از یک تراکنش برای اجرای دستورات SQL استفاده می‌شود، پس از اجرای تراکنش، پایگاه داده در یک وضعیت پایدار قرار دارد:

  • یا در یک وضعیت جدید که با اجرای موفقیت‌آمیز تمام دستورات SQL در تراکنش ایجاد شده است؛
  • یا در حالتی که قبل از شروع اجرای تراکنش در آن بود؛

ما به مثال اجرای دستورات SQL موجود در یک فایل متنی که در بخش «لینک» به آن اشاره شد، باز خواهیم گشت. این اجرا را در یک تراکنش قرار خواهیم داد. دستورات SQL در فایل [sql2.txt] زیر قرار خواهند داشت:


set names 'utf8'
select * from personnes
select nom,prenom from personnes order by nom asc, prenom desc
select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc
insert into personnes values('Josette','Bruneau',46)
update personnes set age=47 where nom='Bruneau'
select * from personnes where nom='Bruneau'
delete from personnes where nom='Bruneau'
select * from personnes where nom='Bruneau'
insert into personnes values('Josette','Bruneau',46)
select * from personnes where nom='Bruneau'
xselect * from personnes where nom='Bruneau'

دستور نادرست در خط ۱۲ باعث شکست کل تراکنش می‌شود. بنابراین، پایگاه داده باید به وضعیت خود پیش از تراکنش بازگردد. در مثال بالا، ردیفی که توسط خط ۱۰ وارد شده است، نباید در جدول قابل مشاهده باشد. اسکریپت تغییر چندانی نکرده است. با این حال، ما کد کامل [mysql-07.php] را در زیر بازتولید می‌کنیم:


<?php

//شناسهٔ پایگاه داده
const DSN = "mysql:host=localhost;dbname=dbpersonnes";
// اطلاعات احراز هویت کاربر
const ID = "admpersonnes";
const PWD = "nobody";
//شناسهٔ فایل متنی حاوی دستورات SQL برای اجرا
const SQL_COMMANDS_FILENAME = "sql2.txt";

try {
  //اتصال به پایگاه داده MySql
  $connexion = new PDO(DSN, ID, PWD);
} catch (PDOException $ex) {
  //نمایش خطا
  print "Erreur : " . $ex->getMessage() . "\n";
  exit;
}
// ما می‌خواهیم برای هر خطا در SGBD یک استثنا پرتاب شود
$connexion->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//اجرای فایل فرمان SQL
$erreurs = exécuterCommandes($connexion, SQL_COMMANDS_FILENAME, TRUE);
// بستن اتصال
$connexion = NULL;
//نمایش تعداد خطاها
printf("\n-----------------------\nIl y a eu %d erreur(s)\n", count($erreurs));
for ($i = 0; $i < count($erreurs); $i++) {
  print "$erreurs[$i]\n";
}

//کار تمام شد
print "Terminé\n";
exit;

// ---------------------------------------------------------------------------------
function exécuterCommandes(PDO $connexion, string $SQLFileName, bool $suivi = FALSE): array {
//از اتصال $connexion استفاده می‌کند
// دستورات SQL موجود در فایل متنی SQLFileName را اجرا می‌کند
//این فایل یک فایل فرمان است که حاوی دستورات SQL می‌باشد، هر دستور در یک خط
//دستورات SQL در داخل یک تراکنش اجرا می‌شوند
// اگر هر یک از دستورات با شکست مواجه شود، تراکنش لغو شده و پایگاه داده به وضعیت قبل از تراکنش بازگردانده می‌شود
//اگر $suivi=1 باشد، هر بار که یک دستور SQL اجرا می‌شود، پیامی نمایش داده می‌شود که نشان می‌دهد دستور موفق بوده یا ناموفق
//تابع یک آرایه را بازمی‌گرداند (تعداد خطاها، خطا۱، خطا۲…)
//
//سیستم وجود فایل SQLFileName را بررسی می‌کند
  if (!file_exists($SQLFileName)) {
    return ["Le fichier [$SQLFileName] n'existe pas"];
  }
  //پرس‌وجوهای SQL را که در SQLFileName موجود است اجرا می‌کند
  // آنها را در یک آرایه قرار دهید
  $requêtes = file($SQLFileName);
  //خطا؟
  if ($requêtes === FALSE) {
    return ["Erreur lors de l'exploitation du fichier SQL [$SQLFileName]"];
  }
  //پرس‌وجوها در یک تراکنش قرار داده خواهند شد
  $connexion->beginTransaction();
  //پرس‌وجوها یکی یکی اجرا می‌شوند – در ابتدا هیچ خطایی وجود ندارد
  $erreurs = [];
  $i = 0;
  $fini = FALSE;
  while ($i < count($requêtes) && !$fini) {
    // متن پرس‌وجو را بازیابی کنید
    // TRIM فاصله خطوط را حذف می‌کند
    $requête = trim($requêtes[$i]);
    // پرس‌وجوی خالی؟
    if (strlen($requête) == 0) {
      // پرس‌وجو نادیده گرفته می‌شود و به پرس‌وجوی بعدی می‌رویم
      $i++;
      continue;
    }
    // اجرای پرس‌وجو
    // استخراج نام آن
    $commande = "";
    if (preg_match("/^\s*(\S+)/", $requête, $champs)) {
      $commande = strtolower($champs[0]);
    }
    try {
      //آیا این یک فرمان SELECT است؟
      if ($commande === "select") {
        $résultat = $connexion->query($requête);
      } else {
        $résultat = $connexion->exec($requête);
      }
      // نظارت بر صفحه یا خیر؟
      if ($suivi) {
        print "[$requête] : Exécution réussie\n";
      }
      // نمایش نتیجه اجرای
      afficherInfos($commande, $résultat);
    } catch (PDOException $ex) {
      // یک خطا رخ داده است
      addError($erreurs, $requête, $ex->getMessage(), $suivi);
      //برنامه در تکرار بعدی متوقف می‌شود
      $fini = TRUE;
    }
    // درخواست بعدی
    $i++;
  }
  //پایان تراکنش
  if (!$fini) {
    // هیچ خطایی رخ نداده است: تراکنش ثبت شد
    $connexion->commit();
  } else {
    //خطاها رخ داده است: تراکنش لغو شد
    $connexion->rollBack();
    // افزودن خطا
    addError($erreurs, "", "Transaction annulée", $suivi);
  }
  // نتیجه
  return $erreurs;
}

function addError(array &$erreurs, string $requête, string $msg, bool $suivi): void {
  
}

// ---------------------------------------------------------------------------------
function afficherInfos(string $commande, $résultat): void {
  
}

توضیحات

ما تغییرات را در اسکریپت اصلی [mysql-04.php] برجسته کرده‌ایم.

  • خطوط ۲۲ و ۳۶: تابع [exécuterCommandes] پارامتر چهارم خود، [$arrêt=TRUE] را از دست داده است. این به این دلیل است که از آنجایی که دستورات SQL در داخل یک تراکنش اجرا می‌شوند، هرگونه خطا باعث پایان یافتن تراکنش می‌شود؛
  • خطوط ۴۰–۴۱: تابع از داخل یک تراکنش فراخوانی می‌شود؛
  • خط ۵۷: یک تراکنش آغاز می‌شود. از این نقطه به بعد، هر دستور SQL که در حلقه خطوط ۶۲–۹۹ اجرا شود، در همین تراکنش اجرا می‌شود؛
  • خطوط ۱۰۱–۱۰۹: متغیر بولی [$fini] در صورتی که خطایی رخ داده باشد، روی TRUE تنظیم می‌شود (خط ۹۵). وقتی مقدار آن روی FALSE تنظیم می‌شود، هیچ خطایی رخ نداده است و در نتیجه تراکنش نهایی می‌شود (خط ۱۰۳). وقتی مقدار روی TRUE تنظیم می‌شود، خطاهایی رخ داده است و در نتیجه تراکنش لغو می‌شود (خط ۱۰۶) و خطای تراکنش به فهرست خطاها اضافه می‌شود (خط ۱۰۸)؛

نتایج

قبل از اجرای اسکریپت، پایگاه داده [admpersonnes] در وضعیت زیر قرار دارد:

Image

اسکریپت [mysql-07.php] را اجرا کنید. خروجی‌های صفحه نمایش به شرح زیر است:


[set names 'utf8'] : Exécution réussie
[select * from personnes] : Exécution réussie
prenom,nom,age
--------------
prenom0,nom0,0
prenom1,nom1,10
prenom2,nom2,20
prenom3,nom3,30
prenom4,nom4,40
prenom5,nom5,50
prenom6,nom6,60
prenom7,nom7,70
prenom8,nom8,80
prenom9,nom9,90
[select nom,prenom from personnes order by nom asc, prenom desc] : Exécution réussie
nom,prenom
----------
nom0,prenom0
nom1,prenom1
nom2,prenom2
nom3,prenom3
nom4,prenom4
nom5,prenom5
nom6,prenom6
nom7,prenom7
nom8,prenom8
nom9,prenom9
[select * from personnes where age between 20 and 40 order by age desc, nom asc, prenom asc] : Exécution réussie
prenom,nom,age
--------------
prenom4,nom4,40
prenom3,nom3,30
prenom2,nom2,20
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[update personnes set age=47 where nom='Bruneau'] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,47
[delete from personnes where nom='Bruneau'] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
[insert into personnes values('Josette','Bruneau',46)] : Exécution réussie
 1 lignes(s) a (ont) été modifiée(s)
[select * from personnes where nom='Bruneau'] : Exécution réussie
prenom,nom,age
--------------
Josette,Bruneau,46
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xselect * from personnes where nom='Bruneau'' at line 1)
[] : Erreur (Transaction annulée)

-----------------------
Il y a eu 2 erreur(s)
[xselect * from personnes where nom='Bruneau'] : Erreur (SQLSTATE[42000]: Syntax error or access violation: 1064 You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'xselect * from personnes where nom='Bruneau'' at line 1)
[] : Erreur (Transaction annulée)
Terminé
  • خط ۵۳: خطایی در فرمان [xselect] رخ می‌دهد؛
  • خط ۵۴: تراکنش سپس لغو می‌شود؛

اگر وضعیت پایگاه داده را بررسی کنیم، می‌بینیم که در همان حالتی است که قبل از اجرای اسکریپت بود. به طور خاص، خط [Josette, Bruneau, 46] از خط ۵۲ نتایج بالا قابل مشاهده نیست.

Image

خلاصه

  • یک تراکنش با متد [PDO::beginTransaction] آغاز می‌شود؛
  • با استفاده از متد [PDO::commit] با موفقیت تکمیل می‌شود؛
  • در صورت خطا با استفاده از متد [PDO::rollback] خاتمه می‌یابد؛

هنگام کار با پایگاه داده، بهترین روش این است که هر عملیات SQL را در یک تراکنش قرار دهید تا آن را از سایر کاربران پایگاه داده ایزوله کنید (این یکی از وظایف آن نیز هست). یک تراکنش باید تا حد امکان کوتاه باشد. بنابراین نباید فراموش کنید که آن را با [commit] یا [rollback]، بسته به مورد، پایان دهید.