ارتباط با دیتابیس در PHP از طریق لایبرری PDO

اگر شما جزو آن دسته از برنامه نویسان PHP هستید که کماکان برای ارتباط با دیتابیس از دستور mysql_connect و نسخه پیشرفته آن mysqli_connect استفاده می کنید، این مقاله برای شما نوشته شده است. به عبارت دیگر، در این مقاله قصد داریم تا به معرفی روشی بپردازیم که نه تنها جدیدتر است بلکه ایمن تر بوده و دست شما را در ارتباط با دیتابیس به مراتب باز می‌گذارد. در ادامه با سکان آکادمی همراه باشید تا بیشتر با این API قدرتمند زبان برنامه نویسی PHP آشنای شویم.

به زبان ساده PDO مخفف واژگان PHP Data Object است و به منزله یکی از APIهای زبان برنامه نویسی پی اچ پی برای ارتباط با دیتابیس است. در‌ واقع روش سنتی mysql_connect کار ما را برای ارتباط با دیتابیس به خوبی انجام می‌دهد و این در حالی است که با استفاده از این متد به سادگی قادر خواهیم بود تا برای دیتابیسی همچون MySQL کوئری ارسال کرده و نتیجه را بگیریم اما توجه داشته باشیم که این روش دارای نقاط ضعف خود نیز هم هست که از آن جمله می‌توان به موارد زیر اشاره کرد: این روش اصطلاحاً Deprecated شده است. به عبارت دیگر استفاده از متد mysql_connect زمانش به سر رسیده و کمتر برنامه نویس حرفه‌ای یی از این روش استفاده می‌کند (اگرچه که به صورت رسمی این روش منقضی نشده است اما در میان برنامه نویسان، این روش دیگر مورد استفاده قرار نمی گیرد.) زمانی که از این متد استفاده می کنیم، فرایند Escaping به عهده برنامه نویس گذاشته می‌شود (به طور کلی منظور از این اصطلاح این است که داده‌های ورودی به‌ خصوص داده‌های ورودی از طریق صفحات لاگین، فرم‌ها و غیر می بایست تصدیق شوند تا مجرمین سایبری از این طریق نتوانند به اطلاعات دیتابیس ما دسترسی پیدا کنند.)

این روش خیلی انعطاف پذیر نیست و یک جاهایی برای دستیابی که کاری خاص برنامه نویس خیلی اذیت خواهد شد. در مقابل زمانی که برای ارتباط با دیتابیس از PDO استفاده کنیم، این API با سیستم‌های مدیریت دیتابیس مختلفی ارتباط برقرار کرده، دارای یکسری متدهای از پیش تعریف شده برای انجام کارهایی خاص مثل فراخوانی داده و … است و در نهایت هم خیلی نیازی نیست تا نگران حملات SQL Injection باشیم (مشاهده مقاله معرفی حملات SQL Injection) و بالاخره این که PDO اصطلاحاً خیلی Flexible است (نیازی به توضیح نیست که PDO از بیش از ۱۲ درایور مختلف پشتیبانی می‌کند که از آن جمله می‌توان به MySQL، Oracle و … اشاره کرد و این در حالی است که mysql_connect صرفاً از سیستم مدیریت دیتابیس MySQL پشتیبانی می کند.) زمانی که بخواهیم از روش استفاده از mysql_connect به PDO مهاجرت کنیم، ممکن است که در ابتدا این کار کمی وحشتناک به نظر برسد و این مسأله به خاطر این نیست که روش استفاده از پی دی او خیلی دشوار است بلکه این ترس از آنجا ناشی می‌شود که متد mysql_connect بسیار ساده و قابل فهم بوده است.

به هر حال بایستی گفت که استفاده از PDO API شتری است که درب منزل هر برنامه نویسی PHP که بخواهد پایش را یک گام فراتر گذاشته و وارد دنیای برنامه نویسان حرفه‌ای شود خوابیده است! پیش از این مهاجرت ضروری می بایست یک نکته را پس ذهن داشته باشیم که Perfomance یا به عبارتی «سرعت و عملکرد» mysqli_connect تا حدودی از PDO بیشتر است (جالب است بدانیم که سرعت mysql_connect از mysqli_connect هم بیشتر است) اما اگر بخواهیم به صورت کلی این سه API را مقایسه کنیم مسلماً برنده بازی PDO خواهد بود. PDO از نسخه PHP 5.1 به بعد به این زبان برنامه نویسی محبوب اضافه شد پس حتماً مطمئن شوید که نسخه PHP نسب شده روی سیستم لوکال شما این پیش نیاز را تأمین می کند. پیش از این گفتیم که پی دی او از دیتابیس های مختلفی پشتیبانی می کند. برای آن که متوجه شویم که روی سیستمی که با استفاده از آن به توسعه وب اپلیکیشن می‌پردازیم از چه پایگاه داده‌هایی پشتیبانی می شود، نیاز است تا دستور زیر را در یک فایل پی اچ پی قرار داده و آن را در localhost روی مرورگر اجرا کنیم:


var_dump(PDO::getAvailableDrivers());

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

array(1) { [0]=> string(5) "mysql" }

حال که خیالمان راحت شد که سیستم آماده استفاده از کلاس PDO است، می بایست یک شیئ جدید از روی این کلاس بسازیم. برای این منظور، متغیری تحت عنوان connection در نظر گرفته و با استفاده از کلیدواژه new یک شیئ جدید ایجاد می کنیم:

$connection = new PDO('mysql:host=host;dbname=Database','username', 'password');

در تفسیر کدهای فوق بایستی گفت که شیئ ساخته شده از روی این کلاس سه پارامتر ورودی می گیرد که پارامتر اول مربوط به نوع سیستم مدیریت دیتابیس، نام دیتابیس و سرور است، پارامتر دوم مربوط به نام کاربری و پارامتر آخر هم مربوط به پسورد است. نسخه تکمیل شده کد فوق برای ارتباط با MySQl به صورت زیر خواهد بود:

$connection = new PDO('mysql:host=localhost;dbname=sokanacademy','root', 'root');

همان طور که در کد فوق ملاحظه می شود، ابتدا نام سیستم مدیریت دیتابیس که mysql است را در نظر گرفته ایم سپس نام هاست را localhost در نظر گرفته ایم و دلیل انتخاب لوکال هاست این است که هم دیتابیس و هم اسکریپت های پی اچ پی ما قرار است که روی یک سرور اجرا شوند و در نهایت هم نام دیتابیس که در این مثال sokanacademy است را در نظر گرفته و پایان این پارامتر یک علامت کاما قرار داده ایم. پارامتر دوم مربوط به نام کاربری اتصال به MySQL است که در این مثال نام کاربری مربوط به phpmyadmin نصب شده روی سیستم root است و پارامتر آخر هم مربوط به رمزعبور است که آن هم root در نظر گرفته شده است. توجه داشته باشیم که به جای Hard Coding یا بهتر بگوییم «وارد کردن نام کاربری و رمز عبور به صورت دستی» می توان از متغیرها و ثابت ها نیز استفاده کرد که در آن صورت می بایست علامت های ' ' را برای نام کاربری و رمز عبور حذف کنیم:

$connection = new PDO('mysql:host=localhost;dbname=sokanacademy',$myUsername, $myPassword);

در زبان برنامه نویسی پی اچ پی زمانی که ما اسکریپت نویسی می کنیم با Errorها مواجه می شویم اما زمانی که اقدام به استفاده از قابلیت شیئ گرایی این زبان می کنیم یا بهتر بگوییم با کلاس ها و اشیاء ساخته شده از روی آن ها سر و کار داریم به جای ارور اصطلاحا با Exception سر و کار داریم. حال اگر این شیئ ساخته شده تحت عنوان connection مشکلی داشته باشد، این مشکل را متوجه نخواهیم شد مگر آن که راه کاری اتخاذ کنیم برای یافتن Exception مرتبط با این شیئ. برای رفع این مشکل می بایست از دستورات try و catch استفاده کنیم:

try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'root', 'root');
} catch(PDOException $e) {
    echo 'Opps, Something bad just happened!' . '<br>';
    echo $e->getMessage();
}

