1: <?php
2: /**
3: * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4: * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5: *
6: * Licensed under The MIT License
7: * For full copyright and license information, please see the LICENSE.txt
8: * Redistributions of files must retain the above copyright notice.
9: *
10: * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11: * @link https://cakephp.org CakePHP(tm) Project
12: * @since 3.3.4
13: * @license https://opensource.org/licenses/mit-license.php MIT License
14: */
15: namespace Cake\Database\Type;
16:
17: use Cake\Database\Driver;
18: use Cake\Database\Type;
19: use Cake\Database\TypeInterface;
20: use Cake\Database\Type\BatchCastingInterface;
21: use InvalidArgumentException;
22: use PDO;
23: use RuntimeException;
24:
25: /**
26: * Decimal type converter.
27: *
28: * Use to convert decimal data between PHP and the database types.
29: */
30: class DecimalType extends Type implements TypeInterface, BatchCastingInterface
31: {
32: /**
33: * Identifier name for this type.
34: *
35: * (This property is declared here again so that the inheritance from
36: * Cake\Database\Type can be removed in the future.)
37: *
38: * @var string|null
39: */
40: protected $_name;
41:
42: /**
43: * Constructor.
44: *
45: * (This method is declared here again so that the inheritance from
46: * Cake\Database\Type can be removed in the future.)
47: *
48: * @param string|null $name The name identifying this type
49: */
50: public function __construct($name = null)
51: {
52: $this->_name = $name;
53: }
54:
55: /**
56: * The class to use for representing number objects
57: *
58: * @var string
59: */
60: public static $numberClass = 'Cake\I18n\Number';
61:
62: /**
63: * Whether numbers should be parsed using a locale aware parser
64: * when marshalling string inputs.
65: *
66: * @var bool
67: */
68: protected $_useLocaleParser = false;
69:
70: /**
71: * Convert integer data into the database format.
72: *
73: * @param mixed $value The value to convert.
74: * @param \Cake\Database\Driver $driver The driver instance to convert with.
75: * @return string|null
76: * @throws \InvalidArgumentException
77: */
78: public function toDatabase($value, Driver $driver)
79: {
80: if ($value === null || $value === '') {
81: return null;
82: }
83: if (!is_scalar($value)) {
84: throw new InvalidArgumentException(sprintf(
85: 'Cannot convert value of type `%s` to a decimal',
86: getTypeName($value)
87: ));
88: }
89: if (is_string($value) && is_numeric($value)) {
90: return $value;
91: }
92:
93: return sprintf('%F', $value);
94: }
95:
96: /**
97: * Convert float values to PHP floats
98: *
99: * @param mixed $value The value to convert.
100: * @param \Cake\Database\Driver $driver The driver instance to convert with.
101: * @return float|null
102: */
103: public function toPHP($value, Driver $driver)
104: {
105: if ($value === null) {
106: return $value;
107: }
108:
109: return (float)$value;
110: }
111:
112: /**
113: * {@inheritDoc}
114: *
115: * @return float[]
116: */
117: public function manyToPHP(array $values, array $fields, Driver $driver)
118: {
119: foreach ($fields as $field) {
120: if (!isset($values[$field])) {
121: continue;
122: }
123:
124: $values[$field] = (float)$values[$field];
125: }
126:
127: return $values;
128: }
129:
130: /**
131: * Get the correct PDO binding type for integer data.
132: *
133: * @param mixed $value The value being bound.
134: * @param \Cake\Database\Driver $driver The driver.
135: * @return int
136: */
137: public function toStatement($value, Driver $driver)
138: {
139: return PDO::PARAM_STR;
140: }
141:
142: /**
143: * Marshals request data into PHP floats.
144: *
145: * @param mixed $value The value to convert.
146: * @return float|string|null Converted value.
147: */
148: public function marshal($value)
149: {
150: if ($value === null || $value === '') {
151: return null;
152: }
153: if (is_string($value) && $this->_useLocaleParser) {
154: return $this->_parseValue($value);
155: }
156: if (is_numeric($value)) {
157: return (float)$value;
158: }
159: if (is_string($value) && preg_match('/^[0-9,. ]+$/', $value)) {
160: return $value;
161: }
162:
163: return null;
164: }
165:
166: /**
167: * Sets whether or not to parse numbers passed to the marshal() function
168: * by using a locale aware parser.
169: *
170: * @param bool $enable Whether or not to enable
171: * @return $this
172: * @throws \RuntimeException
173: */
174: public function useLocaleParser($enable = true)
175: {
176: if ($enable === false) {
177: $this->_useLocaleParser = $enable;
178:
179: return $this;
180: }
181: if (static::$numberClass === 'Cake\I18n\Number' ||
182: is_subclass_of(static::$numberClass, 'Cake\I18n\Number')
183: ) {
184: $this->_useLocaleParser = $enable;
185:
186: return $this;
187: }
188: throw new RuntimeException(
189: sprintf('Cannot use locale parsing with the %s class', static::$numberClass)
190: );
191: }
192:
193: /**
194: * Converts a string into a float point after parsing it using the locale
195: * aware parser.
196: *
197: * @param string $value The value to parse and convert to an float.
198: * @return float
199: */
200: protected function _parseValue($value)
201: {
202: /* @var \Cake\I18n\Number $class */
203: $class = static::$numberClass;
204:
205: return $class::parseFloat($value);
206: }
207: }
208: