관리-도구
편집 파일: NumberToLocalizedStringTransformer.php
<?php /* * This file is part of the Symfony package. * * (c) Fabien Potencier <fabien@symfony.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Form\Extension\Core\DataTransformer; use Symfony\Component\Form\DataTransformerInterface; use Symfony\Component\Form\Exception\TransformationFailedException; /** * Transforms between a number type and a localized number with grouping * (each thousand) and comma separators. * * @author Bernhard Schussek <bschussek@gmail.com> * @author Florian Eckerstorfer <florian@eckerstorfer.org> */ class NumberToLocalizedStringTransformer implements DataTransformerInterface { /** * Rounds a number towards positive infinity. * * Rounds 1.4 to 2 and -1.4 to -1. */ const ROUND_CEILING = \NumberFormatter::ROUND_CEILING; /** * Rounds a number towards negative infinity. * * Rounds 1.4 to 1 and -1.4 to -2. */ const ROUND_FLOOR = \NumberFormatter::ROUND_FLOOR; /** * Rounds a number away from zero. * * Rounds 1.4 to 2 and -1.4 to -2. */ const ROUND_UP = \NumberFormatter::ROUND_UP; /** * Rounds a number towards zero. * * Rounds 1.4 to 1 and -1.4 to -1. */ const ROUND_DOWN = \NumberFormatter::ROUND_DOWN; /** * Rounds to the nearest number and halves to the next even number. * * Rounds 2.5, 1.6 and 1.5 to 2 and 1.4 to 1. */ const ROUND_HALF_EVEN = \NumberFormatter::ROUND_HALFEVEN; /** * Rounds to the nearest number and halves away from zero. * * Rounds 2.5 to 3, 1.6 and 1.5 to 2 and 1.4 to 1. */ const ROUND_HALF_UP = \NumberFormatter::ROUND_HALFUP; /** * Rounds to the nearest number and halves towards zero. * * Rounds 2.5 and 1.6 to 2, 1.5 and 1.4 to 1. */ const ROUND_HALF_DOWN = \NumberFormatter::ROUND_HALFDOWN; /** * Alias for {@link self::ROUND_HALF_EVEN}. * * @deprecated Deprecated as of Symfony 2.4, to be removed in Symfony 3.0. */ const ROUND_HALFEVEN = self::ROUND_HALF_EVEN; /** * Alias for {@link self::ROUND_HALF_UP}. * * @deprecated Deprecated as of Symfony 2.4, to be removed in Symfony 3.0. */ const ROUND_HALFUP = self::ROUND_HALF_UP; /** * Alias for {@link self::ROUND_HALF_DOWN}. * * @deprecated Deprecated as of Symfony 2.4, to be removed in Symfony 3.0. */ const ROUND_HALFDOWN = self::ROUND_HALF_DOWN; protected $precision; protected $grouping; protected $roundingMode; public function __construct($precision = null, $grouping = false, $roundingMode = self::ROUND_HALF_UP) { if (null === $grouping) { $grouping = false; } if (null === $roundingMode) { $roundingMode = self::ROUND_HALF_UP; } $this->precision = $precision; $this->grouping = $grouping; $this->roundingMode = $roundingMode; } /** * Transforms a number type into localized number. * * @param integer|float $value Number value. * * @return string Localized value. * * @throws TransformationFailedException If the given value is not numeric * or if the value can not be transformed. */ public function transform($value) { if (null === $value) { return ''; } if (!is_numeric($value)) { throw new TransformationFailedException('Expected a numeric.'); } $formatter = $this->getNumberFormatter(); $value = $formatter->format($value); if (intl_is_failure($formatter->getErrorCode())) { throw new TransformationFailedException($formatter->getErrorMessage()); } // Convert fixed spaces to normal ones $value = str_replace("\xc2\xa0", ' ', $value); return $value; } /** * Transforms a localized number into an integer or float * * @param string $value The localized value * * @return integer|float The numeric value * * @throws TransformationFailedException If the given value is not a string * or if the value can not be transformed. */ public function reverseTransform($value) { if (!is_string($value)) { throw new TransformationFailedException('Expected a string.'); } if ('' === $value) { return null; } if ('NaN' === $value) { throw new TransformationFailedException('"NaN" is not a valid number'); } $position = 0; $formatter = $this->getNumberFormatter(); $groupSep = $formatter->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL); $decSep = $formatter->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL); if ('.' !== $decSep && (!$this->grouping || '.' !== $groupSep)) { $value = str_replace('.', $decSep, $value); } if (',' !== $decSep && (!$this->grouping || ',' !== $groupSep)) { $value = str_replace(',', $decSep, $value); } $result = $formatter->parse($value, \NumberFormatter::TYPE_DOUBLE, $position); if (intl_is_failure($formatter->getErrorCode())) { throw new TransformationFailedException($formatter->getErrorMessage()); } if ($result >= PHP_INT_MAX || $result <= -PHP_INT_MAX) { throw new TransformationFailedException('I don\'t have a clear idea what infinity looks like'); } if (function_exists('mb_detect_encoding') && false !== $encoding = mb_detect_encoding($value)) { $strlen = function ($string) use ($encoding) { return mb_strlen($string, $encoding); }; $substr = function ($string, $offset, $length) use ($encoding) { return mb_substr($string, $offset, $length, $encoding); }; } else { $strlen = 'strlen'; $substr = 'substr'; } $length = $strlen($value); // After parsing, position holds the index of the character where the // parsing stopped if ($position < $length) { // Check if there are unrecognized characters at the end of the // number (excluding whitespace characters) $remainder = trim($substr($value, $position, $length), " \t\n\r\0\x0b\xc2\xa0"); if ('' !== $remainder) { throw new TransformationFailedException( sprintf('The number contains unrecognized characters: "%s"', $remainder) ); } } // NumberFormatter::parse() does not round return $this->round($result); } /** * Returns a preconfigured \NumberFormatter instance * * @return \NumberFormatter */ protected function getNumberFormatter() { $formatter = new \NumberFormatter(\Locale::getDefault(), \NumberFormatter::DECIMAL); if (null !== $this->precision) { $formatter->setAttribute(\NumberFormatter::FRACTION_DIGITS, $this->precision); $formatter->setAttribute(\NumberFormatter::ROUNDING_MODE, $this->roundingMode); } $formatter->setAttribute(\NumberFormatter::GROUPING_USED, $this->grouping); return $formatter; } /** * Rounds a number according to the configured precision and rounding mode. * * @param integer|float $number A number. * * @return integer|float The rounded number. */ private function round($number) { if (null !== $this->precision && null !== $this->roundingMode) { // shift number to maintain the correct precision during rounding $roundingCoef = pow(10, $this->precision); $number *= $roundingCoef; switch ($this->roundingMode) { case self::ROUND_CEILING: $number = ceil($number); break; case self::ROUND_FLOOR: $number = floor($number); break; case self::ROUND_UP: $number = $number > 0 ? ceil($number) : floor($number); break; case self::ROUND_DOWN: $number = $number > 0 ? floor($number) : ceil($number); break; case self::ROUND_HALF_EVEN: $number = round($number, 0, PHP_ROUND_HALF_EVEN); break; case self::ROUND_HALF_UP: $number = round($number, 0, PHP_ROUND_HALF_UP); break; case self::ROUND_HALF_DOWN: $number = round($number, 0, PHP_ROUND_HALF_DOWN); break; } $number /= $roundingCoef; } return $number; } }