همان طور که در کد فوق ملاحظه می شود، دستور اصلی را داخل بلوک try نوشته ایم. اگر این دستور به درستی کار کند که هیچ اما اگر به هر دلیلی با مشکلی مواجه شود، دستور داخل بلوک catch اجرا می شود. catch یک پارامتر ورودی می گیرد که شیئی از روی کلاس PDOException است تحت عنوان e که نام این شیئ کاملا دلخواه است. حال داخل کروشه های مرتبط با catch گفته ایم که ابتدا عبارت Opps, Something bad just happened به معنی «اوه، یک اتفاق بدی رخ داده» نمایش داده شده سپس در خط بعدی با استفاده از یکی از متدهای کلاس PDOException تحت عنوان getMessage پیغام خطا در معرض دید برنامه نویس قرار گیرد (توجه داشته باشیم که شیئ e را به تنهایی و بدون استفاده از متد getMessage هم می توان echo کرد اما این در حالی است که متد getMessage ارور به مراتب خواناتری را در معرض دید ما برای Debugging قرار می دهد.) برای تست کردن این موضوع اگر کدهای فوق را در یک فایل پی اچ پی قرار داده و آن ها را روی لوکال هاست اجرا کنیم (البته در صورتی که از قبل پایگاه داده ای تحت عنوان sokanacademy روی phpmyadmin ایجاد کرده و هم رمز عبور و هم نام کاربری phpmyadmin برابر با root باشند)، هیچ چیزی نمی بایست روی صفحه مرورگر بیاید. اکنون قصد داریم تا عمدا نام کاربری را از root به ss تغییر دهیم:


try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'ss', 'root');
} catch(PDOException $e) {
    echo 'Opps, Something bad just happened!' . '<br>';
    echo $e->getMessage();
}

حال یک بار دیگر خروجی کدهای فوق را در مرورگر مشاهده می کنیم:

Opps, Something bad just happened!
SQLSTATE[28000] [1045] Access denied for user 'ss'@'localhost' (using password: YES)

می بینیم که اصطلاحا Exception فوق در معرض دید قرار می گیرد. این ویژگی PDO بسیار برای فرایند Debugging وب اپلیکیشن کاربردی خواهد بود اما توجه داشته باشیم که وقتی خواستیم سایت را روی یک سرور لایو قرار دهیم تحت هیچ عنوان کاربران سایت نمی بایست با ارورهای دریافتی رو به رو شوند چرا که این کار امنیت سایت ما را پایین خواهد آورد (توجه داشته باشیم که وضعیت نمایش ارورهای کلاس PDO روی PDO::ERRMODE_SILENT قرار گرفته است و این در حالی است که این Mode را می توان بسته به نیاز خود تغییر داد.) در این مرحله از آموزش توانسته ایم با موفقیت با دیتابیس مد نظر خود ارتباط برقرار سازیم. حال اولین کاری که قصد داریم یاد بگیریم، فراخوانی داده ها از دیتابیس است. برای این منظور از دو روش مختلف می توان استفاده کرد که یکی استفاده از متد query و دیگری متد execute است. ابتدا متد query را بررسی می کنیم:


try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'root', 'root');
    $result = $connection->query('SELECT * FROM users');
    foreach ($result as $row) {
        print_r($row);
    }
} catch(PDOException $e) {
    echo 'Opps, Something bad just happened!' . '<br>';
    echo $e->getMessage();
}

همان طور که در کد فوق ملاحظه می شود، ابتدا یک متغیر جدید ساخته ایم تحت عنوان result و مقدار آن را برابر با شیئ ساخته شده از روی کلاس PDO که متدی تحت عنوان query به آن ضمیمه شده است قرار داده ایم. داخل این متد هم یک دستور بسیار ساده اس کیو ال قرار داده ایم به این صورت که این اسکریپت می بایست کلیه داده های قرار گرفته در جدولی تحت عنوان users را در متغیر result ذخیره سازد. حال با استفاده از حلقه foreach تمامی ردیف های قرار گرفته در جدول users را به متغیری تحت عنوان row اختصاص داده و با استفاده از متدی تحت عنوان print_r آن ها را نمایش می دهیم (توجه داشته باشیم که به جای این متد از متد دیگری تحت عنوان var_dump هم می شود استفاده کرد.) خروجی کدهای فوق به صورت زیر خواهد بود:


Array ( [id] => 2 [0] => 2 [username] => user1 [1] => user1 [password] => 12dea96fec20593566ab75692c9949596833adc9 [2] => 12dea96fec20593566ab75692c9949596833adc9 ) Array ( [id] => 3 [0] => 3 [username] => user2 [1] => user2 [password] => a1881c06eec96db9901c7bbfe41c42a3f08e9cb4 [2] => a1881c06eec96db9901c7bbfe41c42a3f08e9cb4 )

می بینیم که خروجی جدول، یک آرایه است که حاوی دو ردیف است که مقادیر id و username و password رمزنگاری شده در آن قرار گرفته اند. این روش را با استفاده از حلقه while نیز می توان پیاده ساخت:

try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'root', 'root');
    $query = $connection->query('SELECT * FROM users');
    while ($row = $query->fetch()) {
        var_dump($row);
    }
} catch (PDOException $e) {
    echo $e->getMessage();
}

همان طور که در کد فوق ملاحظه می شود شرط حلقه while را متغیری تحت عنوان row قرار داده ایم که مقدار آن برابر است با متغیر query که متدی تحت عنوان fetch به آن ضمیمه شده است. این حلقه آنقدر گردش می کند تا کلیه ردیف های قرار گرفته در جدول به متغیر row اختصاص یابند. اگر بخواهیم تا خروجی حلقه ما یک آرایه از جنس عددی باشد می بایست پارامتری معادل با PDO::FETCH_NUM را برای متد fetch در نظر بگیریم و اگر هم بخواهیم یک آرایه در جنس associative دریافت کنیم، می بایست پارامتر PDO::FETCH_ASSOC را برای این متد در نظر بگیریم:

try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'root', 'root');
    $query = $connection->query('SELECT * FROM users');
    while ($row = $query->fetch(PDO::FETCH_ASSOC)) {
        var_dump($row);
    }
} catch (PDOException $e) {
    echo $e->getMessage();
}

توجه داشته باشیم که متد دیگری PDO در اختیار برنامه نویس می گذارد تحت عنوان fetchAll که این امکان را می دهد تا در قالب یک کوئری به دیتابیس، کلیه مقادیر یک جدول به خصوص را دریافت کند اما توجه داشته باشیم که نسبت به fetch از میزان حافظه بیشتری استفاده خواهد کرد ولی به هر حال مزیت آن ارسال فقط یک کوئری به دیتابیس است:

try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'root', 'root');
    $query = $connection->query('SELECT * FROM users');
    $query = $query->fetchAll();
    foreach ($query as $row) {
        print_r($row);
    }
} catch (PDOException $e) {
    echo $e->getMessage();
}

در ادامه آموزش می بایست با مفهوم تحت عنوان Prepared Statements یا دستورات از پیش آماده شده آشنا شویم. همواره یکی از دغدغه های برنامه نویسان برای ارسال کوئری به دیتابیس و ذخیره سازی چیزی در آن، حملات SQL Injection است که در روش های سنتی می توانستیم با استفاده از متدی تحت عنوان mysql_real_escape_string جلوی این دست حملات را بگیریم. پس از مهاجرت از روش های سنتی ارتباط با دیتابیس در زبان برنامه نویسی پی اچ پی به APIیی تحت عنوان PDO، اگر بدانیم که چگونه از این کلاس به درستی استفاده کنیم، به صورت خودکار جلوی حملات SQL Injection هم گرفته خواهد شد. ساز و کار دستورات Prepared Statement به این شکل است که stringها را صرفا به شکل یک متن در قالب دستوارت اس کیو ال برای دیتابیس ارسال نمی کنند. لذا با خیال راحت می توان به ارتباط با دیتابیس و ذخیره سازی داده ها در آن پرداخت. برای این منظور، می بایست از متدی تحت عنوان prepare در کنار شیئی که از روی کلاس پی دی او ساخته ایم بپردازیم سپس داخل پرانتزهای این متد، کدهای اس کیو ال مد نظر خود را قرار می دهیم.اکنون قصد داریم تا نتیجه فوق را با استفاده از متدهای prepare و execute به دست آوریم:

try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'root', 'root');
    $prepare = $connection->prepare('SELECT * FROM users');
    $prepare->execute();
    $result = $prepare->fetchAll();
    foreach ($result as $row) {
        print_r($row);
    }
} catch(PDOException $e) {
    echo 'Opps, Something bad just happened!' . '<br>';
    echo $e->getMessage();
}

همان طور که در کد فوق ملاحظه می شود، ابتدا یک متغیر ساخته ایم تحت عنوان prepare و مقدار آن را برابر با شیئ ساخته شده از روی کلاس PDO قرار داده ایم که متدی تحت عنوان prepare به آن ضمیمه شده و داخل آن کدهای اس کیو ال مد نظر را نوشته ایم. در ادامه وقتی که کدهای اس کیو ال اصطلاحا آماده شدند، می بایست آن ها را اجرا کرد. برای این منظور، متد execute به معنی اجرا را به متغیر prepare ضمیمه می کنیم. در نهایت یک متغیر جدید می سازیم تحت عنوان result به معنی نتیجه و مقدار آن را برابر با متغیر prepare قرار می دهیم که متدی تحت عنوان fetchAll به معنی «برو همه رو بگیر و بیار!» به آن ضمیمه کرده ایم. از این پس متغیر result به عنوان یک آرایه حساب خواهد شد. لذا با استفاده از حلقه foreach همچون مثال قبل کلیه مقادیر آن را از یکدیگر مجزا سازی کرده و در قالب متغیری تحت عنوان row نمایش می دهیم. حال زمان هایی برای ما فرا می رسد که می خواهیم مقادیر ارسالی به دیتابیس را مجزا از کدهای اس کیو ال ارسال کنیم. برای این منظور، کدهای فوق را به صورت زیر بازنویسی می کنیم:

try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'root', 'root');
    $statement = $connection->prepare('SELECT * FROM users WHERE username=?');
    $statement->bindValue(1, 'user1');
    $statement->execute();
    $result = $statement->fetch();
    foreach ($result as $row) {
        var_dump($row);
    }
} catch(PDOException $e) {
    echo $e->getMessage();
}

همان طور که در کد فوق ملاحظه می شود، در کدهای اس کیو ال دستور داده ایم تا از جدول users فقط ردیفی که نام کاربری آن معادل با username خاصی است نمایش داده شود. برای این منظور، مقابل نام username یک علامت سوال قرار داده و در خط بعد با استفاده از متدی تحت عنوان bindValue آن علامت سوال را مقدار دهی کرده ایم. توجه داشته باشیم با توجه به این که در کدهای اس کیو ال خود صرفا از یک علامت سوال استفاده کرده ایم، پارامتر اول متد bindValue را عدد ۱ انتخاب کرده سپس یک کاما می گذاریم و مقدار مد نظر را وارد می کنیم. اگر در دیتابیس خود در جدول users کاربری با نام کاربری user1 داشته باشیم، فلیدهای مرتبط با این کاربر در معرض دید قرار خواهد گرفت. در ادامه می بایست با مفهومی تحت عنوان Name Parameters یا «پارامترهای نامگذاری شده» آشنا شویم. همان طور که در کد فوق ملاحظه می شود، در دستورات SQL از علامت سوال به جای مقادیر یک فیلد استفاده کردیم. حال زمان هایی برای ما بوجود می آید که می خواهیم همان نامی که در دیتابیس برای خانه های مختلف در نظر گرفته ایم را مورد استفاده قرار دهیم. فایده این کار این است که کدهای ما راحت تر خوانده می شوند و مابین جداول دیتابیس و کدهای اس کیو ال هماهنگی نامی وجود خواهد داشت. برای این منظور، کدهای فوق را به صورت زیر بازنویسی می کنیم:

try {
    $connection = new PDO('mysql:host=localhost;dbname=sokanacademy', 'root', 'root');
    $statement = $connection->prepare('SELECT * FROM users WHERE username=:username');
    $statement->bindValue(':username', 'user1');
    $statement->execute();
    $result = $statement->fetch();
    foreach ($result as $row) {
        var_dump($row);
    }
} catch(PDOException $e) {
    echo $e->getMessage();
}

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

0


بهزاد مرادی

از جمله علائق بهزاد مرادی می توان به نشر علم،‌ سرمایه گذاری روی نسل آینده، زبان برنامه نویسی پی اچ پی و جامعه متن باز و همچنین راه اندازی استارتاپ و کارآفرینی اشاره کرد و او بر این باور است که سکان آکادمی بستری است که از آن طریق می تواند به علائق اش جامه ی عمل بپوشاند. از جمله فعالیت های وی در سکان آکامی می توان به تالیف دوره های آنلاین برنامه نویسی و ترجمه مقالات وبلاگ اشاره کرد. 






از طریق این فرم، می توانید بدون ثبت نام نظر دهید و یا اگر قبلا ثبت نام کرده اید، با ورود ناحیه ی کاربری می توانید علاوه بر ثبت نظر، به مدیریت نظرات خود نیز بپردازید.
(فیلد اجباری)
(فیلد اجباری)
(فیلد اجباری)
(فیلد اجباری)