PHP money_format() i WAMP = problem?

Do formatowania wartości numerycznych na pieniążka mamy bardzo ładną, oddaną w wersji 4 PHP funkcję money_format(), no ale jest pewne ‚ale’ o którym muszę się rozpisać, bo jakże by inaczej. Problem który mnie dotknął polegał na potrzebie przygotowania maszyny testowej w lokalnym środowisku. Ze względu na zabezpieczenia sieci lokalnej i brak możliwości prezentacji systemu Agility zdalnie u Klienta muszę postawić na swoim notebooku WAMP’a. Wszystko ładnie, pięknie, gładko. PHP.ini skonfigurowane, httpd.conf również już chodzi na :81 z Virtualhostem, mod_rewrite uruchomione, ot całe 15 min pracy razem z pobraniem bibliotek. Pojawił się jednak pewien problem, otóż jak można się wczytać na podanej wyżej stronie funkcji:

The function money_format() is only defined if the system has strfmon capabilities. For example, Windows does not, so money_format() is undefined in Windows.

I tutaj już można zwijać manaty i napierdzielać głową w ścianę. Znowu o wspaniały Windows ma problemy z interpretacją PHP i czegoś nie obsługuje! Jest jednak na to pewne obejście. Wczytując się w komentarze pod postem znalazł się wspaniały człowiek, Rafael M. Salvioni,  który zmarnował już trochę czasu i wkleił kod w/w funkcji z obejściem bólu dupy Windowsa.

<?php
/*
That it is an implementation of the function money_format for the
platforms that do not it bear. 

The function accepts to same string of format accepts for the
original function of the PHP.

(Sorry. my writing in English is very bad)

The function is tested using PHP 5.1.4 in Windows XP
and Apache WebServer.
*/
function money_format($format, $number)
{
    $regex  = '/%((?:[\^!\−]|\+|\(|\=.)*)([0−9]+)?'.
              '(?:#([0−9]+))?(?:\.([0−9]+))?([in%])/';
    if (setlocale(LC_MONETARY, 0) == 'C') {
        setlocale(LC_MONETARY, '');
    }
    $locale = localeconv();
    preg_match_all($regex, $format, $matches, PREG_SET_ORDER);
    foreach ($matches as $fmatch) {
        $value = floatval($number);
        $flags = array(
            'fillchar'  => preg_match('/\=(.)/', $fmatch[1], $match) ?
                           $match[1] : ' ',
            'nogroup'   => preg_match('/\^/', $fmatch[1]) > 0,
            'usesignal' => preg_match('/\+|\(/', $fmatch[1], $match) ?
                           $match[0] : '+',
            'nosimbol'  => preg_match('/\!/', $fmatch[1]) > 0,
            'isleft'    => preg_match('/\−/', $fmatch[1]) > 0
        );
        $width      = trim($fmatch[2]) ? (int)$fmatch[2] : 0;
        $left       = trim($fmatch[3]) ? (int)$fmatch[3] : 0;
        $right      = trim($fmatch[4]) ? (int)$fmatch[4] : $locale['int_frac_digits'];
        $conversion = $fmatch[5];

        $positive = true;
        if ($value < 0) {
            $positive = false;
            $value  *= −1;
        }
        $letter = $positive ? 'p' : 'n';

        $prefix = $suffix = $cprefix = $csuffix = $signal = '';

        $signal = $positive ? $locale['positive_sign'] : $locale['negative_sign'];
        switch (true) {
            case $locale["{$letter}_sign_posn"] == 1 && $flags['usesignal'] == '+':
                $prefix = $signal;
                break;
            case $locale["{$letter}_sign_posn"] == 2 && $flags['usesignal'] == '+':
                $suffix = $signal;
                break;
            case $locale["{$letter}_sign_posn"] == 3 && $flags['usesignal'] == '+':
                $cprefix = $signal;
                break;
            case $locale["{$letter}_sign_posn"] == 4 && $flags['usesignal'] == '+':
                $csuffix = $signal;
                break;
            case $flags['usesignal'] == '(':
            case $locale["{$letter}_sign_posn"] == 0:
                $prefix = '(';
                $suffix = ')';
                break;
        }
        if (!$flags['nosimbol']) {
            $currency = $cprefix .
                        ($conversion == 'i' ? $locale['int_curr_symbol'] : $locale['currency_symbol']) .
                        $csuffix;
        } else {
            $currency = '';
        }
        $space  = $locale["{$letter}_sep_by_space"] ? ' ' : '';

        $value = number_format($value, $right, $locale['mon_decimal_point'],
                 $flags['nogroup'] ? '' : $locale['mon_thousands_sep']);
        $value = @explode($locale['mon_decimal_point'], $value);

        $n = strlen($prefix) + strlen($currency) + strlen($value[0]);
        if ($left > 0 && $left > $n) {
            $value[0] = str_repeat($flags['fillchar'], $left − $n) . $value[0];
        }
        $value = implode($locale['mon_decimal_point'], $value);
        if ($locale["{$letter}_cs_precedes"]) {
            $value = $prefix . $currency . $space . $value . $suffix;
        } else {
            $value = $prefix . $value . $space . $currency . $suffix;
        }
        if ($width > 0) {
            $value = str_pad($value, $width, $flags['fillchar'], $flags['isleft'] ?
                     STR_PAD_RIGHT : STR_PAD_LEFT);
        }

        $format = str_replace($fmatch[0], $value, $format);
    }
    return $format;
}

?>

Funkcję wystarczy zarejestrować teraz w naszym projekcie. Pamiętaj jednak żeby sprawdzić czy funkcja istnieje przed jej dodaniem. Oszczędzi Ci to problemów z różnicami pomiędzy Windowsem i Linuxem. Funkcja zostanie zarejestrowana tylko jeżeli nie istnieje:

if (!function_exists('money_format')) { 
function money_format($format, $number) {

... tutaj kod całej funkcji ...
}
}

Update:

Po zastosowaniu funkcji wyszedł jeden problem z setlocale(). Dla polskich piździelników zagubiło się kodowanie (a raczej jest zastosowane błędne), dlatego też rozszerzyłem funkcję o następujący fragment:

(...)
        $regex = '/%((?:[\^!\−]|\+|\(|\=.)*)([0−9]+)?' .
                '(?:#([0−9]+))?(?:\.([0−9]+))?([in%])/';
        if (setlocale(LC_MONETARY, 0) == 'C') {
            setlocale(LC_MONETARY, '');
        }

        $locale = localeconv();

// Poniżej zostało dodane

        $locale['mon_decimal_point'] = iconv('Windows−1252', 'UTF−8', $locale['mon_decimal_point']);
        $locale['mon_thousands_sep'] = iconv('Windows−1252', 'UTF−8', $locale['mon_thousands_sep']);
        $locale['thousands_sep'] = iconv('Windows−1252', 'UTF−8', $locale['thousands_sep']);
        $locale['thousands_sep'] = iconv('Windows−1252', 'UTF−8', $locale['thousands_sep']);
        $locale['decimal_point'] = iconv('Windows−1252', 'UTF−8', $locale['decimal_point']);

// Powyżej zostało dodane       

        preg_match_all($regex, $format, $matches, PREG_SET_ORDER);

(...)

Kasztelan Paweł

Programista samouk, zakochany w ZF i Laravel, szerzący opinię że PHP + JS + HTML + CSS to są języki w których może zostać stworzona aplikacja równie dobra, a nawet lepsza od twardego klienta.