مقاله خود را جستجو کنید

در این بخش میتوانید مقاله خود به صورت حرفه ای جستجو نماید.

معرفی افزونه PDO برای دستیابی بهتر به دیتابیس

 تاریخ انتشار : 14 ژانویه 2014    دسته بندی : پی اچ پی
PDO یک افزونه سبک و قدرتمند PHP برای به دیتابیسه . از خصوصیات خوب این افزونه که از نسخه 5.1 روی PHP نصب شده موارد زیر است: پشتیبانی از دیتابیسهای متنوع از جمله MySQL، MsSQL، SQLite و غیره با توابع ثابت. امکان فوق العاده PDO که اجازه میده تا بدون تغییر کدها، دیتابیس رو تغییر بدیم. یعنی مثلا اگر Applicationای با MySQL ساخته باشیم و تحت شرایطی مجبور به تغییر دیتابیس به MsSQL باشیم، اگر سینتکس کوئری ها مشکلی ایجاد نکنه، کافیه دیتابیس رو تعویض کنیم و همین. برای سیستمهای بزرگ این یک مزیت خیلی مهمه. پشتیبانی از Exceptionهای PHP. Exceptionها امکان کنترل و بدست گیری خطاها رو به برنامه نویس میده. PDO امکان کنترل خطاهای دیتابیس رو هم بهمون میده. پشتیبانی از Prepared Statement و Stored Procedure ها و Multiple Recordset. این سه مورد هیچ کدوم توسط توابع کثافت MySQL اجرا نمیشند. PDO مورد آخر رو در حال حاضر برای MySQL پشتیبانی نمی کنه. طراحی شده به شکل کلاس. قابلیت مهمی که امکان گسترش و شخصی سازی PDO رو میده مثلا رفتار توابعش رو تغییر بدیم یا توابع جدیدی بهش اضافه کنیم و در واقع همه امکانات شی گرایی رو باهاش داشته باشیم. PDO از سه تا کلاس تشکیل شده: کلاس اصلی به نام PDO که حاوی توابع اصلی مثل اجرای کوئری و اتصال و غیره است. کلاس با نام PDOStatement حاوی توابع برای پردازش و بهره گیری از کوئری های اجرا شده است مثل fetch. کلاس PDOException برای بدست گیری خطاهای رخ داده. این موارد همه در ادامه، به همراه آموزش ابتدایی، بیشتر توضیح داده خواهند شد. نکته: کلاس حاوی تعدادی متد (Method) است که مانند تابع (Function) عمل میکنند. اطلاق عنوان تابع به یک متد از نظر تخصصی صحیح نیست ولی برای فهم روانتر مقاله از عبارت تابع بجای متد استفاده شده است. نحوه اتصال با توجه به اینکه PDO یک کلاس است اتصال به دیتابیس با نمونه گیری این کلاس شروع میشه:
$pdo = new PDO('mysql:dbname=mydatabase;host=localhost', 'db_username', 'db_password');
با اینکار ما به دیتابیس مورد نظرمون متصل شدیم. پارامتر اول فرمت خاصی داره. همونجور که میبینی در پارامتر اول، ما نوع دیتابیس که MySQL هست رو هم مشخص کردیم. برای قطع اتصال هم کافیه این شی رو از بین ببریم. اینجوری:
$pdo = null;
اجرای کوئری با سه نوع تابع میشه یک کوئری رو اجرا کرد. کوئری های آماده یا ساده توسط تابع PDO::query اجرا میشند و یک شی از کلاس PDOStatement برمیگردونند.
$stmt = $pdo->query("SELECT id, name, age FROM users");
متغیر stmt$ یک نمونه کلاس PDOStatement هست که توابع خودش رو داره. میخوایم این کوئری رو Fetch کنیم. برای اینکار چند مدل تابع وجود داره. تابع PDOStatement::fetch که در حالت پیشفرض مشابه تابع mysql_fetch_arrayعمل میکنه.
$row = $stmt->fetch();
echo "Name: ".$row['name'];
echo "Name: ".$row[1];
echo "Age: ".$row['age'];
echo "Age: ".$row[2];
توسط پارامتر میشه به این تابع فهموند که چه مدلی آرایه ای تولید کنه:
$assoc_row = $stmt->fetch(PDO::FETCH_ASSOC);
echo "Name: ".$assoc_row['name'];
echo "Age: ".$assoc_row['age'];

$num_row = $stmt->fetch(PDO::PDO::FETCH_NUM);
echo "Name: ".$num_row[1];
echo "Age: ".$num_row[2];

$obj_row = $stmt->fetch(PDO::PDO::FETCH_OBJ);
echo "Name: ".$obj_row->name;
echo "Age: ".$obj_row->age;
تابع PDOStatement::fetchAll مشابه تابع قبل عمل میکنه ولی یک آرایه برمیگردونه از سطرهای بدست اومده به اضافه اینکه حافظه ی اشغال شده برای کوئری (داخل PDO) رو هم آزاد میکنه.
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach($rows as $row) {
echo "Name: ".$row['name'];
echo "Age: ".$row['age'];
}
مشکلی که PDO داره اینه که نمیشه هنگام fetch شدن یک کوئری، کوئری دیگه ای رو fetch کرد! یعنی تابع fetch دوم کد زیر هیچی برنمیگردونه:
$select = $pdo->query("SELECT id,name FROM parents");
while($parent = $select->fetch(PDO::FETCH_ASSOC)) {
// ...
$stmt = $pdo->query("SELECT name FROM children WHERE parent=$parent[id]");
$child = $stmt->fetch(PDO::FETCH_ASSOC);
echo "Child is ".$child['name']; // Output: "Child is "
// ...
}
البته هیچ خطایی هم رخ نداده. PDO میگه: "مگه من چند تا دست دارم؟ موقع Fetch کردن یک کوئری یه کوئری دیگه رو هم Fetch کنم؟ نکنه فکر کردی من کامپیوترم؟"برای حل این مشکل چند تا راه داریم یکیش استفاده از تابع PDOStatement::fetchAll است:
$select = $pdo->query("SELECT id,name FROM parents");
$parents = $select->fetchAll(PDO::FETCH_ASSOC);
foreach($parents as $parent) {
// ...
$stmt = $pdo->query("SELECT name FROM children WHERE parent=$parent[id]");
$child = $stmt->fetch(PDO::FETCH_ASSOC);
echo "Child is ".$child['name']; // Output: "Child is baby"
// ...
}
PDOStatement::fetchAll توانایی های دیگه ای هم توی ساختن آرایه داره که مثلا کلید هر سطر فلان فیلدش باشه و غیره. بعد از fetch کردن بهتره شی PDOStatement ساخته شده رو پاک کنیم بعضی مواقع باید این کار رو بکنیم برای این کار، یکی از دو روش زیر رو انجام میدیم:
$stmt = null;
$stmt->closeCursor();
حالت اول کلا ریشه ی شی رو میکنه ولی حالت دوم فقط حافظه رو تخلیه میکنه و هنوز میشه از شی استفاده کرد. تابع PDOStatement::fetchColumn هم فقط مقدار یک ستون رو برمیگردونه. اگر هیچ پارامتری نگیره ستون اول، در غیر اینصورت ستون مشخص شده:
$select = $pdo->query("SELECT id,name FROM parents");
echo "ID: ".$select->fetchColumn();
echo "Name: ".$select->fetchColumn(1);
همونجور که میبینی شماره 1 یعنی ستون دوم یعنی شماره ستونها از صفر شروع میشه. کاربرد این تابع زمانی هست که کوئری رو فقط برای یک مقدار و ستون اجرا کردیم مثل این:
echo "Name: ".$pdo->query("SELECT name FROM users WHERE id=7")->fetchColumn();
توابع مهم دیگه شی PDOStatement تا اینجا تابع rowCount و nextRowset هستند.اولی مثل mysql_num_rows تعداد سطرهای بدست اومده رو میده و دومی وقتی بکار میاد که کوئری بجای یک دسته سطر، چند دسته سطر رو برگردونه. این حالت توی Stored Procedureها رخ میده که من بتونم با یک کوئری چند مدل RecordSet یا دسته سطر داشته باشم. این تابع در MySQL فعلا کار خاصی نمی کنه! حالت دوم اجرای کوئری ها، مواقعی است که مقدار برگشتی نداریم مثل عملیات INSERT یا DELETE و غیره. تابع PDO::exec مثل PDO::query ، کوئری اجرا میکنه ولی فقط تعداد سطرهایی رو برمیگردونه که تحت تاثیر قرار گرفته اند. حسن این تابع اینه که از تابع PDO::query سریعتره.
$num = $pdo->exec("UPDATE users SET level=1 WHERE age<19");
echo $num ." user(s) were changed";
پس برای مواقعی که نیاز به خروجی و fetch نداریم بهتره از این تابع استفاده کنیم. حالت سوم و مهم اجرای کوئری ها، استفاده از قابلیت Prepared Statement است. این قابلیت در دو مورد زیر کاراست: پردازش و تحلیل کوئری در دیتابیس. بررسی کوتیشن ها بصورت خودکار برای حل مسئله SQL Injection. زمانی که لازم داریم تا یک کوئری رو بر اساس مقادیری به دفعات اجرا کنیم، حالت عادی بهینه نیست. PDO امکان میده تا کوئری رو با پارامترهای مشخص ابتدا معرفی کنیم تا در دیتابیس پردازش بشه و بعد هر تعداد که میخوام اجراش کنیم. تابع PDO::prepare این کار رو میکنه و مثل PDO::query یک شی PDOStatement برمیگردونه که هنوز کامل اجرا نشده:
$stmt = $pdo->prepare("SELECT * FROM users WHERE id=:id");
اینجا من کوئری رو معرفی کردم که یک پارامتر داره که با ":" شروع شده (id:) و پردازش شده حالا باید اول پارامترش رو مقدار بدم و بعد اجراش کنم. برای تعیین پارامتر دو روش وجود داره. روش اول استفاده از توابع این کار هست. توابع PDOStatement:bindParam و PDOStatement::bindValue
$stmt = $pdo->prepare("SELECT age FROM users WHERE id=:id AND name=:name");
$id = 5;
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', 'Amir Hossein');
حالا این کوئری آماده اجراست. به این موضوع دقت کن من هیچ کوتیشنی برای مقدار name که String هست استفاده نکردم. این تابع خودش این کار رو کامل انجام میده. یعنی مثل قبل نیازی به توابعی مثل mysql_real_escape_string یا addslashes نداریم. حالا باید توسط تابع PDOStatement::execute این Statement رو اجرا کنیم تا بتونیم Fetchاش کنیم:
$stmt->execute();
echo "Age is ".$stmt->fetchColumn();
همونطور که گفتم خصوصیت مهم Prepared Statementها امکان اجرای کوئری ها تکراری با سرعت بالاست. مثل این:
$ids = array(1, 5, 4, 78, 9, 45, 6, 7);
$stmt = $pdo->prepare("SELECT name FROM users WHERE id=:id");
foreach($ids as $id) {
$stmt->bindValue(':id', $id);
$stmt->execute();
echo "ID: $id , Age: ".$stmt->fetchColumn();
}
اگر از این روش استفاده نکنیم باید در حلقه از تابع query استفاده کنیم که سرعت رو نسبت به این روش پایین میاره. تابع PDOStatement::bindParam مثل PDOStatement::bindValue عمل میکنه با این تفاوت باحال که مقدار رو از متغیرهایی میگیره که ممکنه هنوز وجود نداشته باشند. به محض اجرای Statement اون متغیرها چک میشند:
$stmt = $pdo->prepare("SELECT age FROM users WHERE id=:id AND name=:name");
$stmt->bindParam(':id', $id);
$stmt->bindParam(':name', $name);
$id = 5;
$name = 'Amir Hossein';
$stmt->execute();
echo "Age is ".$stmt->fetchColumn();
باحالی این تابع در مثال زیر قابل درکه:
$select = $pdo->query("SELECT id,name FROM parents");
$parents = $select->fetchAll(PDO::FETCH_ASSOC);
$stmt = $pdo->prepare("SELECT name FROM children WHERE parent=:parent");
$stmt->bindParam(':parent', $parent_id);
foreach($parents as $parent) {
// ...
$parent_id = $parent['id'];
$stmt->execute();
$child = $stmt->fetch(PDO::FETCH_ASSOC);
echo "Child is ".$child['name'];
// ...
}
یعنی حتی عملیات bind هم یکبار اجرا شده و داخل حلقه تکرار نشد! شکل دیگه ی پارامتر دادن به کوئری استفاده از علامت سوال هست مثل زیر:
$stmt = $pdo->prepare("SELECT age FROM users WHERE id=? AND name=?");
که توابع bind بجای گرفتن نام از شماره استفاده میکنند:
$stmt->bindValue(1, 5);
$stmt->bindValue(2, 'Amir Hossein');
$stmt->execute();
پارامتر سوم توابع bind هم یک ثابت برای تعیین نوع پارامتر هست که PDO مقدار رو اصلاح کنه. مثلا اگر مقدار رو عدد تعیین کنیم، PDO مقدار ما رو به عدد تبدیل میکنه:
$stmt->bindValue(':id', $_POST['id'], PDO::PARAM_INT);
روش دیگه ی bind کردن استفاده از خود تابع PDOStatement::execute هست. این تابع یک پارامتر اختیاری میگیره که یک آرایه از مقادیر هست:
$stmt = $pdo->prepare("SELECT age FROM users WHERE id=:id AND name=:name");
$stmt->execute(
array(
':id' => 5,
':name' => 'Amir Hossein'
)
);
$stmt = $pdo->prepare("SELECT age FROM users WHERE id=? AND name=?");
$stmt->execute(
array(5, 'Amir Hossein')
);
نکته مهم در تعیین پارامترها با نامشون هست. PDO وقتی نام پارامتر رو از ما میگیره انقدر باهوش نیست که توی کوئری بره پیداش کنه، مقدار رو با اون عبارت توی کوئری عوض میکنه! به مثال زیر دقت کن:
$stmt = $pdo->prepare("SELECT MAX(age) FROM users WHERE age>(age AND name='Ali'");
$stmt->bindValue("(age", 15);
ما اینجا اومدیم از خودمون روش اختراع کردیم. در حالیکه بعد از تابع MAX هم این پارامتر توسط PDO تشخیص داده میشه! با این کار کوئری به این شکل درمیاد
SELECT MAX'15') FROM users WHERE age>'15' AND name='Ali'
که عبارت "('MAX'15" خطا ایجاد میکنه. شاید بگی من که از خودم روش اختراع نمی کنم .پس نمونه زیر رو ببین:
$stmt = $pdo->prepare("
SELECT id FROM users WHERE name=:name AND father_name=:name_father
");
$stmt->bindValue(":name", 'Taghi');
$stmt->bindValue(":name_father", 'Naghi');
با این کار کوئری این شکلی میشه:
SELECT id FROM users WHERE name='Taghi' AND father_name='Taghi'_father
یعنی bindValue اومد هرچی name: توی کوئری پیدا کرد رو تعویض کرد. پس در دو حالت از Prepared Statementها استفاده می کنیم. یا بخوایم مقادیر رو Escape کنیم (همون مسئله کوتیشن ها) یا بخوایم کوئری خاصی رو تکرار کنیم. مثالی هم از Stored Procedureها بزنم که تفاوتی با کوئری های معمولی نداره:
$select = $pdo->query("CALL mySelectProcedure(5, 'salam');");
$rows = $select->fetchAll();
$pdo->prepare("CALL myUpdateProcedure(?);")->execute(array($_GET['id']));
همونطور که گفتم PDO کلاس سومی با نام PDOException هم داره که خود کلاس Exception در PHP رو استفاده میکنه و همه توابع و مشخصات اون رو داره. نکته هیجان انگیز PDO اینه که کلاسه! اگر این مقاله رو از اول خونده باشی، متوجه کابوس استفاده از تابع PDOStatement:fetch شدی که برای گرفتن آرایه Association هی باید پارامتر PDO::FETCH_ASSOC رو بدیم. این موضوع اینقدر دردسر شده که توی نسخه های جدید یک ثابت براش تعیین کردند که یکبار مشخص بشه ولی در حالت عادی آیا میشه کاری کرد که فقط یکبار چنین چیزی رو مشخص کنیم؟ راه حل اینه که یک کلاس تعریف کنیم که PDOStatement رو extends کنه و مقدار پیشفرض این تابع رو توش تغییر بدیم. شبیه زیر:
class myPDO extends PDO {
public function __construct($dsn,
$username=null, $password=null, $options=array()) {
parent::__construct($dsn,$username,$password,$options);
$this->setAttribute(PDO::ATTR_STATEMENT_CLASS,
array('myPDOStatement', array($this)));
}
}
class myPDOStatement extends PDOStatement {
public function fetch($fetch_style=PDO::FETCH_ASSOC,
$cursor_orientation=PDO::FETCH_ORI_NEXT, $cursor_offset=0) {
return parent::fetch($fetch_style, $cursor_orientation, $cursor_offset);
}

$pdo = new myPDO('mysql:dbname:mydb;host=localhost', 'username', 'password');
$select = $pdo->prepare("SELECT * FROM users WHERE id=?");
$select->execute(array($_GET['user']));
$row = $select->fetch();
echo "Name: ".$row['name'];
$select = null;
این یک مثال خیلی خیلی ساده از قابلیت توسعه کلاس پرکاربردی مثل PDO بود. چند تا تابع مهم دیگه هم معرفی کنم: توابع PDO::beginTransaction و PDO::commit و PDO::rollback برای سادگی کار با Transaction ها هستند. تابع PDO::quote کوتیشن برای پارامترش میذاره ولی یه جور ناجور! این تابع خودش مقدار رو داخل کوتیشن میذاره.
$name = $pdo->quote($_POST['name']);
$select = $pdo->exec("DELETE FROM users WHERE name=$name");
(من متغیر name$ رو توی کوئری داخل کوتیشن نذاشتم!) تابع مفید دیگه PDO::errorInfo و یا PDOStatement::errorInfo هست که برای دیباگ کردن کدها خیلی خوب و مفید هستند. تابع PDO::lastInsertId که کار mysql_insert_id رو میکنه. من یه جایی با این تابع به مشکل خوردم که مقدار صحیح برنگردوند! نهایتا مجبور شدم بجای این تابع از کد زیر استفاده کنیم:
$id = $pdo->query("SELECT LAST_INSERT_ID()")->fetchColumn();
PDO کلی تابع و Constant و جزییات دیگه داره که من واردش نشدم. اگر علاقمندی خودت از سایت PHP.net میتونی مطالعه کنی: PHP.net :: PDO

یک پاسخ به “معرفی افزونه PDO برای دستیابی بهتر به دیتابیس”

  1. zanyar گفت:

    با سلام لطفا خواهشا بفرمایید مشکل کد زیر چیه؟

    $id=intval($_GET[‘edit’]);
    if ( isset($_POST[‘edit’]) )
    {
    $dbh = new PDO(“mysql:host=localhost;dbname=zeryan; charset=UTF8″,’zeryan’, ‘zeryan’);
    $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $dbh->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
    $sql = “UPDATE posts SET title = :title,
    content = :content,
    fullcontent = :fullcontent,
    tags = :tags,

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *