محاسبات روی اعداد رشته ای


ارسال در تاریخ ۲۲ مهر,۱۳۹۷



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

non well formed numeric value

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

$total = '76 trombones' * 2;

php کاراکترهای غیر عددی که در ادامه رشته آمده است را نادیده می گیرد و آن را تبدیل به عدد می کند. نتیجه کد بالا عدد 152 خواهد بود. 

اما از php 7.1 به بعد استفاده از اعداد رشته ای در محاسابت که در ادامه آن کاراکترهای غیر عددی آمده است یک خطای notice تولید می کند. 

به کدی که داریم سر می زنیم:

<?php

$max = ini_get('post_max_size');
echo $max;

function convertToBytes($val) {
    $val = trim($val);
    // Get last character and convert to lowercase
    $last = strtolower($val[strlen($val)-1]);
    if (in_array($last, array('g', 'm', 'k'))){
        // Use fall-through to calculate the number of bytes
        switch ($last) {
            case 'g':
                $val *= 1024;
            case 'm':
                $val *= 1024;
            case 'k':
                $val *= 1024;
        }
    }
    return $val;
}

 در این کد می خواهیم حداکثر اندازه مجاز برای آپلود فایل توسط متد post را بدست بیاریم. خب این اسکریپت مقدار post_max_size را از تنظیمات سرویس بدست میاره و در خط پایین نمایش می دهد. خب من این کد را اجرا می کنم. عددی که برای سرور لوکال من نمایش داده میشه 8M هست، به عبارت دیگر 8 مگابایت. اگر شما این اسکریپت را روی سرور خودتان اجرا کنید مقدار آن بستگی به تنظیمات سرویس شما دارد. این مقدار می تواند توسط مدیر سرور تغییر کند. 

حرف g اشاره به واحد گیگابایت و حرف k اشاره به کیلو بایت دارد. اما به جای استفاده از این واحدها برخی سرورها این مقدار را بر حسب بایت نمایش می دهند و برای اینکه کد ما در تمام سرورها کار کند ما باید مقدار post_max_size را به بایت تبدیل کنیم و برای این منظور از تابع convertToBytes که نوشتیم استفاده می کنیم. این تابع یک ورودی می گیرد، مقداری که می خواهید آن را تبدیل کنید.

ابتدا فضاهای خالی ابتدا و انتهای رشته را توسط تابع trim حذف می کنیم، سپس توسط تابع strlen کاراکتر انتهایی رشته را جدا می کنیم و پس از تبدیل آن به حرف کوچک این مقدار را در متغیر $last ذخیره می کنیم. 

سپس شرط ما شروع می شود که اگر مقدار متغیر $last در آرایه ای که حاوی سه مقدار g, m ,k هست وجود داشت سپس دستور شرطی switch شروع می شود.

بصورت معمول هر case در یک switch به یک break ختم می شود، اما در اینجا از break استفاده نکرده ایم. خب اگر کاراکتر آخر g هست، مقدار متغیر $val سه بار در عدد 1024 ضرب می شود و مقدار معادل بایت آن بدست می آید. اگر کاراکتر آخر m باشد مقدار $val دو بار در عدد 1024 ضرب می شود و اگر k باشد یک بار مقدار $val در عدد 1024 ضرب می شود و تابع به برگشت دادن مقدار $val تمام می شود. و اگر کاراکتر آخر هیچ کدام از مقادیر g, m یا k نبوده است، حتما مقدار آن به بایت بوده است و دیگر نیازی به تبدیل به بایت ندارد. این کد تا قبل از اینکه نسخه php سرور پایین تر از php 7.1 بوده بدون هیچ مشکلی کار کرده است. 

خب اجازه بدید این کد را روی سرور خودمان اجرا کنیم. نسخه php سرور لوکال من در حال حاضر 7.2.4 هست. 

کد را بصورت زیر ویرایش می کنم تا نتیجه را مشاهده کنیم:

$max = ini_get('post_max_size');
//echo $max;

$maxbytes = convertToBytes($max);
echo $maxbytes;

function convertToBytes($val) {
    $val = trim($val);
    // Get last character and convert to lowercase
    $last = strtolower($val[strlen($val)-1]);
    if (in_array($last, array('g', 'm', 'k'))){
        // Use fall-through to calculate the number of bytes
        switch ($last) {
            case 'g':
                $val *= 1024;
            case 'm':
                $val *= 1024;
            case 'k':
                $val *= 1024;
        }
    }
    return $val;
}

نتیجه بصورت زیر خواهد بود همراه با یک خطای notice

این خطا را به خاطر اینکه نسخه php سرور من 7.1 یا بالاتر است دریافت کردم. اگر شما از php نسخه پایینتر استفاده کنید یا اینکه خطاهای notice را در تنظیمات سرور خاموش کنید فقط نتیجه را دریافت می کنید و دیگر خبری از خطای notice نخواهد بود. 

خطاهای notice پایینترین سطح خطاها در php هستند و خیلی از توسعه دهندگان از آن چشم پوشی می کنند. اما من معتقدم که باید همیشه به خطاها توجه کنیم. اگر چه کد ما بدون ایراد کار کند. 

اولین ایده این است که کاراکتر آخر را قبل از استفاده در محاسبات حذف کنیم، این راه حل برای این مسئله جوابگو خواهد بود ولی این یک جواب قابل انعطاف نیست، در اینجا ما می دانیم که فقط باید یک کاراکتر آخر را حذف کنیم، اما در شرایطی که نمی دانیم چند کاراکتر آخر غیر عددی است چطور؟ 

ایده بعدی استفاده از عبارات با قاعده یا regular expression است، اما سوالات زیادی بوجود میاد، اگر ما فقط روی مقادیر integer کار می کنیم، جواب ساده خواهد بود، اما در مورد مقادیر اعشاری چطور؟ اگر عدد کمتر از 1 باشد آیا صفر قبل از ممیز وجود دارد یا خیر؟ در مورد اعداد منفی و مثبت چطور؟ خب ایده استفاده از عبارات با قاعده برای جدا کردن عدد از ابتدای رشته به خاطر پیچیده بودن منتفی هست.

تبدیل هایی که php بصورت نامحصوص انجام می دهد را implicit casting یا تبدیل ضمنی گفته می شود، تبدیل رشته $val به نوع عددی را php بصورت ضمنی انجام می دهد. خب به جای استفاده از تبدیل ضمنی چرا از تبدیل صریح یا explicit casting استفاده نکنیم. 

خب به کدی که داریم بر می گردیم، اگر بدانیم که مقدار $val به یکی از مقادیر g, m یا k ختم می شود ما باید آن را به نوع عددی تبدیل کنیم. خب در بلاک شرطی if یک خط کد اضافه می کنیم و مقدار $val را به مقدار عددی تبدیل می کنیم، برای تبدیل صریح یک مقدار قبل از آن مقدار یک پرانتز باز و بسته را می نویسیم و نوعی که می خواهیم متغیر یا مقدار به آن تبدیل شود را داخل پرانتز می نویسیم. برای اینکه کد ما هم مقادیر اعشاری و هم اعداد صحیح را تبدیل کنید در داخل پرانتز مقدار float را می نویسیم:

<?php

$max = ini_get('post_max_size');
//echo $max;

$maxbytes = convertToBytes($max);
echo $maxbytes;

function convertToBytes($val) {
    $val = trim($val);
    // Get last character and convert to lowercase
    $last = strtolower($val[strlen($val)-1]);
    if (in_array($last, array('g', 'm', 'k'))){
 		$val = (float) $val;
        // Use fall-through to calculate the number of bytes
        switch ($last) {
            case 'g':
                $val *= 1024;
            case 'm':
                $val *= 1024;
            case 'k':
                $val *= 1024;
        }
    }
    return $val;
}

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


برای ارسال نظر لطفا وارد شوید.