خطای Division by zero در PHP یکی از آن خطاهایی است که در نگاه اول ساده به‌نظر می‌رسد، ولی در عمل می‌تواند کل سایت را از کار بیندازد. یک فرمول درصدی ساده، یک حلقه محاسباتی، یا یک محاسبه‌ی میانگین در گزارش فروش، کافی است تا صفحه سفید شود و پیام Fatal error در لاگ ظاهر شود. سال‌هاست روی پروژه‌های وردپرسی و PHP با این خطا مواجه می‌شوم و در تجربه‌ام، ریشه‌ی این خطا تقریباً همیشه در یکی از پنج لایه مشخص پنهان است: فرمول‌های ریاضی بدون محافظت، متغیرهای خالی از دیتابیس، خروجی غیرمنتظره توابع، منطق ووکامرس و تغییرات رفتاری نسخه‌های PHP.

این مقاله را برای عیب‌یابی نظام‌مند نوشته‌ام، نه برای وصل کردن راه‌حل‌های آماده. اگر سایت شما با خطای Division by zero سفید می‌شود، اگر در لاگ PHP این خطا به‌طور مکرر ثبت می‌شود، اگر فرمول‌های درصدی در گزارش‌های ووکامرس باعث خرابی می‌شوند، یا اگر بعد از آپدیت به PHP 8 این خطا ظاهر شده، ترتیب بخش‌ها همان مسیری است که در بحران‌های واقعی اجرا می‌کنم.

خطای Division by zero دقیقاً چه معنایی دارد؟

خطای Division by zero در PHP، در ساده‌ترین تعریف، زمانی رخ می‌دهد که برنامه‌ای تلاش می‌کند یک عدد را بر صفر تقسیم کند. این عملیات در ریاضیات تعریف‌نشده است و PHP نیز آن را به‌عنوان یک خطای کشنده در نظر می‌گیرد. توضیح تکمیلی این مفهوم در ویکی‌پدیا موجود است، ولی جان ماجرا در این نکته است که در PHP، این خطا به‌صورت پیش‌فرض یک Fatal Error است و اجرای اسکریپت را کاملاً متوقف می‌کند.

نکته‌ی مهمی که در پروژه‌های واقعی بارها دیده‌ام این است که خطای Division by zero همیشه از یک تقسیم مستقیم بر صفر نمی‌آید. در بسیاری از موارد، این خطا از یک فرمول پیچیده می‌آید که در آن یک متغیر که انتظار می‌رفت مقدار داشته باشد، خالی از دیتابیس برگشته است یا خروجی یک تابع، به‌جای عدد مورد انتظار، مقدار صفر یا null داده است. همین لایه‌ی ناپیدا، دلیل اصلی سردرگمی در عیب‌یابی این خطاست. مباحث پایه‌ای ساختار PHP در آموزش PHP از صفر باز شده است.

در پروژه‌های وردپرسی، خطای Division by zero معمولاً در دو بستر رخ می‌دهد: اول، در کدهای سفارشی که به functions.php قالب یا افزونه‌های سفارشی اضافه شده‌اند و فرمول‌های محاسباتی دارند. دوم، در افزونه‌های معروف مثل ووکامرس یا افزونه‌های گزارش‌گیری که در محاسبه‌ی درصد فروش، میانگین بازدید یا نرخ تبدیل، تقسیم‌هایی دارند. اگر این تقسیم‌ها به‌درستی محافظت نشده باشند، یک مقدار صفر در یک رکورد خاص می‌تواند کل صفحه‌ی گزارش را از کار بیندازد. مباحث مرتبط در رفع خطای Fatal error در PHP باز شده است.

نکته‌ی دومی که در تجربه‌ی چندساله‌ام بسیار مهم بوده، تغییر رفتار PHP در نسخه‌های اخیر است. در PHP 7، خطای Division by zero به‌صورت Warning نمایش داده می‌شد و اجرای اسکریپت ادامه می‌یافت (با بازگشت مقدار false به‌جای عدد). در PHP 8، این خطا به یک DivisionByZeroError ارتقا یافته که یک استثنای کشنده است. این تغییر، باعث شده بسیاری از کدهای قدیمی که در PHP 7 کار می‌کردند، در PHP 8 سایت را از کار بیندازند. مباحث مرتبط در تفاوت PHP 7 و PHP 8 باز شده است.

تقسیم بر صفر در PHP 8، دیگر یک هشدار ساده نیست؛ یک استثنای کشنده است که تمام صفحه را از کار می‌اندازد. اگر روی PHP 8 هستید و این خطا را می‌بینید، باید کد را با الگوهای دفاعی بازنویسی کنید.

تفاوت رفتار PHP 7 و PHP 8 در تقسیم بر صفر

یکی از مهم‌ترین نکاتی که در عیب‌یابی این خطا باید بدانید، تفاوت رفتار نسخه‌های PHP در برخورد با تقسیم بر صفر است:

نسخه‌ی PHPعملگر /عملگر %تابع intdiv()
PHP 5.xWarning و falseWarning و falseوجود ندارد
PHP 7.0Warning و INF/NANWarning و falseDivisionByZeroError
PHP 8.0DivisionByZeroErrorDivisionByZeroErrorDivisionByZeroError

این تفاوت، پیامدهای عملی جدی دارد. اگر کد قدیمی شما در PHP 7 روی یک تقسیم بر صفر با Warning ادامه می‌داد، در PHP 8 همان کد سایت را کامل از کار می‌اندازد. همین موضوع، در پروژه‌هایی که به PHP 8 مهاجرت کرده‌اند، شایع‌ترین علت ظهور ناگهانی خطای Division by zero است.

نمونه عملی تفاوت رفتار

کد زیر در PHP 7 یک Warning می‌دهد ولی ادامه می‌یابد:

<?php
$a = 10;
$b = 0;
$result = $a / $b;
echo $result; // در PHP 7: Warning + INF
?>

همین کد در PHP 8 باعث خطای کشنده می‌شود:

Fatal error: Uncaught DivisionByZeroError: Division by zero

راهکار عملی برای مهاجرت

اگر کد قدیمی دارید که به PHP 8 مهاجرت می‌دهید، قبل از هر چیز تمام موارد تقسیم را با ابزارهایی مثل PHPStan یا Psalm تحلیل کنید. این ابزارها می‌توانند موارد بالقوه تقسیم بر صفر را قبل از اجرا شناسایی کنند:

vendor/bin/phpstan analyse --level=8 /path/to/plugin

تحلیل استاتیک کد، در پروژه‌های من بهترین ابزار برای کشف این نوع خطاهاست. اگر به یاد داشته باشید که خطای Division by zero در PHP 8 حتماً رخ می‌دهد، این تحلیل، از بحران‌های بعدی جلوگیری می‌کند. مباحث مرتبط در رفع خطای Fatal error در PHP باز شده است.

ده ریشه‌ی اصلی خطای Division by zero

در عیب‌یابی خطای Division by zero روی پروژه‌های وردپرسی و PHP، این ده ریشه بیش از بقیه تکرار می‌شوند:

ریشه‌ی اول: فرمول‌های درصدی بدون محافظت

شایع‌ترین دلیل. هرجا فرمول درصد محاسبه می‌شود (مثل (مقدار فعلی / مقدار کل) * 100)، اگر مقدار کل صفر باشد، خطا رخ می‌دهد. این سناریو در افزونه‌های گزارش‌گیری، داشبوردهای آمار و کدهای سفارشی شایع‌تر است. راه‌حل: بررسی مقدار قبل از تقسیم و استفاده از الگوهای دفاعی.

ریشه‌ی دوم: متغیر خالی از دیتابیس

اگر کوئری دیتابیس مقداری را برنگرداند که انتظار می‌رفت عدد باشد (مثلاً NULL یا صفر)، محاسبات بعدی با خطا مواجه می‌شوند. این سناریو در گزارش‌های فروشگاهی یا آماری که با ماه‌های بدون داده مواجه می‌شوند، شایع‌تر است.

ریشه‌ی سوم: خروجی صفر از تابع count()

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

$total = array_sum($ratings);
$count = count($ratings);
$average = $total / $count; // اگر $count صفر باشد، خطا

ریشه‌ی چهارم: تقسیم بر نتیجه‌ی تابع

گاهی نتیجه‌ی یک تابع در ادامه‌ی محاسبات استفاده می‌شود، ولی این تابع در بعضی شرایط صفر برمی‌گرداند. مثلاً wc_get_customer_order_count() در ووکامرس، اگر مشتری هیچ سفارشی نداشته باشد، صفر برمی‌گرداند. اگر از این مقدار در محاسبه استفاده شود، خطا رخ می‌دهد. مباحث مرتبط در مدیریت مشتریان در ووکامرس باز شده است.

ریشه‌ی پنجم: تقسیم در حلقه‌های محاسباتی

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

ریشه‌ی ششم: تقسیم بر خروجی عملیات ریاضی

گاهی خود محاسبه‌ای که در مخرج قرار دارد، ممکن است در شرایط خاصی صفر شود. مثلاً (a - b) اگر a و b مساوی باشند، صفر می‌شود و تقسیم بعدی خطا می‌دهد:

$growth = ($current - $previous) / $previous * 100; // اگر $previous صفر باشد، خطا

ریشه‌ی هفتم: تقسیم در افزونه‌های معروف

افزونه‌های محبوب مثل ووکامرس، Yoast SEO یا افزونه‌های گزارش‌گیری، گاهی در شرایط خاص به تقسیم بر صفر می‌رسند. اگر نسخه‌ی افزونه قدیمی باشد، ممکن است این خطا رفع نشده باشد. راه‌حل: به‌روزرسانی افزونه به آخرین نسخه.

ریشه‌ی هشتم: تقسیم بر مقدار فرم ورودی کاربر

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

ریشه‌ی نهم: ناسازگاری نوع داده (Type Juggling)

در بعضی موارد، متغیری که انتظار می‌رود عدد باشد، در واقع رشته‌ای خالی یا مقدار false است. در PHP 8، این نوع ناسازگاری بیشتر از قبل باعث خطا می‌شود. راه‌حل: استفاده از is_numeric() یا تبدیل صریح نوع قبل از تقسیم.

ریشه‌ی دهم: تقسیم در حلقه‌های تکراری با ورودی خالی

اگر حلقه‌ای روی مجموعه‌ای از داده‌ها تکرار می‌شود و برای هر آیتم محاسبه‌ای انجام می‌دهد، یک آیتم خالی می‌تواند کل حلقه را متوقف کند. این سناریو در پردازش فایل‌های CSV یا ورودی‌های انبوه شایع‌تر است.

پروتکل واکنش سریع در بحران

اگر سایت شما همین حالا با خطای Division by zero سفید شده و کاربران نمی‌توانند وارد شوند، این پنج حرکت را به همین ترتیب اجرا کنید:

  1. تعیین دامنه‌ی خطا: سریع تست کنید که آیا همه‌ی صفحات سفید هستند یا فقط بعضی. اگر همه، ریشه در هسته یا یک افزونه‌ی سراسری است. اگر بعضی، ریشه در کد مرتبط با همان بخش است.
  2. فعال‌سازی لاگ PHP: اگر لاگ فعال نیست، در wp-config.php مقادیر WP_DEBUG و WP_DEBUG_LOG را تنظیم کنید. اگر سایت کامل سفید است، از طریق FTP فایل wp-config.php را ویرایش کنید.
  3. غیرفعال‌سازی افزونه‌های اخیر: اگر خطا بعد از فعال‌سازی یا آپدیت یک افزونه ظاهر شده، از طریق FTP پوشه‌ی آن افزونه را موقتاً تغییر نام دهید.
  4. بررسی لاگ PHP: آخرین خطوط لاگ را بررسی کنید. پیام دقیق معمولاً مسیر فایل و شماره خط را نشان می‌دهد که ریشه‌ی خطاست.
  5. بازگرداندن کد قبلی: اگر خطا بعد از تغییر کد سفارشی در functions.php ظاهر شده، از بکاپ قبلی استفاده کنید.

نکته‌ی میدانی: در بحران، هیچ‌گاه بدون بکاپ، کد را تغییر ندهید. اگر خطای Division by zero در هسته‌ی وردپرس رخ داده، به‌احتمال زیاد ریشه در یک افزونه یا کد سفارشی است که با هسته در تعامل است. اول آن افزونه را غیرفعال کنید و بعد کد را بررسی کنید.

تشخیص دقیق با لاگ و ردیابی خطا

ابزارهای تشخیصی، دقیق‌ترین راه پیدا کردن ریشه‌ی خطای Division by zero هستند. سه ابزار کلیدی:

لاگ PHP

لاگ PHP دقیق‌ترین منبع اطلاعات است. با فعال‌سازی WP_DEBUG_LOG، پیام کامل خطا در wp-content/debug.log ثبت می‌شود. نمونه‌ی پیام:

[12-Oct-2026 14:23:15 UTC] PHP Fatal error: Uncaught DivisionByZeroError: Division by zero in /path/to/plugin/file.php:125
Stack trace:
#0 /path/to/wordpress/wp-includes/class-wp-hook.php(308): my_function()
#1 /path/to/wordpress/wp-includes/class-wp-hook.php(324): WP_Hook->apply_filters()
...

این پیام، سه اطلاعات کلیدی می‌دهد: مسیر فایل، شماره خط و مسیر فراخوانی. با این اطلاعات می‌توانید دقیقاً خطای مورد نظر را پیدا کنید. روش دقیق خواندن لاگ در بررسی خطاهای سرور در لاگ‌ها باز شده است.

ردیابی خطا با Xdebug

ابزار Xdebug می‌تواند به‌طور دقیق مسیر اجرای کد تا لحظه‌ی خطا را نشان دهد. برای فعال‌سازی، در php.ini:

xdebug.mode = develop
xdebug.show_error_trace = 1
xdebug.show_exception_trace = 1

این تنظیمات، در محیط استیجینگ بسیار مفید است ولی در محیط تولید می‌تواند سرعت را کاهش دهد. حتماً قبل از فعال‌سازی، در محیط استیجینگ تست کنید. مباحث مرتبط در دیباگ کدهای سفارشی وردپرس باز شده است.

تحلیل استاتیک کد

ابزارهایی مثل PHPStan یا Psalm می‌توانند کد را قبل از اجرا تحلیل کنند و موارد بالقوه تقسیم بر صفر را شناسایی کنند:

vendor/bin/phpstan analyse --level=8 /path/to/plugin
vendor/bin/psalm --find-unused-code=false /path/to/plugin

این ابزارها در پروژه‌های بزرگ بسیار مفید هستند و می‌توانند قبل از بروز خطا در محیط تولید، آن را شناسایی کنند. مباحث مرتبط در یادگیری حرفه‌ای کدنویسی وردپرس باز شده است.

بررسی با try-catch

اگر می‌خواهید دقیقاً بدانید خطا از کجا می‌آید، می‌توانید کد را در try-catch بپیچید:

try {
    $result = $a / $b;
} catch (DivisionByZeroError $e) {
    error_log('Division by zero: ' . $e->getMessage());
    error_log('File: ' . $e->getFile() . ' Line: ' . $e->getLine());
    $result = 0;
}

این الگو برای عیب‌یابی موقت بسیار مفید است، ولی به‌عنوان راه‌حل دائمی، بهتر است کد را با الگوهای دفاعی بازنویسی کنید که در بخش‌های بعدی این مقاله می‌آید.

فرمول‌های درصدی و محاسبات میانگین

فرمول‌های درصدی و محاسبات میانگین، شایع‌ترین بستر بروز خطای Division by zero در پروژه‌های واقعی هستند:

الگوی ناامن فرمول درصدی

کد زیر، در نگاه اول سالم به‌نظر می‌رسد ولی اگر $total صفر باشد، خطا می‌دهد:

function calculate_percentage($current, $total) {
    return ($current / $total) * 100;
}

echo calculate_percentage(5, 0); // خطای Division by zero

الگوی امن با بررسی صفر

نسخه‌ی امن این تابع، قبل از تقسیم، مقدار مخرج را بررسی می‌کند:

function calculate_percentage($current, $total) {
    if ($total == 0) {
        return 0;
    }
    return ($current / $total) * 100;
}

الگوی پیشرفته با بررسی نوع داده

نسخه‌ی پیشرفته‌تر، نوع داده را هم بررسی می‌کند:

function calculate_percentage($current, $total) {
    if (!is_numeric($current) || !is_numeric($total)) {
        return 0;
    }
    if ((float) $total === 0.0) {
        return 0;
    }
    return ($current / $total) * 100;
}

الگوی بسیار پیشرفته با try-catch

برای کدهای حساس، استفاده از try-catch توصیه می‌شود:

function calculate_percentage($current, $total) {
    try {
        if (!is_numeric($current) || !is_numeric($total)) {
            throw new InvalidArgumentException('Arguments must be numeric');
        }
        if ((float) $total === 0.0) {
            throw new InvalidArgumentException('Total cannot be zero');
        }
        return ($current / $total) * 100;
    } catch (InvalidArgumentException | DivisionByZeroError $e) {
        error_log('calculate_percentage error: ' . $e->getMessage());
        return 0;
    }
}

محاسبه میانگین با تعداد صفر

محاسبه‌ی میانگین هم یکی از شایع‌ترین موارد است. نمونه‌ی ناامن:

$ratings = get_post_meta($post_id, 'ratings', true);
$average = array_sum($ratings) / count($ratings); // اگر آرایه خالی باشد، خطا

نمونه‌ی امن:

$ratings = get_post_meta($post_id, 'ratings', true);
if (empty($ratings) || !is_array($ratings)) {
    $average = 0;
} else {
    $count = count($ratings);
    $average = $count > 0 ? array_sum($ratings) / $count : 0;
}

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

یکی از ریشه‌های ناپیدا در خطای Division by zero، مقادیر خالی از دیتابیس است:

مشکل با get_post_meta

اگر از get_post_meta برای دریافت یک مقدار عددی استفاده می‌کنید و متا وجود ندارد، نتیجه false یا رشته‌ی خالی است:

$views = get_post_meta($post_id, 'view_count', true); // اگر وجود نداشته باشد، false برمی‌گرداند
$total = get_post_meta('global', 'total_views', true);
$percentage = ($views / $total) * 100; // خطا اگر $total صفر یا false باشد

راه‌حل با مقدار پیش‌فرض

همیشه مقدار پیش‌فرض عددی برای get_post_meta تعیین کنید:

$views = (int) get_post_meta($post_id, 'view_count', true);
$total = (int) get_post_meta('global', 'total_views', true);
if ($total === 0) {
    $percentage = 0;
} else {
    $percentage = ($views / $total) * 100;
}

مشکل با کوئری‌های wpdb

در کوئری‌های مستقیم دیتابیس، اگر مقدار برنگردد، نتیجه null است که در محاسبات به‌عنوان صفر در نظر گرفته می‌شود:

global $wpdb;
$total = $wpdb->get_var("SELECT SUM(amount) FROM {$wpdb->prefix}orders");
$count = $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}orders");
$average = $total / $count; // خطا اگر $count صفر باشد

راه‌حل امن:

global $wpdb;
$total = (float) $wpdb->get_var("SELECT SUM(amount) FROM {$wpdb->prefix}orders");
$count = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->prefix}orders");
$average = $count > 0 ? $total / $count : 0;

مباحث مرتبط با دیتابیس وردپرس در تأثیر دیتابیس بر سرعت سایت باز شده است.

مشکل با متغیرهای undefined

اگر متغیری تعریف نشده باشد و در محاسبه استفاده شود، PHP 8 خطای Warning می‌دهد و مقدار null را در نظر می‌گیرد که در محاسبات صفر می‌شود:

// اگر $total قبلاً تعریف نشده باشد
$percentage = ($views / $total) * 100; // Warning + DivisionByZeroError در PHP 8

راه‌حل: همیشه متغیرها را قبل از استفاده تعریف کنید یا از isset() استفاده کنید:

$total = isset($total) ? (int) $total : 0;
if ($total > 0) {
    $percentage = ($views / $total) * 100;
} else {
    $percentage = 0;
}

توابع آرایه‌ای و خروجی غیرمنتظره

توابع آرایه‌ای در PHP، در بعضی موارد خروجی‌های غیرمنتظره‌ای می‌دهند که در محاسبات باعث خطا می‌شوند:

تابع count روی آرایه‌ی خالی

count() روی آرایه‌ی خالی صفر برمی‌گرداند که در محاسبه‌ی میانگین باعث خطا می‌شود. راه‌حل: همیشه قبل از تقسیم، بررسی کنید:

$items = get_posts(['post_type' => 'product', 'numberposts' => -1]);
$count = count($items);
$total_price = array_sum(array_map(function($item) {
    return (float) get_post_meta($item->ID, '_price', true);
}, $items));

$average = $count > 0 ? $total_price / $count : 0;

تابع array_sum روی آرایه‌ی حاوی رشته

اگر آرایه حاوی رشته‌های غیرعددی باشد، array_sum() ممکن است نتیجه‌ی غیرمنتظره بدهد. راه‌حل: قبل از جمع، مقادیر را به عدد تبدیل کنید:

$prices = ['100', '200', 'abc', null];
$numeric_prices = array_filter($prices, 'is_numeric');
$numeric_prices = array_map('floatval', $numeric_prices);
$sum = array_sum($numeric_prices);
$count = count($numeric_prices);
$average = $count > 0 ? $sum / $count : 0;

تابع array_reduce و مقدار اولیه

اگر array_reduce() بدون مقدار اولیه استفاده شود و آرایه خالی باشد، نتیجه null است:

// ناامن
$total = array_reduce($items, function($carry, $item) {
    return $carry + $item['price'];
});
$average = $total / count($items); // خطا اگر $items خالی باشد

// امن
$total = array_reduce($items, function($carry, $item) {
    return $carry + $item['price'];
}, 0);
$average = count($items) > 0 ? $total / count($items) : 0;

تابع array_filter و تغییر تعداد عناصر

اگر از array_filter() استفاده می‌کنید، توجه داشته باشید که تعداد عناصر آرایه کاهش می‌یابد. اگر بعد از فیلتر از count() استفاده شود، ممکن است نتیجه صفر باشد:

$valid_items = array_filter($items, function($item) {
    return $item['price'] > 0;
});
$total = array_sum(array_column($valid_items, 'price'));
$count = count($valid_items);
$average = $count > 0 ? $total / $count : 0;

خطای Division by zero در ووکامرس

ووکامرس، به‌دلیل ماهیت محاسباتی‌اش (قیمت، مالیات، تخفیف، میانگین امتیاز)، یکی از شایع‌ترین بسترهای خطای Division by zero است:

محاسبه میانگین امتیاز محصول

در ووکامرس، میانگین امتیاز محصول با فرمول (total_rating / review_count) محاسبه می‌شود. اگر محصول دیدگاهی نداشته باشد، review_count صفر است و این خطا رخ می‌دهد. اگرچه هسته‌ی ووکامرس این مورد را مدیریت می‌کند، ولی افزونه‌های سفارشی ممکن است این محافظت را نداشته باشند. مباحث مرتبط در سفارشی‌سازی صفحه محصول در ووکامرس باز شده است.

محاسبه تخفیف درصدی

در محاسبه‌ی درصد تخفیف، اگر قیمت اصلی صفر باشد، خطا رخ می‌دهد:

// ناامن
$discount_percentage = (($regular_price - $sale_price) / $regular_price) * 100;

// امن
$discount_percentage = $regular_price > 0 
    ? (($regular_price - $sale_price) / $regular_price) * 100 
    : 0;

محاسبه نرخ تبدیل فروشگاه

اگر گزارش فروشگاه روی ماهی با صفر بازدید یا صفر سفارش محاسبه شود، خطا رخ می‌دهد:

// ناامن
$conversion_rate = ($orders / $visits) * 100;

// امن
$conversion_rate = $visits > 0 ? ($orders / $visits) * 100 : 0;

محاسبه میانگین سفارش مشتری

// ناامن
$customer_order_count = wc_get_customer_order_count($customer_id);
$total_spent = wc_get_customer_total_spent($customer_id);
$average_order = $total_spent / $customer_order_count;

// امن
$customer_order_count = wc_get_customer_order_count($customer_id);
$total_spent = wc_get_customer_total_spent($customer_id);
$average_order = $customer_order_count > 0 ? $total_spent / $customer_order_count : 0;

مباحث مرتبط با ووکامرس در مدیریت مشتریان در ووکامرس و مدیریت سفارش‌ها در ووکامرس باز شده است.

افزونه‌های گزارش‌گیری ووکامرس

افزونه‌های گزارش‌گیری مثل WooCommerce Analytics یا افزونه‌های تخصصی گزارش، معمولاً محاسبات پیچیده دارند و ممکن است در شرایط خاص به تقسیم بر صفر برسند. اگر این خطا را در گزارش‌ها می‌بینید، اول افزونه‌ی گزارش را به‌روزرسانی کنید.

الگوهای دفاعی و کد امن

برای جلوگیری از خطای Division by zero، مجموعه‌ای از الگوهای دفاعی وجود دارد که در پروژه‌های واقعی استفاده می‌کنم:

الگوی اول: بررسی قبل از تقسیم

ساده‌ترین الگو، بررسی مخرج قبل از تقسیم است:

if ($divisor != 0) {
    $result = $dividend / $divisor;
} else {
    $result = 0;
}

الگوی دوم: استفاده از عملگر سه‌تایی

برای کدهای کوتاه، عملگر سه‌تایی گزینه‌ی تمیزی است:

$result = $divisor != 0 ? $dividend / $divisor : 0;

الگوی سوم: تابع کمکی سفارشی

برای استفاده‌ی مکرر، یک تابع کمکی بسازید:

function safe_divide($dividend, $divisor, $default = 0) {
    if (!is_numeric($dividend) || !is_numeric($divisor)) {
        return $default;
    }
    if ((float) $divisor === 0.0) {
        return $default;
    }
    return $dividend / $divisor;
}

// استفاده
$percentage = safe_divide($current, $total) * 100;
$average = safe_divide($sum, $count);

الگوی چهارم: استفاده از کلاس کمکی

برای پروژه‌های بزرگ، یک کلاس کمکی بسازید:

class SafeMath {
    public static function divide($dividend, $divisor, $default = 0) {
        if (!is_numeric($dividend) || !is_numeric($divisor)) {
            return $default;
        }
        if ((float) $divisor === 0.0) {
            return $default;
        }
        return $dividend / $divisor;
    }
    
    public static function percentage($current, $total, $default = 0) {
        return self::divide($current, $total, $default) * 100;
    }
    
    public static function average(array $values, $default = 0) {
        if (empty($values)) {
            return $default;
        }
        $numeric_values = array_filter($values, 'is_numeric');
        if (empty($numeric_values)) {
            return $default;
        }
        return array_sum($numeric_values) / count($numeric_values);
    }
}

// استفاده
$percentage = SafeMath::percentage($current, $total);
$average = SafeMath::average($ratings);

الگوی پنجم: تبدیل صریح نوع

قبل از هر تقسیم، نوع داده را صریحاً تبدیل کنید:

$dividend = (float) $dividend;
$divisor = (float) $divisor;

if ($divisor !== 0.0) {
    $result = $dividend / $divisor;
} else {
    $result = 0;
}

توجه داشته باشید که مقایسه با !== (به‌جای !=) امن‌تر است چون نوع داده را هم بررسی می‌کند. مباحث مرتبط در کار با آرایه‌ها در PHP باز شده است.

الگوی ششم: استفاده از try-catch

برای کدهای حساس، استفاده از try-catch توصیه می‌شود:

try {
    if ((float) $divisor === 0.0) {
        throw new DivisionByZeroError('Divisor is zero');
    }
    $result = $dividend / $divisor;
} catch (DivisionByZeroError $e) {
    error_log('Division error: ' . $e->getMessage());
    $result = 0;
}

مقایسه الگوها

الگومزیتمناسب برای
بررسی سادهسریع و سادهکدهای کوتاه و مشخص
عملگر سه‌تاییخوانا و مختصرمحاسبات یک‌خطی
تابع کمکیقابل استفاده مجددپروژه‌های متوسط
کلاس کمکیسازمان‌یافته و قابل تستپروژه‌های بزرگ
try-catchمدیریت خطای پیشرفتهکدهای حساس

مدیریت پیشرفته خطا و بازیابی

علاوه بر الگوهای دفاعی، مدیریت پیشرفته خطا می‌تواند از بحران‌های بزرگ جلوگیری کند:

گوش دادن به رویداد خطا

با تنظیم error handler سفارشی، می‌توانید خطای Division by zero را قبل از رسیدن به کاربر، مدیریت کنید:

set_error_handler(function($errno, $errstr, $errfile, $errline) {
    if ($errno === E_WARNING && strpos($errstr, 'Division by zero') !== false) {
        error_log("Division by zero detected: {$errfile}:{$errline}");
        return true; // جلوگیری از نمایش خطای پیش‌فرض
    }
    return false;
});

توجه داشته باشید که این روش، در PHP 8 کار نمی‌کند چون DivisionByZeroError یک استثناست، نه یک Warning. برای PHP 8، باید از set_exception_handler استفاده کنید.

گوش دادن به استثناها

set_exception_handler(function($exception) {
    if ($exception instanceof DivisionByZeroError) {
        error_log("Division by zero: " . $exception->getMessage());
        // نمایش پیام دوستانه به کاربر
        return;
    }
    // مدیریت سایر استثناها
    throw $exception;
});

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

لاگ‌گیری سفارشی

برای پیگیری خطاهای مکرر، یک لاگ‌گیری سفارشی بسازید:

function log_division_error($context, $message) {
    $log_entry = sprintf(
        "[%s] Division error in %s: %s
",
        date('Y-m-d H:i:s'),
        $context,
        $message
    );
    error_log($log_entry, 3, WP_CONTENT_DIR . '/division-errors.log');
}

// استفاده
if ($divisor == 0) {
    log_division_error('my_function', "dividend={$dividend}");
    $result = 0;
}

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

بازگردانی سایت و اولویت‌بندی

بعد از پیدا کردن ریشه، نوبت به بازگردانی سایت می‌رسد. ترتیب اولویت‌بندی من در پروژه‌های واقعی:

  1. بازگردانی سریع دسترسی: اگر سایت کامل سفید است، از طریق FTP پوشه‌ی افزونه‌ی مشکوک را موقتاً تغییر نام دهید.
  2. بازگرداندن کد قبلی: اگر خطا بعد از تغییر کد سفارشی ظاهر شده، از بکاپ قبلی استفاده کنید.
  3. رفع کد معیوب: با استفاده از الگوهای دفاعی، کد را بازنویسی کنید.
  4. افزودن try-catch: برای کدهای حساس، try-catch اضافه کنید.
  5. بررسی لاگ و مستندسازی: ریشه، روش تشخیص و راه‌حل را ثبت کنید.
  6. اجرای تحلیل استاتیک: با PHPStan یا Psalm، کد را بررسی کنید تا خطاهای مشابه در آینده جلوگیری شوند.

پایش مستمر و پیشگیری

بعد از رفع، مهم‌تر از رفع، پیشگیری است. پنج سطح پایش توصیه می‌کنم:

سطح اول: تحلیل استاتیک کد

ماهی یک‌بار، کد سفارشی خود را با PHPStan یا Psalm تحلیل کنید. این ابزارها می‌توانند خطاهای بالقوه را قبل از وقوع شناسایی کنند:

vendor/bin/phpstan analyse --level=8 /path/to/plugin

سطح دوم: پایش لاگ PHP

لاگ PHP را هفتگی بررسی کنید. اگر خطاهای Division by zero به‌طور مکرر دیده می‌شود، ریشه را قبل از بحران پیدا کنید. مباحث مرتبط در بررسی خطاهای سرور در لاگ‌ها باز شده است.

سطح سوم: تست‌های واحد

برای توابع محاسباتی مهم، تست‌های واحد بنویسید که شرایط مرزی (مثل مخرج صفر) را هم بررسی کنند:

public function test_safe_divide_with_zero() {
    $this->assertEquals(0, SafeMath::divide(10, 0));
}

public function test_safe_percentage_with_zero_total() {
    $this->assertEquals(0, SafeMath::percentage(5, 0));
}

مباحث مرتبط در تست و دیباگ پروژه‌های وردپرس باز شده است.

سطح چهارم: پایش خطاهای کاربر

ابزارهایی مثل Sentry یا Rollbar می‌توانند خطاهای کاربران واقعی را ثبت کنند. این ابزارها خطاهایی را نشان می‌دهند که در تست خودتان اتفاق نمی‌افتد ولی در دستگاه‌ها یا مرورگرهای خاص کاربران رخ می‌دهد.

سطح پنجم: بکاپ منظم قبل از تغییرات

قبل از هر تغییر در کد سفارشی، بکاپ کامل بگیرید. مباحث مرتبط در پشتیبان‌گیری از سایت وردپرس باز شده است.

پرسش‌های پرتکرار درباره خطای Division by zero در PHP

تفاوت Warning و DivisionByZeroError در PHP چیست؟

در PHP 7، تقسیم بر صفر به‌صورت Warning نمایش داده می‌شد و اجرای اسکریپت ادامه می‌یافت (با بازگشت مقدار false یا INF). در PHP 8، این خطا به یک DivisionByZeroError ارتقا یافته که یک استثنای کشنده است و اجرای اسکریپت را کاملاً متوقف می‌کند. این تفاوت، در مهاجرت به PHP 8 بسیار مهم است.

آیا استفاده از @ برای مخفی کردن خطا توصیه می‌شود؟

خیر. استفاده از عملگر @ برای مخفی کردن خطا، راه‌حل موقتی است و خطاهای دیگر را هم مخفی می‌کند. راه‌حل اصولی، بررسی مقدار قبل از تقسیم و استفاده از الگوهای دفاعی است.

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

با فعال‌سازی WP_DEBUG_LOG در wp-config.php، پیام کامل خطا با نام فایل و شماره خط در wp-content/debug.log ثبت می‌شود. همچنین با ابزار Xdebug می‌توانید مسیر اجرا تا لحظه‌ی خطا را به‌طور دقیق ببینید.

آیا این خطا می‌تواند در هسته‌ی وردپرس رخ دهد؟

به‌طور مستقیم، خیر. هسته‌ی وردپرس در سال‌های اخیر محاسبات خود را با الگوهای دفاعی نوشته است. اگر خطای Division by zero می‌بینید، ریشه در یک افزونه، قالب یا کد سفارشی است که با هسته در تعامل است.

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

روش حذف تدریجی دقیق‌ترین راه است: ابتدا تمام افزونه‌ها را غیرفعال کنید و سایت را تست کنید. سپس افزونه‌ها را یکی‌یکی فعال کنید تا مقصر پیدا شود. مباحث مرتبط در پیدا کردن افزونه‌ی مشکل‌ساز وردپرس باز شده است.

آیا خطای Division by zero می‌تواند ناشی از هک شدن سایت باشد؟

در موارد نادر بله. اگر هکر کدی را تغییر دهد که شامل تقسیم باشد، ممکن است این خطا ظاهر شود. برای اطمینان، روش تشخیص هک شدن سایت را بررسی کنید.

چطور از وقوع این خطا در آینده جلوگیری کنم؟

سه اقدام: اول، استفاده از الگوهای دفاعی برای تمام محاسباتی که مخرج دارند. دوم، اجرای تحلیل استاتیک کد با PHPStan یا Psalm. سوم، نوشتن تست‌های واحد برای توابع محاسباتی که شرایط مرزی را بررسی کنند.

آیا intdiv نیز می‌تواند خطای Division by zero بدهد؟

بله. تابع intdiv() در PHP 7 و 8، اگر مخرج صفر باشد، DivisionByZeroError پرتاب می‌کند. برای استفاده‌ی امن:

if ($divisor !== 0) {
    $result = intdiv($dividend, $divisor);
} else {
    $result = 0;
}

چطور خطای Division by zero را در ووکامرس مدیریت کنم؟

دو رویکرد: اول، استفاده از توابع داخلی ووکامرس که محافظت دارند (مثل wc_get_price_to_display()). دوم، بررسی مقادیر قبل از تقسیم در کد سفارشی. مباحث مرتبط در مدیریت مشتریان در ووکامرس باز شده است.

آیا محاسبات اعشاری (float) هم می‌تواند باعث این خطا شود؟

بله. اگر مخرج از نوع float باشد و مقدارش بسیار نزدیک به صفر (مثل 0.00001) باشد، نتیجه‌ی تقسیم می‌تواند بسیار بزرگ یا INF شود. در PHP 8، فقط اگر مخرج دقیقاً صفر باشد DivisionByZeroError رخ می‌دهد، ولی مقادیر بسیار کوچک می‌توانند نتایج غیرمنطقی بدهند:

$divisor = 0.00001;
$result = 10 / $divisor; // نتیجه: 1000000 (غیرمنطقی ولی نه خطا)

برای جلوگیری، از بررسی دقیق‌تر استفاده کنید:

if (abs($divisor) < PHP_FLOAT_EPSILON) {
    $result = 0;
} else {
    $result = $dividend / $divisor;
}

آیا استفاده از تابع is_numeric کافی است؟

نه، is_numeric() فقط بررسی می‌کند که مقدار عددی است یا نه. اگر مقدار عددی صفر باشد، is_numeric() مقدار true برمی‌گرداند ولی تقسیم همچنان خطا می‌دهد. باید هر دو شرط را بررسی کنید:

if (is_numeric($divisor) && (float) $divisor !== 0.0) {
    $result = $dividend / $divisor;
} else {
    $result = 0;
}

نکته‌های میدانی از رفع خطای تقسیم بر صفر

در پایان این مقاله، چند نکته‌ای را می‌گویم که در مستندات رسمی کم‌تر به آن‌ها اشاره می‌شود ولی در پروژه‌های واقعی بارها به کارم آمده:

نخست: در PHP 8، خطای Division by zero دیگر یک هشدار ساده نیست؛ یک استثنای کشنده است. اگر پروژه‌ای روی PHP 7 با این خطا کار می‌کرد و به PHP 8 مهاجرت کرده، به‌احتمال زیاد با یک بحران جدی مواجه می‌شود. توصیه می‌کنم قبل از مهاجرت به PHP 8، حتماً کد خود را با PHPStan تحلیل کنید و موارد تقسیم را بررسی کنید.

دوم: در پروژه‌های ووکامرس، محاسبه‌ی میانگین امتیاز، درصد تخفیف و نرخ تبدیل، شایع‌ترین بسترهای خطای Division by zero هستند. اگر افزونه‌ی گزارش‌گیری یا داشبورد آماری دارید، حتماً این محاسبات را با الگوهای دفاعی بازنویسی کنید. تجربه‌ی من نشان داده که در سایت‌های فروشگاهی با چند هزار محصول، احتمال بروز این خطا در گزارش‌های ماهانه بسیار بالاست.

سوم: یک تابع کمکی ساده برای تقسیم امن، در پروژه‌های شما می‌تواند ده‌ها ساعت عیب‌یابی را ذخیره کند. اگر تابع safe_divide() را در یک فایل کمکی قرار دهید و از آن در تمام محاسبات استفاده کنید، نه‌تنها از خطای Division by zero جلوگیری می‌کنید، بلکه کد شما خواناتر و قابل نگهداری‌تر می‌شود. تجربه‌ی من نشان داده که این تابع ساده، در طول سال، بیش از هر ابزار دیگری از بحران‌های سایت جلوگیری کرده است.

در تجربه‌ی چندساله‌ام روی پروژه‌های وردپرسی و PHP، الگویی که بارها تکرار شده این است که خطای Division by zero تقریباً همیشه در یکی از پنج لایه ریشه دارد: فرمول‌های درصدی، مقادیر خالی دیتابیس، توابع آرایه‌ای، منطق ووکامرس یا تغییرات PHP 8. تشخیص سریع این لایه، از هر راه‌حل آماده مؤثرتر است. اگر ابزارهای تشخیصی و تحلیل استاتیک را در اختیار داشته باشید، این خطا از یک بحران ترسناک به یک تمرین روتین تبدیل می‌شود.

اگر روی پروژه خود با نوعی از خطای Division by zero مواجه شده‌اید که در این مقاله پوشش داده نشده، یا اگر راه‌حل متفاوتی پیدا کرده‌اید که به کارتان آمده، برای من جالب است آن را بشنوم. مشخصاً اگر پیام کامل خطا (با نام فایل و شماره خط) یا لاگ PHP که ریشه‌ی واقعی را نشان داد، با خوانندگان دیگر به اشتراک بگذارید؛ این یادداشت‌های دقیق، برای توسعه‌دهنده‌ی بعدی ساعت‌ها زمان صرفه‌جویی می‌کنند. 🧮