💻 آخرین فرصت یادگیری برنامه‌نویسی با آفر ویژه قبل از افزایش قیمت در ۵ آذر ماه (🎁 به همراه یک هدیه ارزشمند )
۰ ثانیه
۰ دقیقه
۰ ساعت
۴ رسول طیبی‌راد
اگر ارتباط با دیتابیس از دست بره، چه منطقی رو پیاده سازی کنیم؟
محسن موحد حل شده توسط محسن موحد

سلام،

توی PHP، به هر دلیلی اگر ارتباط با دیتابیس از دست بره، چه فرآیندی رو باید اجرا کنیم؟

بعنوان مثال توی فریمورک لاراول وقتی کانکشن با دیتابیس برقرار نیست، یک QueryException با پیغام Connection refused ایجاد میشه. در این حالت لاجیک کد به چه صورت پیاده سازی میشه؟! آیا باید در هر تکه از کد که قرار هست یک رکورد ایجاد یا ویرایش بشه از بلوک try catch استفاده کنیم؟

 

لینک مقاله خوبی هم سراغ دارید ممنون میشم به اشتراک بذارید.

سلام.

در مورد دیتابیس زمانیکه کانکشن PDO رو میسازیم Error Mode ست میکنیم:

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

یعنی اگر مشکلی برای کانکشن و ارتباط هامون با دیتابیس اتفاق بیوفته، حتما یک PDOException اتفاق میوفته.

این اکسپشن دنبال بلاک catch مخصوصش میگرده و اگر وجود نداشت یک fatal error اتفاق میوفته.

بجای اینکه تمام تکه کدهارو try/catch اضافه کنید، پروژه رو بصورتی پیاده کنید که تمام هندل request هاف response‌ها و ... پروژه در یک try/catch اتفاق بیوفته و Custom Exception هایی بسازید که با if/else بعضی قسمت هارو میتونید چک کنید و throw new Excepton یا throw new CustomException یا حتی با توجه به مثال شما throw new QueryException قرار بدید.

در مورد if/else یه مثال میزنم، چک میکنم if آدرسی که کاربر خواسته باز کنه در سایت وجود نداشت، throw new HttpException و ...

قطعا در بعضی از تکه کدها مجددا از try/catch نیازه که استفاده کنید تا مثلا اگر تکه کدی exception ای تولید شد، داخل catch، مثلا یک لاگ یا هر کاری دیگه بسته به موقعیت کد و فکر برنامه نویس، اتفاق بیوفته و بعد از اون throw new MyException کند و ...

میتونید تمام exception‌ها و حتی fatal error‌ها رو خودتون مدیریت و customize کنید.

اینایی که گفتم یک کلیتی از هندل کردن exception‌ها بود که فک میکنم در PHP پیشرفته با یادگیریه شی گرایی و مخصوصا در سرفصل میکروفریمورک این مباحث جا میوفته.

محسن موحد ۲۰ فروردین ۱۳۹۹، ۰۹:۰۱

در پاسخ به دوست گرامی @محسن موحد:

بنظرم بطور کلی برای انجام عملیات CRUD بهتر هست از الگوی Repository Pattern استفاده بکنیم. یعنی اینکه تمامی موجودیت‌های برنامه شامل یوزر، محصول، سفارش، ... از یک BaseRepository ارث بری کنند و در این کلاس خطاهای احتمالی رو در چند بلوک try-catch مدیریت کنیم.


مثال (در لاراول):

interface RepositoryInterface
{
    public function find($someArgiments);
    // other needed methods ...
}

 

و برای BaseRepository:

class BaseRepository implements RepositoryInterface
{
    protected $model;
    public function find($someArguments)
    {
        try
        {
            // do stuff with $someArguments ...
            return $this->model::find($ID);
        } catch(Exception $e) {
            Notification::send($e); // To developers stack ...
            return back()->with($message);
        }
    }
    // Implement other methods ...
}

 

و در نهایت، UserRepository از کلاس Base ارث بری کنه و توی سطح کنترلرها یا سرویس‌ها دیگه از try-catch استفاده نکنیم..

 

آیا این لاجیک Clean Code محسوب میشه؟ اگر نه چه تغییری لازمه تا بتونیم خطاهای ارتباط با دیتابیس رو در یک جا متمرکز کنیم؟

 

ممنون از همراهی..

رسول طیبی‌راد ۲۰ فروردین ۱۳۹۹، ۱۶:۴۵

مبحثی که شما فرمودید خیلی جلوتر از بحث دوره هست فعلا.

لاراول امکانات و ابزار زیادی برای این موارد داره و این امکان وجود داره اونجا که در هنگام رخداد یه اتفاق خاص (مثل از دست رفتن کانکشت دیتابیس) یه ایونتی فایر شه و به اون ایونت پاسخ بدیم.

اینجا توی php چیز خاصی نیست مگه اینکه همچین چیزی رو پیاده سازی کنیم.

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

لقمان آوند ۲۰ فروردین ۱۳۹۹، ۲۱:۲۴

سلام مجدد، ببینید از استفاده ی try/catch که نمیشه جلوگیری کرد، حتی فریمورک‌های بزرگ و میکروفریمورک هارو هم که ببینید مثلا متدهایی دارن با اسم هایی چون init یا run یا render یا bootstrap یا ... با اینکه Error Handler و Exception Handler رو برای فریمورک پیاده سازی کردن ولی باز هم میبینیم از بلاک‌های try/catch در جاهای مختلف استفاده شده.

در بعضی جاها قبل از throw یا پرتاب یک اکسپشن، ممکنه نیاز باشه اگر اکسپشنی رخ داد، فایلی یا اتصالی رو close کنن یا ممکنه بخوان لاگ بگیرن ازون موقعیت و ... و بعد ازون throw کنن، بنابراین اون تکه کدی که احتمال بروز اکسپشن رو میدن، داخل بلاک try/catch میذارن و در catch عملیاتشونو انجام میدن و بعد throw میکنن یک exception رو.

 

اما خلاصش برای اینکه از try/catch‌های متعدد جلوگیری کنید، میتونید از set_exception_handler استفاده کنید و یک callback بعنوان پارامتر بدید تا مشخص کنید که در مواجهه با یک اکسپشن چه رفتاری رو انجام بده.

بعد از اجرا callback برنامه terminate میشه. در اینجا خطاهایی که پرتاب میشه و همچنین بلاک catch ای  برای گرفتن  خطا وجود نداره، اصطلاحاً(Uncaught Exception) توسط callback خونده میشه.

مثالی از داکیومنت PHP:

<?php
function exception_handler($exception) {
  echo "Uncaught exception: " , $exception->getMessage(), "\n";
}
set_exception_handler('exception_handler');
throw new Exception('Uncaught Exception');
echo "Not Executed\n";

 

هرجایی از برنامه اکسپنی رخ بده(مثل اکسپشن‌های دیتابیس) اینجا مدیریت میشه. میتونید Custom Exception هم بنویسید. کافیه هر کجا از برنامه که نیاز بود، throw new MyCustomException رو فراخوانی کنیم.

باید کمی دستتون اومده باشه.

 

فریمورک لاراول به این شکل بود که در متد bootstrap از کلاس HandleExceptions اومده:

error_reporting(-1);
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
register_shutdown_function([$this, 'handleShutdown']);
if (! $app->environment('testing')) {
    ini_set('display_errors', 'Off');
}

error handler رو خودش مدیریت کرده تا خطاهایی مثل notice و warning رو بصورت exception تولید کنه. داخل متد errorHandle دستور throw new ErrorException اومده.

 

 

و در داخل متد handleException:

if (!$e instanceof Exception) {
    $e = new FatalThrowableError($e); // catch fatal error
}
try {
} catch (Exception $e) {
}
if ($this->app->runningInConsole()) {
    $this->renderForConsole($e);
} else {
    $this->renderHttpResponse($e);
}

 

register_shutdown_function هم بعد از اتمام اسکریپت فراخوانی میشه.

 

در YiiFramework هم شبیه به Laravel به شکل زیر:

public function register()
{
    ini_set('display_errors', false);
    set_exception_handler([$this, 'handleException']);
    if (defined('HHVM_VERSION')) {
        set_error_handler([$this, 'handleHhvmError']);
    } else {
        set_error_handler([$this, 'handleError']);
    }
    if ($this->memoryReserveSize > 0) {
        $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
    }
    register_shutdown_function([$this, 'handleFatalError']);
}

 

البته مسئله ی مدیریت اکسپشن‌ها در این فریمورک‌ها خلاصه به این چند مورد نمیشه و به قول استاد آوند، event‌های زیادی بررسی میشه و مخصوصا بجز اینهایی که اوردم، داخل متد‌ها مخصوصا متدهای کار با دیتابیس try/catch‌های متعددی اومده که بخاطر بزرگی پروژه فعلا از درک من خارجه! :دی

 

در مورد دیتابیس هم PDO اگر مشکلی پیش بیاد، اکسپشن تولید میکنه ولی اگر میخواید خیلی ریزتر وارد مسئله بشید Custom Exception بسازید مثلا class CustomException extends Exception و در موقعیت مناسب throw کنید.

 

اگر این پاسخ هم شما رو قانع نکرد، صبر کنید تا به سرفصل ساخت میکرو فریمورک برسیم.

بهترین پاسخ
محسن موحد ۲۰ فروردین ۱۳۹۹، ۲۳:۴۸