Advertisement
ferrarisp

class_extenso.php

Aug 21st, 2015
260
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 14.27 KB | None | 0 0
  1. <?php
  2. /**
  3.  * GExtenso class file
  4.  *
  5.  * @author Fausto Gonçalves Cintra (goncin) <goncin@gmail.com>
  6.  * @link http://devfranca.ning.com
  7.  * @link http://twitter.com/g0nc1n
  8.  * @license http://creativecommons.org/licenses/LGPL/2.1/deed.pt
  9.  */
  10.  
  11. /**
  12.  * GExtenso é uma classe que gera a representação por extenso de um número ou valor monetário.
  13.  *
  14.  * ATENÇÃO: A PÁGINA DE CÓDIGO DESTE ARQUIVO É UTF-8 (Unicode)!
  15.  *
  16.  * Sua implementação foi feita como prova de conceito, utilizando:
  17.  *
  18.  *
  19.  * <ul>
  20.  * <li>Métodos estáticos, implementando o padrão de projeto (<i>design pattern</i>) <b>SINGLETON</b>;</li>
  21.  * <li>Chamadas recursivas a métodos, minimizando repetições e mantendo o código enxuto;</li>
  22.  * <li>Uso de pseudoconstantes ('private static') diante das limitações das constantes de classe;</li>
  23.  * <li>Tratamento de erros por intermédio de exceções; e</li>
  24.  * <li>Utilização do phpDocumentor ({@link http://www.phpdoc.org}) para documentação do código fonte e
  25.  * geração automática de documentação externa.</li>
  26.  * </ul>
  27.  *
  28.  * <b>EXEMPLOS DE USO</b>
  29.  *
  30.  * Para obter o extenso de um número, utilize GExtenso::{@link numero}.
  31.  * <pre>
  32.  * echo GExtenso::numero(832); // oitocentos e trinta e dois
  33.  * echo GExtenso::numero(832, GExtenso::GENERO_FEM) // oitocentas e trinta e duas
  34.  * </pre>
  35.  *
  36.  * Para obter o extenso de um valor monetário, utilize GExtenso::{@link moeda}.
  37.  * <pre>
  38.  * // IMPORTANTE: veja nota sobre o parâmetro 'valor' na documentação do método!
  39.  * echo GExtenso::moeda(15402); // cento e cinquenta e quatro reais e dois centavos
  40.  * echo GExtenso::moeda(47); // quarenta e sete centavos
  41.  * echo GExtenso::moeda(357082, 2,
  42.  *   array('peseta', 'pesetas', GExtenso::GENERO_FEM),
  43.  *   array('cêntimo', 'cêntimos', GExtenso::GENERO_MASC));
  44.  *   // três mil, quinhentas e setenta pesetas e oitenta e dois cêntimos
  45.  * </pre>
  46.  *
  47.  * @author Fausto Gonçalves Cintra (goncin) <goncin@gmail.com>
  48.  * @version 0.1 2010-03-02
  49.  * @package GUtils
  50.  *
  51.  */
  52.  
  53.  class GExtenso {
  54.  
  55.   const NUM_SING = 0;
  56.   const NUM_PLURAL = 1;
  57.   const POS_GENERO = 2;
  58.   const GENERO_MASC = 0;
  59.   const GENERO_FEM = 1;
  60.  
  61.   const VALOR_MAXIMO = 999999999;
  62.  
  63.   /* Uma vez que o PHP não suporta constantes de classe na forma de matriz (array),
  64.     a saída encontrada foi declarar as strings numéricas como 'private static'.
  65.   */
  66.  
  67.   /* As unidades 1 e 2 variam em gênero, pelo que precisamos de dois conjuntos de strings (masculinas e femininas) para as unidades */
  68.   private static $UNIDADES = array(
  69.     self::GENERO_MASC => array(
  70.       1 => 'um',
  71.       2 => 'dois',
  72.       3 => 'três',
  73.       4 => 'quatro',
  74.       5 => 'cinco',
  75.       6 => 'seis',
  76.       7 => 'sete',
  77.       8 => 'oito',
  78.       9 => 'nove'
  79.     ),
  80.     self::GENERO_FEM => array(
  81.       1 => 'uma',
  82.       2 => 'duas',
  83.       3 => 'três',
  84.       4 => 'quatro',
  85.       5 => 'cinco',
  86.       6 => 'seis',
  87.       7 => 'sete',
  88.       8 => 'oito',
  89.       9 => 'nove'
  90.     )
  91.   );
  92.  
  93.   private static $DE11A19 = array(
  94.     11 => 'onze',
  95.     12 => 'doze',
  96.     13 => 'treze',
  97.     14 => 'quatorze',
  98.     15 => 'quinze',
  99.     16 => 'dezesseis',
  100.     17 => 'dezessete',
  101.     18 => 'dezoito',
  102.     19 => 'dezenove'
  103.   );
  104.  
  105.   private static $DEZENAS = array(
  106.     10 => 'dez',
  107.     20 => 'vinte',
  108.     30 => 'trinta',
  109.     40 => 'quarenta',
  110.     50 => 'cinquenta',
  111.     60 => 'sessenta',
  112.     70 => 'setenta',
  113.     80 => 'oitenta',
  114.     90 => 'noventa'
  115.   );
  116.  
  117.   private static $CENTENA_EXATA = 'cem';
  118.  
  119.   /* As centenas, com exceção de 'cento', também variam em gênero. Aqui também se faz
  120.     necessário dois conjuntos de strings (masculinas e femininas).
  121.   */
  122.  
  123.   private static $CENTENAS = array(
  124.     self::GENERO_MASC => array(
  125.       100 => 'cento',
  126.       200 => 'duzentos',
  127.       300 => 'trezentos',
  128.       400 => 'quatrocentos',
  129.       500 => 'quinhentos',
  130.       600 => 'seiscentos',
  131.       700 => 'setecentos',
  132.       800 => 'oitocentos',
  133.       900 => 'novecentos'
  134.     ),
  135.     self::GENERO_FEM => array(
  136.       100 => 'cento',
  137.       200 => 'duzentas',
  138.       300 => 'trezentas',
  139.       400 => 'quatrocentas',
  140.       500 => 'quinhentas',
  141.       600 => 'seiscentas',
  142.       700 => 'setecentas',
  143.       800 => 'oitocentas',
  144.       900 => 'novecentas'
  145.     )
  146.   );
  147.  
  148.   /* 'Mil' é invariável, seja em gênero, seja em número */
  149.   private static $MILHAR = 'mil';
  150.  
  151.   private static $MILHOES = array(
  152.     self::NUM_SING => 'milhão',
  153.     self::NUM_PLURAL => 'milhões'
  154.   );
  155.  
  156.  
  157.  
  158.  /**
  159.  * Gera a representação por extenso de um número inteiro, maior que zero e menor ou igual a GExtenso::VALOR_MAXIMO.
  160.  *
  161.  * @param int O valor numérico cujo extenso se deseja gerar
  162.  *
  163.  * @param int (Opcional; valor padrão: GExtenso::GENERO_MASC) O gênero gramatical (GExtenso::GENERO_MASC ou GExtenso::GENERO_FEM)
  164.  * do extenso a ser gerado. Isso possibilita distinguir, por exemplo, entre 'duzentos e dois homens' e 'duzentas e duas mulheres'.
  165.  *
  166.  * @return string O número por extenso
  167.  *
  168.  * @since 0.1 2010-03-02
  169.  */
  170.   public static function numero($valor, $genero = self::GENERO_MASC) {
  171.  
  172.     /* ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ---- */
  173.  
  174.     if(!is_numeric($valor))
  175.       throw new Exception("[Exceção em GExtenso::numero] Parâmetro \$valor não é numérico (recebido: '$valor')");
  176.  
  177.     else if($valor <= 0)
  178.       throw new Exception("[Exceção em GExtenso::numero] Parâmetro \$valor igual a ou menor que zero (recebido: '$valor')");
  179.  
  180.     else if($valor > self::VALOR_MAXIMO)
  181.       throw new Exception('[Exceção em GExtenso::numero] Parâmetro $valor deve ser um inteiro entre 1 e ' . self::VALOR_MAXIMO . " (recebido: '$valor')");
  182.  
  183.     else if($genero != self::GENERO_MASC && $genero != self::GENERO_FEM)
  184.       throw new Exception("Exceção em GExtenso: valor incorreto para o parâmetro \$genero (recebido: '$genero').");
  185.  
  186.     /* ----------------------------------------------- */
  187.  
  188.     else if($valor >= 1 && $valor <= 9)
  189.       return self::$UNIDADES[$genero][$valor]; // As unidades 'um' e 'dois' variam segundo o gênero
  190.  
  191.     else if($valor == 10)
  192.       return self::$DEZENAS[$valor];
  193.  
  194.     else if($valor >= 11 && $valor <= 19)
  195.       return self::$DE11A19[$valor];
  196.  
  197.     else if($valor >= 20 && $valor <= 99) {
  198.       $dezena = $valor - ($valor % 10);
  199.       $ret = self::$DEZENAS[$dezena];
  200.       /* Chamada recursiva à função para processar $resto se este for maior que zero.
  201.        * O conectivo 'e' é utilizado entre dezenas e unidades.
  202.        */
  203.       if($resto = $valor - $dezena) $ret .= ' e ' . self::numero($resto, $genero);
  204.       return $ret;
  205.     }
  206.  
  207.     else if($valor == 100) {
  208.       return self::$CENTENA_EXATA;
  209.     }
  210.  
  211.     else if($valor >= 101 && $valor <= 999) {
  212.       $centena = $valor - ($valor % 100);
  213.       $ret = self::$CENTENAS[$genero][$centena]; // As centenas (exceto 'cento') variam em gênero
  214.       /* Chamada recursiva à função para processar $resto se este for maior que zero.
  215.        * O conectivo 'e' é utilizado entre centenas e dezenas.
  216.        */
  217.       if($resto = $valor - $centena) $ret .= ' e ' . self::numero($resto, $genero);
  218.       return $ret;
  219.     }
  220.  
  221.     else if($valor >= 1000 && $valor <= 999999) {
  222.       /* A função 'floor' é utilizada para encontrar o inteiro da divisão de $valor por 1000,
  223.        * assim determinando a quantidade de milhares. O resultado é enviado a uma chamada recursiva
  224.        * da função. A palavra 'mil' não se flexiona.
  225.        */
  226.       $milhar = floor($valor / 1000);
  227.       $ret = self::numero($milhar, self::GENERO_MASC) . ' ' . self::$MILHAR; // 'Mil' é do gênero masculino
  228.       $resto = $valor % 1000;
  229.       /* Chamada recursiva à função para processar $resto se este for maior que zero.
  230.        * O conectivo 'e' é utilizado entre milhares e números entre 1 e 99, bem como antes de centenas exatas.
  231.        */
  232.       if($resto && (($resto >= 1 && $resto <= 99) || $resto % 100 == 0))
  233.         $ret .= ' e ' . self::numero($resto, $genero);
  234.       /* Nos demais casos, após o milhar é utilizada a vírgula. */
  235.       else if ($resto)
  236.         $ret .= ', ' . self::numero($resto, $genero);
  237.       return $ret;
  238.     }
  239.  
  240.     else if($valor >= 100000 && $valor <= self::VALOR_MAXIMO) {
  241.       /* A função 'floor' é utilizada para encontrar o inteiro da divisão de $valor por 1000000,
  242.        * assim determinando a quantidade de milhões. O resultado é enviado a uma chamada recursiva
  243.        * da função. A palavra 'milhão' flexiona-se no plural.
  244.        */
  245.       $milhoes = floor($valor / 1000000);
  246.       $ret = self::numero($milhoes, self::GENERO_MASC) . ' '; // Milhão e milhões são do gênero masculino
  247.      
  248.       /* Se a o número de milhões for maior que 1, deve-se utilizar a forma flexionada no plural */
  249.       $ret .= $milhoes == 1 ? self::$MILHOES[self::NUM_SING] : self::$MILHOES[self::NUM_PLURAL];
  250.  
  251.       $resto = $valor % 1000000;
  252.  
  253.       /* Chamada recursiva à função para processar $resto se este for maior que zero.
  254.        * O conectivo 'e' é utilizado entre milhões e números entre 1 e 99, bem como antes de centenas exatas.
  255.        */
  256.       if($resto && (($resto >= 1 && $resto <= 99) || $resto % 100 == 0))
  257.         $ret .= ' e ' . self::numero($resto, $genero);
  258.       /* Nos demais casos, após o milhão é utilizada a vírgula. */
  259.       else if ($resto)
  260.         $ret .= ', ' . self::numero($resto, $genero);
  261.       return $ret;
  262.     }
  263.  
  264.   }
  265.  
  266.  /**
  267.  * Gera a representação por extenso de um valor monetário, maior que zero e menor ou igual a GExtenso::VALOR_MAXIMO.
  268.  *
  269.  * @param int O valor monetário cujo extenso se deseja gerar.
  270.  * ATENÇÃO: PARA EVITAR OS CONHECIDOS PROBLEMAS DE ARREDONDAMENTO COM NÚMEROS DE PONTO FLUTUANTE, O VALOR DEVE SER PASSADO
  271.  * JÁ DEVIDAMENTE MULTIPLICADO POR 10 ELEVADO A $casasDecimais (o que equivale, normalmente, a passar o valor com centavos
  272.  * multiplicado por 100)
  273.  *
  274.  * @param int (Opcional; valor padrão: 2) Número de casas decimais a serem consideradas como parte fracionária (centavos)
  275.  *
  276.  * @param array (Opcional; valor padrão: array('real', 'reais', GExtenso::GENERO_MASC)) Fornece informações sobre a moeda a ser
  277.  * utilizada. O primeiro valor da matriz corresponde ao nome da moeda no singular, o segundo ao nome da moeda no plural e o terceiro
  278.  * ao gênero gramatical do nome da moeda (GExtenso::GENERO_MASC ou GExtenso::GENERO_FEM)
  279.  *
  280.  * @param array (Opcional; valor padrão: array('centavo', 'centavos', self::GENERO_MASC)) Provê informações sobre a parte fracionária
  281.  * da moeda. O primeiro valor da matriz corresponde ao nome da parte fracionária no singular, o segundo ao nome da parte fracionária no plural
  282.  * e o terceiro ao gênero gramatical da parte fracionária (GExtenso::GENERO_MASC ou GExtenso::GENERO_FEM)
  283.  *
  284.  * @return string O valor monetário por extenso
  285.  *
  286.  * @since 0.1 2010-03-02
  287.  */
  288.   public static function moeda(
  289.     $valor,
  290.     $casasDecimais = 2,
  291.     $infoUnidade = array('real', 'reais', self::GENERO_MASC),
  292.     $infoFracao = array('centavo', 'centavos', self::GENERO_MASC)
  293.   ) {
  294.  
  295.     /* ----- VALIDAÇÃO DOS PARÂMETROS DE ENTRADA ---- */
  296.  
  297.     if(!is_numeric($valor))
  298.       throw new Exception("[Exceção em GExtenso::moeda] Parâmetro \$valor não é numérico (recebido: '$valor')");
  299.  
  300.     else if($valor <= 0)
  301.       throw new Exception("[Exceção em GExtenso::moeda] Parâmetro \$valor igual a ou menor que zero (recebido: '$valor')");
  302.  
  303.     else if(!is_numeric($casasDecimais) || $casasDecimais < 0)
  304.       throw new Exception("[Exceção em GExtenso::moeda] Parâmetro \$casasDecimais não é numérico ou é menor que zero (recebido: '$casasDecimais')");
  305.  
  306.     else if(!is_array($infoUnidade) || count($infoUnidade) < 3) {
  307.       $infoUnidade = print_r($infoUnidade, true);
  308.       throw new Exception("[Exceção em GExtenso::moeda] Parâmetro \$infoUnidade não é uma matriz com 3 (três) elementos (recebido: '$infoUnidade')");
  309.     }
  310.  
  311.     else if($infoUnidade[self::POS_GENERO] != self::GENERO_MASC && $infoUnidade[self::POS_GENERO] != self::GENERO_FEM)
  312.       throw new Exception("Exceção em GExtenso: valor incorreto para o parâmetro \$infoUnidade[self::POS_GENERO] (recebido: '{$infoUnidade[self::POS_GENERO]}').");
  313.  
  314.     else if(!is_array($infoFracao) || count($infoFracao) < 3) {
  315.       $infoFracao = print_r($infoFracao, true);
  316.       throw new Exception("[Exceção em GExtenso::moeda] Parâmetro \$infoFracao não é uma matriz com 3 (três) elementos (recebido: '$infoFracao')");
  317.     }
  318.  
  319.     else if($infoFracao[self::POS_GENERO] != self::GENERO_MASC && $infoFracao[self::POS_GENERO] != self::GENERO_FEM)
  320.       throw new Exception("Exceção em GExtenso: valor incorreto para o parâmetro \$infoFracao[self::POS_GENERO] (recebido: '{$infoFracao[self::POS_GENERO]}').");
  321.  
  322.     /* ----------------------------------------------- */
  323.  
  324.     /* A parte inteira do valor monetário corresponde ao $valor passado dividido por 10 elevado a $casasDecimais, desprezado o resto.
  325.      * Assim, com o padrão de 2 $casasDecimais, o $valor será dividido por 100 (10^2), e o resto é descartado utilizando-se floor().
  326.      */
  327.     $parteInteira = floor($valor / pow(10, $casasDecimais));
  328.  
  329.     /* A parte fracionária ('centavos'), por seu turno, corresponderá ao resto da divisão do $valor por 10 elevado a $casasDecimais.
  330.      * No cenário comum em que trabalhamos com 2 $casasDecimais, será o resto da divisão do $valor por 100 (10^2).
  331.      */
  332.     $fracao = $valor % pow(10, $casasDecimais);
  333.  
  334.     /* O extenso para a $parteInteira somente será gerado se esta for maior que zero. Para tanto, utilizamos
  335.      * os préstimos do método GExtenso::numero().
  336.      */
  337.     if($parteInteira) {
  338.       $ret = self::numero($parteInteira, $infoUnidade[self::POS_GENERO]) . ' ';
  339.       $ret .= $parteInteira == 1 ? $infoUnidade[self::NUM_SING] : $infoUnidade[self::NUM_PLURAL];
  340.     }
  341.  
  342.     /* De forma semelhante, o extenso da $fracao somente será gerado se esta for maior que zero. */
  343.     if($fracao) {
  344.       /* Se a $parteInteira for maior que zero, o extenso para ela já terá sido gerado. Antes de juntar os
  345.        * centavos, precisamos colocar o conectivo 'e'.
  346.        */
  347.       if ($parteInteira) $ret .= ' e ';
  348.       $ret .= self::numero($fracao, $infoFracao[self::POS_GENERO]) . ' ';
  349.       $ret .= $parteInteira == 1 ? $infoFracao[self::NUM_SING] : $infoFracao[self::NUM_PLURAL];
  350.     }
  351.  
  352.     return $ret;
  353.  
  354.   }
  355.  
  356. }
  357. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement