O number_format($v, 2, ',', '.') resolve os separadores e para por aí: ele não sabe pôr o R$ na frente nem escrever “mil duzentos e trinta e quatro reais” no rodapé do boleto. Quem faz isso é a classe NumberFormatter, que vem com a extensão intl e já entende pt-BR sem você escrever um único array de dezenas.
Moeda e extenso em cinco linhas
$moeda = new NumberFormatter('pt_BR', NumberFormatter::CURRENCY);
echo $moeda->formatCurrency(1234.5, 'BRL'); // R$ 1.234,50
$extenso = new NumberFormatter('pt_BR', NumberFormatter::SPELLOUT);
echo $extenso->format(1234); // mil duzentos e trinta e quatroPara o valor cheio, separe reais e centavos antes. Jogar 1234.50 direto no SPELLOUT devolve mil duzentos e trinta e quatro vírgula cinco, que não serve para nota nem para boleto:
$valor = 1234.50;
$centavos = (int) round(fmod($valor, 1) * 100);
$texto = $extenso->format((int) $valor) . ' reais';
if ($centavos > 0) {
$texto .= ' e ' . $extenso->format($centavos) . ' centavos';
}
echo $texto; // mil duzentos e trinta e quatro reais e cinquenta centavosDois tropeços que custam tempo
O primeiro é o erro Uncaught Error: Class "NumberFormatter" not found. Falta a extensão: sudo apt install php8.3-intl e depois sudo systemctl restart php8.3-fpm. Confirme com php -m | grep intl, e lembre que o CLI e o FPM podem carregar php.ini diferentes — funcionar no terminal não quer dizer funcionar no site.
O segundo é mais silencioso: o espaço entre o R$ e o número é um espaço não separável (U+00A0), não o espaço da barra. Se você compara a string com 'R$ 1.234,50' num teste, ou tenta um str_replace('R$ ', '', $s), nunca bate. Para voltar ao float use $moeda->parseCurrency($s, $cur), que devolve o número e ainda preenche $cur com BRL, em vez de mexer no texto na unha.