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 string|int|float $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 null|string|resource $value The value to convert.
100: * @param \Cake\Database\Driver $driver The driver instance to convert with.
101: * @return float|null
102: * @throws \Cake\Core\Exception\Exception
103: */
104: public function toPHP($value, Driver $driver)
105: {
106: if ($value === null) {
107: return $value;
108: }
109:
110: return (float)$value;
111: }
112:
113: /**
114: * {@inheritDoc}
115: *
116: * @return array
117: */
118: public function manyToPHP(array $values, array $fields, Driver $driver)
119: {
120: foreach ($fields as $field) {
121: if (!isset($values[$field])) {
122: continue;
123: }
124:
125: $values[$field] = (float)$values[$field];
126: }
127:
128: return $values;
129: }
130:
131: /**
132: * Get the correct PDO binding type for integer data.
133: *
134: * @param mixed $value The value being bound.
135: * @param \Cake\Database\Driver $driver The driver.
136: * @return int
137: */
138: public function toStatement($value, Driver $driver)
139: {
140: return PDO::PARAM_STR;
141: }
142:
143: /**
144: * Marshalls request data into PHP floats.
145: *
146: * @param mixed $value The value to convert.
147: * @return mixed Converted value.
148: */
149: public function marshal($value)
150: {
151: if ($value === null || $value === '') {
152: return null;
153: }
154: if (is_string($value) && $this->_useLocaleParser) {
155: return $this->_parseValue($value);
156: }
157: if (is_numeric($value)) {
158: return (float)$value;
159: }
160: if (is_string($value) && preg_match('/^[0-9,. ]+$/', $value)) {
161: return $value;
162: }
163:
164: return null;
165: }
166:
167: /**
168: * Sets whether or not to parse numbers passed to the marshal() function
169: * by using a locale aware parser.
170: *
171: * @param bool $enable Whether or not to enable
172: * @return $this
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: