Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
NumericParser
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
8 / 8
12
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 allowGroupingSeparator
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getFrmtr
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLocale
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isGroupingSeparatorAllowed
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parseValue
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getReturnType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setReturnType
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * @author: Doug Wilbourne (dougwilbourne@gmail.com)
5 */
6declare(strict_types=1);
7
8namespace pvc\parser\numeric;
9
10use NumberFormatter;
11use pvc\interfaces\intl\LocaleInterface;
12use pvc\interfaces\msg\MsgInterface;
13use pvc\parser\err\InvalidReturnTypeException;
14use pvc\parser\Parser;
15
16/**
17 * Class NumericParser
18 * @extends Parser<DataType>
19 * @template DataType
20 *
21 * This is a wrapper for php's NumberFormatter class.  The child classes of this class simply set up different
22 * flavors of NumberFormatter for some common scenarios.  You can access the underlying NumberFormatter object and
23 * further manipulate it to accommodate whatever special use case you have.
24 *
25 * Unfortunately, the underlying NumberFormatter class is created with a non-optional locale argument which is
26 * immutable.  This class exhibits the same behavior.  The reason is that if we were to create new NumberFormatters
27 * each time a user changed the locale of this class, all other changes that were made to the old formatter would be
28 * lost.
29 */
30abstract class NumericParser extends Parser
31{
32    protected NumberFormatter $frmtr;
33
34    /**
35     * the locale attribute is 'de-normalized' in the sense that it is stored as an attribute but also embedded
36     * in the formatter at constrfuction time.  It was tempting to just remove the attribute and have the getter
37     * return the locale from the formatter.  BUT, we want to set and get a Locale object (pvc\intl\Locale), NOT a
38     * string, so we keep the object handy.
39     */
40
41    protected LocaleInterface $locale;
42
43    protected int $returnType = NumberFormatter::TYPE_INT64;
44
45    /**
46     * @var array <int>
47     * in this day and age we are not listing the 32 bit return types as being valid.  Also, as of php 8.3. the
48     * currency type is deprecated.  The only choices should be integer or float
49     */
50    protected array $validReturnTypes = [
51        NumberFormatter::TYPE_INT64,
52        NumberFormatter::TYPE_DOUBLE,
53    ];
54
55    public function __construct(MsgInterface $msg, LocaleInterface $locale, NumberFormatter $frmtr)
56    {
57        parent::__construct($msg);
58        $this->frmtr = $frmtr;
59        $this->locale = $locale;
60        /**
61         * grouping separator is allowed but not required
62         */
63        $this->allowGroupingSeparator(true);
64    }
65
66    /**
67     * allowGroupingSeparator
68     * @param bool $allowed
69     */
70    public function allowGroupingSeparator(bool $allowed): void
71    {
72        if (!$allowed) {
73            $this->getFrmtr()->setAttribute(NumberFormatter::GROUPING_USED, 0);
74        } else {
75            $this->getFrmtr()->setAttribute(NumberFormatter::GROUPING_USED, 1);
76        }
77    }
78
79    public function getFrmtr(): NumberFormatter
80    {
81        return $this->frmtr;
82    }
83
84    public function getLocale(): LocaleInterface
85    {
86        return $this->locale;
87    }
88
89    public function isGroupingSeparatorAllowed(): bool
90    {
91        return (bool)$this->getFrmtr()->getAttribute(NumberFormatter::GROUPING_USED);
92    }
93
94    /**
95     * @function parseValue
96     * @param string $data
97     * @return bool
98     */
99    public function parseValue(string $data): bool
100    {
101        $pos = 0;
102        $expectedPos = strlen($data);
103        /** @var DataType $result */
104        $result = $this->frmtr->parse($data, $this->getReturnType(), $pos);
105
106        /**
107         * NumberFormatter 'fails gracefully' if it successfully parses a part of a string, e.g. it will not throw
108         * an exception if it parses the first x characters of the string into the return type and can't parse any
109         * more from the x + 1 character to the end of the string.  The $pos variable holds the offset of the last
110         * character successfully parsed.
111         */
112        if (($pos == $expectedPos) && ($result !== false)) {
113            $this->parsedValue = $result;
114            return true;
115        } else {
116            $this->setMsgContent($this->msg);
117            return false;
118        }
119    }
120
121    /**
122     * getReturnType
123     * @return int
124     * make integer the default return type
125     */
126    public function getReturnType(): int
127    {
128        return $this->returnType;
129    }
130
131    public function setReturnType(int $returnType): void
132    {
133        if (!in_array($returnType, $this->validReturnTypes)) {
134            throw new InvalidReturnTypeException();
135        }
136        $this->returnType = $returnType;
137    }
138}