در این درس قصد داریم مبحث PDO را برای آن دسته از عزیزانی که تمایل دارند یک کد واحد برای تمام دیتابیس ها ایجاد کنند، تشریح کنیم.
در پاسخ به این سوال که PDO چیست باید نوشت:
PDO مخفف (PHP Data Object) ابزار و یا یک افزونه (Extention) برای PHP ورژن ۵ به بالاست که به شما اجازه می دهد یک کد واحد برای تمام دیتابیس ها ایجاد کنید.
یعنی چه؟ یعنی شما با استفاده از این ابزار می توانید بگونه ای دیتابیس را طراحی کنید که هر لحظه دوست داشتید نوع آن را تغییر دهید.
مثلا سایت شما در حال حاضر با دیتابیس MySQL طراحی شده و به یکباره و بنا به دلایل مختلف می خواهید این پایگاه داده را به نوع SQL Server تغییر دهید. در صورتیکه از افزونه PDO برای کدنویسی خودتان استفاده نکرده باشید باید تمام کدهای مربوط به پایگاه داده و بعضا کوئری ها را تغییر دهید. ولی اگر کدهای شما با ابزار PDO طراحی شده باشد می توانید به راحتی هرچه تمام تر به و تنها با اعمال چندین دستور ساده پایگاه داده خود را نوع دلخواه تغییر دهید. انواع مختلف دیتابیسی که این افزونه پشتیبانی میکند عبارت است از:
همچنین معادل تمام این دیتابیس ها یک سری درایورها یا راهاندازها وجود دارند که باید روی سرور شما نصب شوند. (در ادامه نام هر دیتابیس، نام درایور آن نیز نوشته شده است)
توجه داشته باشید که نصب تمام این درایورها روی سیستم شما ضروری نیست برای اینکه بدانید چه درایوری در سیستم شما نصب شده است می توانید از دستور زیر استفاده کنید:
print_r(PDO::getAvailableDrivers());
همانطور که در جریان هستید هر پایگاه داده یک روش برای اتصال دارد. اما با استفاده از PDO میتوان یک کد واحد برای اتصال به تمام پایگاههای داده ایجاد کرد که زیر مشاهده میکنید:
$DBH = new PDO("mysql:host=$host;dbname:$dbname", $user, $pass);
متغیر DBH$ به عنوان یک متغیر برای ذخیرهی اطلاعات دیتابیس و کنترل کردن آنها به حساب میآید که همواره نام آن میتواند ثابت باشد.
عبارت mysql نوع پایگاه داده را مشخص کرده است که برای هر پایگاه داده، منحصر به فرد است. مثلا sqlserver, oracle و ...
دستورهای host = $host; dbname = $dbname, $user, $pass به عنوان یک رشته برای اتصال به پایگاه داده استفاده میشوند.
در ادامه چند مثال از پایگاه دادههای مختلف خدمت شما عزیزان ارائه میدهیم:
try { # MS SQL Server and Sybase with PDO_DBLIB $DBH = new PDO("mssql:host=$host;dbname=$dbname, $user, $pass"); $DBH = new PDO("sybase:host=$host;dbname=$dbname, $user, $pass"); # MySQL with PDO_MYSQL $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass); # SQLite Database $DBH = new PDO("sqlite:my/database/path/database.db"); } catch(PDOException $e) { echo $e->getMessage(); }
همچنین برای قطع اتصال با پایگاه داده میتوان از دستور زیر استفاده کرد:
# close the connection $DBH = null;
برای کنترل خطاها در PDO افزونههایی در اختیار شما قرار میگیرد که با استفاده از آن میتوانید وضعیت نمایش خطاها را مشخص کنید. معمولا این خطاها زمانیکه از بلاک try/catch استفاده میشود، در بخش catch نمایش داده خواهند شد. در حالت کلی ۳ نوع مود خطا داریم که با استفاده از عبارت ATTR_ERRMODE تنظیم میشوند:
$DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING ); $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );
همواره به عنوان خطای پیشفرض میباشد. اگر شما تنظیمات را روی این نوع خطا قرار دهید تنها خطاهایی که در افزونههای MYSQL یا MYSQLI میباشد را در اختیار دارید. دو راه دیگر برای برنامهنویسی بدون تکرار یا DRY Programming وجود دارد که بهتر است از آنها استفاده شود:
این مود هشدارها و اخطارهای PHP را نمایش میدهد و برنامه پس از دریافت هشدار به اجرا شدن خود ادامه میدهد. این حالت برای خطایابی مورد استفاده قرار میگیرد.
این مود در بیشتر مواقع مورد استفاده قرار میگیرد زیرا با اعمال آن یک exception یا استثناء مشخص میدهد که میتوان از طریق آن خطاها را بررسی کرده و دادههایی که برای تخریب سایت شما ممکن است خطرناک باشند را پنهان میکند. در ادامه یک مثال از این سیستم تنظیم خطا ارائه میدهیم:
# connect to the database try { $DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass); $DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); # UH-OH! Typed DELECT instead of SELECT! $DBH->prepare('DELECT name FROM people'); } catch(PDOException $e) { echo "I'm sorry, Dave. I'm afraid I can't do that."; file_put_contents('PDOErrors.txt', $e->getMessage(), FILE_APPEND); }
هماینطور که ملاحظه میکنید در صورت وجود خطا از طریق PDOException متن آن خطا نمایش داده خواهد شد.
بدیهیست که ایجاد و یا آپدیت داده در دیتابیسها هموراه به عنوان یکی از مهمترین اصول میشاد. با استفاده از PDO این عملیات در طی دو مرحله پردازش و ارسال میشود.
همانطور که در تصویر فوق مشاهده میکنید با اعمال هر دستور PDO در تابع و متد prepare ابتدا پردازش انجام میشود و سپس اطلاعات بایند شده به مرحله اجرا در میآید.
به این مثال توجه کنید:
# STH means "Statement Handle" $STH = $DBH->prepare("INSERT INTO folks ( first_name ) values ( 'Cathy' )"); $STH->execute();
همانطور که ملاحظه میکنید دستورهای مربوط به ایجاد یک رکورد به متد prepare ارسال شده است و آن را درون متغیر STH قرار دادهایم. سپس با اعمال تابع execute عملیات پزدارش شروع میشود.
استفاده از جملات و عبارات تنظیم شده یا Prepared به شما کمک میکند تا وب سایت و پایگاه داده شما از حملات SQL Injection یا تزریق SQL در امان باشد. برای آشنایی کامل از SQL Injection حتما مقالهی زیر را مطالعه بفرمایید:
حال که با مفهوم دقیق SQL Injection آشنا شدید راههای مقابله با آن را نیز باید بیاموزید.
برای مقابله با این نوع حملات باید ورودیهای پایگاه داده را پارامتری کنیم. یعنی اطلاعات را در غالب متغیرها و پارامترها به کوئری ارسال کرده و از قرارگیری مستقیم عبارات در کوئری ها خودداری کنیم. پارامتری کردن دادههای دریافتی از طریق یک نگهدارندهی داده یا Placeholder اتفاق میافتد و هنگامیکه شما عبارت prepared را بکار میبرید در واقع دارید این placeholder را به دستورات SQL خود اضافه میکنید! در ادامه با سه مثال از دستور بدون placeholder، دستور همراه با placeholder نامگذاری نشده و دستور همراه با placeholder نامگذاری شده، این مفهوم را برای شما شفافسازی میکنیم:
//بدون استفاده از placeholder // افزایش خطر حملات تزریق SQL $STH = $DBH->("INSERT INTO folks (name, addr, city) values ($name, $addr, $city)"); // استفاده از placeholder // بدون نام # unnamed placeholders $STH = $DBH->("INSERT INTO folks (name, addr, city) values (?, ?, ?); // استفاده از placeholder // به همراه نام متغیر # named placeholders $STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)");
همواره باید از اجرای متد ۱ خودداری کنید چون قطعا وب سایت شما در معرض حملات SQL خواهد بود. اما استفاده از هر یک از دو عبارت بدون نام یا همراه با نام متغیر مشکلی ندارد. بنابراین به توضیح متدهای ۲ و ۳ میپردازیم:
# assign variables to each place holder, indexed 1-3 $STH->bindParam(1, $name); $STH->bindParam(2, $addr); $STH->bindParam(3, $city); # insert one row $name = "Daniel" $addr = "1 Wicked Way"; $city = "Arlington Heights"; $STH->execute(); # insert another row with different values $name = "Steve" $addr = "5 Circle Drive"; $city = "Schaumburg"; $STH->execute();
این روش دو مرحله دارد. مرحله اول در خطوط ۲ تا ۴ رخ میدهد و شامل متغیرهاییست که تعریف کرده و آن را به مقادیر placeholder به ترتیب ارجاع دادهایم. مرحله دوم ارسال مجموعهی دادهها به این متغیرها و در نتیجه جایگذاری آنها در placeholder است. بنابراین برای هر اجرای مجدد تنها کافیست مقادیر را تغییر دهیم.
آیا این شکل نمایش زمانیکه پارامترهای شما در دیتابیس زیاد باشند کمی بینظم و گمراه کننده نیست؟
قطعا همینطوره! بنابراین بهتره دستورات را به صورت آرایه ای از مقادیر به ورودی تابع execute ارسال کرده تا دقیقا مقادیر متناسب با ایندکس ۰ و ۱ و ۲ در دستور SQL به جای علامت سوال اول دوم و سوم جایگزین شود:
# the data we want to insert $data = array('Cathy', '9 Dark and Twisty Road', 'Cardiff'); $STH = $DBH->("INSERT INTO folks (name, addr, city) values (?, ?, ?); $STH->execute($data);
این شیوه نمایش بهتر شد همچنین شما میتوانید با استفاده از دستور data[1]$ به دادهی دوم دست پیدا کنید.
روش دیگری برای ارسال اطلاعات وجود دارد که کدنویسی شما را تمیز تر میکند. نام این روش نگهدارندهی با نام است:
# the first argument is the named placeholder name - notice named # placeholders always start with a colon. $STH->bindParam(':name', $name);
این نگهدارنده همواره به صورت یک اسم در اولین آرگومان تعریف میشود و برای تعریف آن حتما و حتما باید از یک علامت : استفاده کرد.
حال برای ارسال دادهها به یک دستور sql میتوان به سادگی هرچه تمام آرایهای از رشتهها را به همراه انتساب هر کلید که نمایانگر placeholder است، متغیرها را جایگزین کرد:
# the data we want to insert $data = array( 'name' => 'Cathy', 'addr' => '9 Dark and Twisty', 'city' => 'Cardiff' ); # the shortcut! $STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)"); $STH->execute($data);
یک روش دیگر که خیلی کلاسیک محسوب میشود استفاده از کلاسها و سازندهها برای تعریف فیلدهای یک جدول میباشد که به صورت زیر خواهد بود:
# a simple object class person { public $name; public $addr; public $city; function __construct($n,$a,$c) { $this->name = $n; $this->addr = $a; $this->city = $c; } # etc ... } $cathy = new person('Cathy','9 Dark and Twisty','Cardiff'); # here's the fun part: $STH = $DBH->("INSERT INTO folks (name, addr, city) value (:name, :addr, :city)"); $STH->execute((array)$cathy);
در این حالت با ساختن یک شیء جدید از کلاس میتوان مقادیر را به سازنده ارسال کرده و در query ذخیره کنیم.
همانطور که مطلع هستید، دادهها توسط متد ()fetch<- بازگردانی خواهند شد. اما قبل از فراخوانی تابع fetch بهتر است با انواع تنظیمات این متد جهت بازیابی اطلاعات در PDO آشنا شوید.
PDO:FETCH_ASSOC : این گزینه آرایهی ایندکس شده توسط نام ستون را باز میگرداند.
PDO::FETCH_BOTH : این گزینه آرایهای ایندکس شده توسط ستون و اعداد آن را باز میگرداند. و به صورت پیشفرض فعال است.
PDO::FETCH_BOUND : این گزینه مقادیر ستونها را به متغیرهایی با استفاده از متد ()bindColumn نسبت میدهد.
PDO::FETCH_CLASS : این گزینه مقادیر ستونها را به ویژگیها و نام کلاس نسبت میدهد. با این گزینه اگر یک ویژگی وجود نداشته باشد آن را ایجاد میکند.
PDO::FETCH_INTO : این گزینه یک نمونه از کلاس ایجاد شده را آپدیت میکند.
PDO::FETCH_LAZY : این گزینه روشهای PDO::FETCH_BOTH و PDO::FETCH_OBJ را با هم ترکیب کرده و یک شیء متغیر ایجاد میکند.
PDO::FETCH_NUM : این گزینه آرایهی ایندکس شده توسط شماره ستون را باز میگرداند.
PDO::FETCH_OBJ : این گزینه یک شیء بینام به همراه ویژگیهای نامگذاری شده را باز میگرداند که با ستون موردنظر در ارتباط است.
در حالت کلی برای تنظیم این گزینهها از دستور زیر باید استفاده کنید:
$STH->setFetchMode(PDO::FETCH_ASSOC);
در ادامه به توضیح سه گزینه که پرکاربردترین هستند میپردازیم:
این گزینه آرایهای مجتمع را برای شما ایجاد میکند که توسط نام ستون آن ایندکس شده است:
# using the shortcut ->query() method here since there are no variable # values in the select statement. $STH = $DBH->query('SELECT name, addr, city from folks'); # setting the fetch mode $STH->setFetchMode(PDO::FETCH_ASSOC); while($row = $STH->fetch()) { echo $row['name'] . "\n"; echo $row['addr'] . "\n"; echo $row['city'] . "\n"; }
همانطور که ملاحظه میکنید ابتدا با استفاده از متد ()query دستور را ارسال کرده و مقادیر را درون متغیر STH ذخیره کردهایم. حال برای نمایش آنها از مود FETCH_ASSOC استفاده کرده و در نهایت درون حلقهی while برای نمایش هر یک از فلیدها درون یک سطر بهره بردهایم.
این نوع fetch یا بازیابی اطلاعات یک شیء از کلاس خالی یا std class برای هر سطر از اطلاعات بازیابی شده ایجاد میکند:
# creating the statement $STH = $DBH->query('SELECT name, addr, city from folks'); # setting the fetch mode $STH->setFetchMode(PDO::FETCH_OBJ); # showing the results while($row = $STH->fetch()) { echo $row->name . "\n"; echo $row->addr . "\n"; echo $row->city . "\n"; }
این روش به شما اجازه میدهد که اطلاعات و دادهها را به صورت مستقیم از کلاس انتخابی بازیابی کنید. هنگامیکه از FETCH_CLASS استفاده میشود ویژگیهای شیء شما قبل از اینکه سازنده فراخوانی شود، تنظیم (set) میشوند. این جمله را مجددا بخوانید چون بسیار مهم است. اگر یکی از ویژگیهایی با نام ستون برابر نباشد آن را از نو (به صورت public) ایجاد میکند. به مثال زیر توجه کنید:
class secret_person { public $name; public $addr; public $city; public $other_data; function __construct($other = '') { $this->address = preg_replace('/[a-z]/', 'x', $this->address); $this->other_data = $other; } }
حال میخواهم دیتاها را از این کلاس استخراج کنیم:
$STH = $DBH->query('SELECT name, addr, city from folks'); $STH->setFetchMode(PDO::FETCH_CLASS, 'secret_person'); while($obj = $STH->fetch()) { echo $obj->addr; }
در صورتیکه بخواهیم سازنده پیشفرض قبل از تنظیم شدن ویژگیها فراخوانی شود باید از دستور زیر استفاده کرد:
$stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'secret_person');
دوستان عزیز توجه داشته باشید که برای دستیابی به مثالها و نمونههای بیشتر متدهای FETCH در PDO به مقاله موجود در وب سایت مرجع PHP Manual مراجعه کنید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.