مدیریت خطا (Error handling) در PDO

16 بهمن 1397
درسنامه درس 8 از سری آموزش PDO
PDO-error-handler

با سلام، در این جلسه در رابطه با مبحث Error handling یا مدیریت خطا صحبت خواهیم کرد. به طور خلاصه می توان گفت Error handling سعی دارد تا با پیش بینی و مدیریت خطاهای ممکن در سیستم ما از متوقف شدن یا به خطر افتادن برنامه ی ما جلوگیری کند.

Error handling

با اینکه حالت های مختلفی برای مدیریت خطا در PDO موجود است اما مورد صحیح و استاندارد آن PDO::ERRMODE_EXCEPTION است و شما باید همیشه از آن استفاده کنید.

بنابراین دو راه دارید: یا همیشه هنگام ساخت کانکشن خود، این دستور را هم به عنوان connection option وارد کنید و یا اینکه بعد از ساخت PDO از این خط استفاده نمایید:

$dbh->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

تمام کاری که باید برای دریافت error reporting (گزارش خطاها) انجام دهید، همین است.

گزارش خطاها در PDO

بر خلاف اشتباهی که در اکثر سایت های اینترنتی می بینید شما نباید برای گزارش (report) خطاها (error) آن ها را catch (منظور بنده اپراتور try..catch است) کنید.

یک ماژول (مانند لایه ی پایگاه داده) نباید خطاهای خود را گزارش کند بلکه چنین کاری وظیفه ی site-wide handler است. در ضمن شما نیازی به نوشتن این handler ندارید و خود PHP دارای یک handler خوب است.

تنها کاری که ما باید انجام دهیم این است که خطاها را به صورت exception گزارش کنیم. تنها استثناء این قانون زمانی است که میخواهید یک نمونه از PDO بسازید.

اگر یادتان باشد در جلسات قبل گفتیم که این حالت ممکن است اطلاعات حساس شما را نمایش دهد. بنابراین راه حل زیر را ارائه دادیم (قسمت زیر، یادآوری از قسمت های قبلی است):


یادآوری: ... تا اینجا قسمت credentials (اعتبارات) و connection options (گزینه های اتصال) را توضیح دادیم. تنها قسمت باقی مانده در مثال ما، قسمت مدیریت خطا است که در کد زیر می بینید:

try {
     $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
     throw new \PDOException($e->getMessage(), (int)$e->getCode());
}

چرا از این شیوه استفاده کرده ایم؟

اگر exception ای داشته باشیم که دریافت نشود (uncaught)، تبدیل به Fatal error در PHP خواهد شد. تا اینجای کار مشکلی نیست و ما به گزارش خطاها نیاز داریم تا بتوانیم مشکل برنامه را رفع کنیم اما این خطا شامل stack trace ای است که به خطا الصاق می شود.

این خطا در مورد PDO به این نحو عمل می کند که پارامتر های constructor را نیز به ما بر میگرداند؛ همانطور که می دانید این پارامتر ها همان اعتبارات ما (نام کاربری، رمز عبور و …) است.

حتما می گویید که ما گزارش خطا (error reporting) را در وب سایت های واقعی غیر فعال می کنیم، بنابراین اعتبارات ما به کسی نشان داده نمی شود.

حرف شما کاملا درست است و ما فقط برای محکم کاری، exception مورد نظر را میگیریم (catch) و دوباره پرتاب (throw) می کنیم تا حتی کوچکترین احتمالی هم از نمایان شدن اعتبارات ما برای کسی وجود نداشته باشد. [پایان یادآوری]


قسمت بالا، یادآوری از قسمت های قبلی بود و باید توجه داشته باشید که لازم نیست تمام عملیات های PDO خود را داخل try/catch قرار دهید، بلکه catch کردن یک exception باید در مواقع خاصی انجام بگیرد نه بدون حساب و کتاب.

به عبارت دیگر exception ها در PDO هیچ چیز خاصی ندارند و تنها خطا هستند بنابراین شما نیز باید با آن ها مانند بقیه ی خطاها برخورد کنید.

به طور مثال اگر از قبل یک handler برای خطاها داشته اید نباید یک handler دیگر به طور اختصاصی برای PDO بسازید. از طرفی اگر handler نداشته اید و اهمیتی نداده اید، مشکلی نیست چرا که خودِ PHP یک handler خوب در اختیار ما قرار میدهد که خطاها را به خوبی گزارش می کند.

یکی از مشکلات دوره های آموزشی PDO در اینترنت (حتی در منابع خارجی) مبحث error handling است.

زمانی که توسعه دهندگان برای بار اول با exception ها در PDO آشنا می شوند، تصور می کنند که exception ها تنها مخصوص همین زمینه هستند و به همین خاطر فقط در همین زمینه exception ها را مدیریت می کنند. اما همانطور که گفتیم، exception ها مسئله ی خاصی نیستند و فقط یک نوع خطا هستند مانند بقیه ی خطاها، بنابراین اگر از قبل با exception ها کار نمی کردید چرا باید الان این کار را به این شدت انجام دهید؟

توجه داشته باشید که ما نمی گوییم handle کردن خطاها و exception ها بد است، بلکه می گوییم هر چیزی جایی دارد و باید آگاهانه انجام شود.

در وب سایت رسمی PHP آمده است که:

If your application does not catch the exception thrown from the PDO constructor, the default action taken by the zend engine is to terminate the script and display a back trace. This back trace will likely reveal the full database connection details, including the username and password.

ترجمه: اگر برنامه ی شما exception ای را که توسط PDO constructor به آن پاس داده می شود، دریافت نکند موتور zend اسکریپت شما را متوقف کرده و back trace را نمایش خواهد داد. این back trace به احتمال زیاد اطلاعات اتصال به پایگاه داده ی شما (شامل نام کاربری و رمز عبور) را نمایان خواهد کرد.

این جمله به شدت گنگ نوشته شده و باعث سردر گمی بسیاری از برنامه نویسان شده است. در واقع اصلا چیزی به نام نمایش دادن back trace نداریم! کاری که موتور Zend انجام می دهد این است که exception ای را که catch نشده باشد تبدیل به یک fatal error (باید از دوره های مقدماتی PHP با این مبحث آشنا باشید) می کند.

سپس این fatal error مانند هر خطای دیگری رفتار می کند و فقط هنگامی که دستورات مربوطه در فایل php.ini نوشته شده باشد نمایش داده می شود. بنابراین شما چه exception را دریافت کنید و چه نکنید، این مبحث ربطی به نمایان شدن اطلاعات حساس ندارد چرا که تنظیمات configuration کاملا متفاوتی دارد. بنابراین به جای catch کردن exception ها در PDO، سرور خود را به طور صحیح تنظیم کنید.

در حالت توسعه (زمانی که در حال کدنویسی برای سایت هستید) نمایش خطاها را در فایل php.ini روشن کنید:

ini_set('display_errors', 1);

در حالت تولید (زمانی که وب سایت برای بازدید عموم باز است و دارد کار می کند) می توانید نمایش خطاها را خاموش کرده اما خطاهای لاگین را روشن بگذارید:

ini_set('display_errors', 0);
ini_set('log_errors', 1);

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

چه زمانی exception ها را catch کنیم؟

سوال بسیار مهم این است که این توصیفات چه مواقعی برای catch کردن exception ها مناسب است؟

شما تنها در دو موقعیت می توانید exception های PDO را catch کنید:

  1. اگر میخواهید برای PDO یک wrapper بنویسید تا اطلاعات بیشتری (مانند رشته ی کوئری که باعث خطا شده) را به پیام خطا اضافه کنید و سپس تمام این اطلاعات را نمایش دهید. این کار می تواند دلایل مختلفی داشته باشد، مثلا ممکن است متن خطاهای پیش فرض برای شما کافی نباشد و دوست دارید اطلاعات کاملی از خطا داشته باشید. در چنین حالتی exception را catch کنید، اطلاعات اضافی و لازم را جمع کرده و همه را دوباره (در قالب یک excetion دیگر) throw کنید.
  2. اگر حالت خاص یا موقعیت خاصی را برای handle کردن خطاها در قسمت خاصی از سورس کد در نظر دارید. چند مثال از این حالت عبارت اند از:
    1. اگر خطا قابل بایپس شدن می باشد می توانید از try..catch استفاده کنید اما مراقب باشید که تبدیل به عادت نشود. یک catch خالی، مانند اپراتور دفع خطا (error control operator با علامت @) عمل می کند و به همان اندازه خطرناک است.
    2. اگر در موقعیتی هستید که پس از برخورد با خطا برنامه باید کار خاصی انجام دهد؛ مانند خطا در تراکنش های بانکی و بحث transaction rollback.
    3. اگر منتظرید تا خطایی خاص را handle کنید. در چنین حالتی exception را catch کنید، ببینید که آیا این خطا همان خطایی است که شما منتظرش بودید یا نه، در صورت مثبت بودن جواب آن را handle کنید ودر غیر این صورت آن را دوباره throw کنید تا به حالت عادی به handler پیش فرض برسد. مثال:
try {
    $pdo->prepare("INSERT INTO users VALUES (NULL,?,?,?,?)")->execute($data);
} catch (PDOException $e) {
    $existingkey = "Integrity constraint violation: 1062 Duplicate entry";
    if (strpos($e->getMessage(), $existingkey) !== FALSE) {

        // Take some action if there is a key constraint violation, i.e. duplicate name
    } else {
        throw $e;
    }
}

اما در حالت کلی هیچ نیازی به نوشتن کد خاص یا برخورد خاص با exception ها در PDO نیست. به طور خلاصه برای گزارش خطاها به صورت صحیح در PDO:

  • PDO را در حالت exception قرار دهید (منظور همان دستور PDO::ERRMODE_EXCEPTION است).
  • بدون حساب و کتاب از ساختار try..catch برای گزارش خطاها استفاده نکنید.
  • PHP را در زمینه ی گزارش خطا، به صورت مناسب تنظیم کنید:
    • در سایت های در حال کار و عمومی: display_errors=off و log_errors=on (البته با توجه به مسئله ای که برای کاربران فارسی زبان گوشزد شد).
    • در سایت های در حال توسعه و کد نویسی (تکمیل نشده): display_errors=on
    • در هر دو حالت باید از حالت E_ALL استفاده شود.

خلاصه ی جلسه

در این قسمت در رابطه با مدیریت خطاها و exception ها و همچنین ساختار های try..catch صحبت کرده و ابهامات و اشکالات پیرامون این مسائل را باز کردیم. امیدوارم از این قسمت لذت برده باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش PDO توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (2 دیدگاه)

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

ارمین
28 آذر 1398
درود بر شما جناب زوارمی عزیز یک سوال داشتم خدمتتون در متن ارورها متاسفانه نام دیتایس یا ... واضح قابل مشاهده است! میشه راهنمایی کنید که در نرم افزار xampp دقیقا چطور این ارورهارو از سایت غیر فعال کنیم و درون یک فایل دیگه مثل منتقلش کنیم؟ و دقیقا چه کدهایی رو باید در چه فایلی قرار بدیم ممنون

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

Ahmad Tavakoli
16 بهمن 1397
اقا دمت گرما دوره ی خیلی خوبیه سایتای دیگه زیاد دیدم ولی اینجوری نکته ای نمیگن خیلی کلی حرف میزنن

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

امیر زوارمی
19 بهمن 1397
خوشحالم که استفاده کردید. ما سعی کردیم عملی کار کنیم و غیر از منابع تئوری از برنامه نویسان با تجربه هم کمک بگیریم.

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