Total Complexity | 46 |
Total Lines | 333 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like MoneyField often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use MoneyField, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
17 | class MoneyField extends FormField |
||
18 | { |
||
19 | |||
20 | // TODO replace with `FormField::SCHEMA_DATA_TYPE_TEXT` when MoneyField is implemented |
||
21 | /** @skipUpgrade */ |
||
22 | protected $schemaDataType = 'MoneyField'; |
||
23 | |||
24 | /** |
||
25 | * Limit the currencies |
||
26 | * |
||
27 | * @var array |
||
28 | */ |
||
29 | protected $allowedCurrencies = []; |
||
30 | |||
31 | /** |
||
32 | * @var NumericField |
||
33 | */ |
||
34 | protected $fieldAmount = null; |
||
35 | |||
36 | /** |
||
37 | * @var FormField |
||
38 | */ |
||
39 | protected $fieldCurrency = null; |
||
40 | |||
41 | /** |
||
42 | * Gets field for the currency selector |
||
43 | * |
||
44 | * @return FormField |
||
45 | */ |
||
46 | public function getCurrencyField() |
||
47 | { |
||
48 | return $this->fieldCurrency; |
||
49 | } |
||
50 | |||
51 | /** |
||
52 | * Gets field for the amount input |
||
53 | * |
||
54 | * @return NumericField |
||
55 | */ |
||
56 | public function getAmountField() |
||
59 | } |
||
60 | |||
61 | public function __construct($name, $title = null, $value = "") |
||
62 | { |
||
63 | $this->setName($name); |
||
64 | $this->fieldAmount = NumericField::create( |
||
65 | "{$name}[Amount]", |
||
66 | _t('SilverStripe\\Forms\\MoneyField.FIELDLABELAMOUNT', 'Amount') |
||
67 | ) |
||
68 | ->setScale(2); |
||
69 | $this->buildCurrencyField(); |
||
70 | |||
71 | parent::__construct($name, $title, $value); |
||
72 | } |
||
73 | |||
74 | public function __clone() |
||
78 | } |
||
79 | |||
80 | /** |
||
81 | * Builds a new currency field based on the allowed currencies configured |
||
82 | * |
||
83 | * @return FormField |
||
84 | */ |
||
85 | protected function buildCurrencyField() |
||
86 | { |
||
87 | $name = $this->getName(); |
||
88 | |||
89 | // Validate allowed currencies |
||
90 | $currencyValue = $this->fieldCurrency ? $this->fieldCurrency->dataValue() : null; |
||
91 | $allowedCurrencies = $this->getAllowedCurrencies(); |
||
92 | if (count($allowedCurrencies ?: []) === 1) { |
||
93 | // Hidden field for single currency |
||
94 | $field = HiddenField::create("{$name}[Currency]"); |
||
95 | reset($allowedCurrencies); |
||
96 | $currencyValue = key($allowedCurrencies ?: []); |
||
97 | } elseif ($allowedCurrencies) { |
||
|
|||
98 | // Dropdown field for multiple currencies |
||
99 | $field = DropdownField::create( |
||
100 | "{$name}[Currency]", |
||
101 | _t('SilverStripe\\Forms\\MoneyField.FIELDLABELCURRENCY', 'Currency'), |
||
102 | $allowedCurrencies |
||
103 | ); |
||
104 | } else { |
||
105 | // Free-text entry for currency value |
||
106 | $field = TextField::create( |
||
107 | "{$name}[Currency]", |
||
108 | _t('SilverStripe\\Forms\\MoneyField.FIELDLABELCURRENCY', 'Currency') |
||
109 | ); |
||
110 | } |
||
111 | |||
112 | $field->setReadonly($this->isReadonly()); |
||
113 | $field->setDisabled($this->isDisabled()); |
||
114 | if ($currencyValue) { |
||
115 | $field->setValue($currencyValue); |
||
116 | } |
||
117 | $this->fieldCurrency = $field; |
||
118 | return $field; |
||
119 | } |
||
120 | |||
121 | public function setSubmittedValue($value, $data = null) |
||
122 | { |
||
123 | if (empty($value)) { |
||
124 | $this->value = null; |
||
125 | $this->fieldCurrency->setValue(null); |
||
126 | $this->fieldAmount->setValue(null); |
||
127 | return $this; |
||
128 | } |
||
129 | |||
130 | // Handle submitted array value |
||
131 | if (!is_array($value)) { |
||
132 | throw new InvalidArgumentException("Value is not submitted array"); |
||
133 | } |
||
134 | |||
135 | // Update each field |
||
136 | $this->fieldCurrency->setSubmittedValue($value['Currency'], $value); |
||
137 | $this->fieldAmount->setSubmittedValue($value['Amount'], $value); |
||
138 | |||
139 | // Get data value |
||
140 | $this->value = $this->dataValue(); |
||
141 | return $this; |
||
142 | } |
||
143 | |||
144 | public function setValue($value, $data = null) |
||
145 | { |
||
146 | if (empty($value)) { |
||
147 | $this->value = null; |
||
148 | $this->fieldCurrency->setValue(null); |
||
149 | $this->fieldAmount->setValue(null); |
||
150 | return $this; |
||
151 | } |
||
152 | |||
153 | // Convert string to array |
||
154 | // E.g. `44.00 NZD` |
||
155 | if (is_string($value) && |
||
156 | preg_match('/^(?<amount>[\\d\\.]+)( (?<currency>\w{3}))?$/i', (string) $value, $matches) |
||
157 | ) { |
||
158 | $currency = isset($matches['currency']) ? strtoupper($matches['currency']) : null; |
||
159 | $value = [ |
||
160 | 'Currency' => $currency, |
||
161 | 'Amount' => (float)$matches['amount'], |
||
162 | ]; |
||
163 | } elseif ($value instanceof DBMoney) { |
||
164 | $value = [ |
||
165 | 'Currency' => $value->getCurrency(), |
||
166 | 'Amount' => $value->getAmount(), |
||
167 | ]; |
||
168 | } elseif (!is_array($value)) { |
||
169 | throw new InvalidArgumentException("Invalid currency format"); |
||
170 | } |
||
171 | |||
172 | // Save value |
||
173 | $this->fieldCurrency->setValue($value['Currency'], $value); |
||
174 | $this->fieldAmount->setValue($value['Amount'], $value); |
||
175 | $this->value = $this->dataValue(); |
||
176 | return $this; |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Get value as DBMoney object useful for formatting the number |
||
181 | * |
||
182 | * @return DBMoney |
||
183 | */ |
||
184 | protected function getDBMoney() |
||
185 | { |
||
186 | return DBMoney::create_field('Money', [ |
||
187 | 'Currency' => $this->fieldCurrency->dataValue(), |
||
188 | 'Amount' => $this->fieldAmount->dataValue() |
||
189 | ]) |
||
190 | ->setLocale($this->getLocale()); |
||
191 | } |
||
192 | |||
193 | public function dataValue() |
||
194 | { |
||
195 | // Non-localised money |
||
196 | return $this->getDBMoney()->getValue(); |
||
197 | } |
||
198 | |||
199 | public function Value() |
||
200 | { |
||
201 | // Localised money |
||
202 | return $this->getDBMoney()->Nice(); |
||
203 | } |
||
204 | |||
205 | /** |
||
206 | * 30/06/2009 - Enhancement: |
||
207 | * SaveInto checks if set-methods are available and use them |
||
208 | * instead of setting the values in the money class directly. saveInto |
||
209 | * initiates a new Money class object to pass through the values to the setter |
||
210 | * method. |
||
211 | * |
||
212 | * (see @link MoneyFieldTest_CustomSetter_Object for more information) |
||
213 | * |
||
214 | * @param DataObjectInterface|Object $dataObject |
||
215 | */ |
||
216 | public function saveInto(DataObjectInterface $dataObject) |
||
217 | { |
||
218 | $fieldName = $this->getName(); |
||
219 | if ($dataObject->hasMethod("set$fieldName")) { |
||
220 | $dataObject->$fieldName = $this->getDBMoney(); |
||
221 | } else { |
||
222 | $currencyField = "{$fieldName}Currency"; |
||
223 | $amountField = "{$fieldName}Amount"; |
||
224 | |||
225 | $dataObject->$currencyField = $this->fieldCurrency->dataValue(); |
||
226 | $dataObject->$amountField = $this->fieldAmount->dataValue(); |
||
227 | } |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * Returns a readonly version of this field. |
||
232 | */ |
||
233 | public function performReadonlyTransformation() |
||
234 | { |
||
235 | $clone = clone $this; |
||
236 | $clone->setReadonly(true); |
||
237 | return $clone; |
||
238 | } |
||
239 | |||
240 | public function setReadonly($bool) |
||
241 | { |
||
242 | parent::setReadonly($bool); |
||
243 | |||
244 | $this->fieldAmount->setReadonly($bool); |
||
245 | $this->fieldCurrency->setReadonly($bool); |
||
246 | |||
247 | return $this; |
||
248 | } |
||
249 | |||
250 | public function setDisabled($bool) |
||
251 | { |
||
252 | parent::setDisabled($bool); |
||
253 | |||
254 | $this->fieldAmount->setDisabled($bool); |
||
255 | $this->fieldCurrency->setDisabled($bool); |
||
256 | |||
257 | return $this; |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Set list of currencies. Currencies should be in the 3-letter ISO 4217 currency code. |
||
262 | * |
||
263 | * @param array $currencies |
||
264 | * @return $this |
||
265 | */ |
||
266 | public function setAllowedCurrencies($currencies) |
||
267 | { |
||
268 | if (empty($currencies)) { |
||
269 | $currencies = []; |
||
270 | } elseif (is_string($currencies)) { |
||
271 | $currencies = [ |
||
272 | $currencies => $currencies |
||
273 | ]; |
||
274 | } elseif (!is_array($currencies)) { |
||
275 | throw new InvalidArgumentException("Invalid currency list"); |
||
276 | } elseif (!ArrayLib::is_associative($currencies)) { |
||
277 | $currencies = array_combine($currencies ?: [], $currencies ?: []); |
||
278 | } |
||
279 | |||
280 | $this->allowedCurrencies = $currencies; |
||
281 | |||
282 | // Rebuild currency field |
||
283 | $this->buildCurrencyField(); |
||
284 | return $this; |
||
285 | } |
||
286 | |||
287 | /** |
||
288 | * @return array |
||
289 | */ |
||
290 | public function getAllowedCurrencies() |
||
291 | { |
||
292 | return $this->allowedCurrencies; |
||
293 | } |
||
294 | |||
295 | /** |
||
296 | * Assign locale to format this currency in |
||
297 | * |
||
298 | * @param string $locale |
||
299 | * @return $this |
||
300 | */ |
||
301 | public function setLocale($locale) |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Get locale to format this currency in. |
||
309 | * Defaults to current locale. |
||
310 | * |
||
311 | * @return string |
||
312 | */ |
||
313 | public function getLocale() |
||
316 | } |
||
317 | |||
318 | /** |
||
319 | * Validate this field |
||
320 | * |
||
321 | * @param Validator $validator |
||
322 | * @return bool |
||
323 | */ |
||
324 | public function validate($validator) |
||
325 | { |
||
326 | // Validate currency |
||
327 | $currencies = $this->getAllowedCurrencies(); |
||
328 | $currency = $this->fieldCurrency->dataValue(); |
||
329 | if ($currency && $currencies && !in_array($currency, $currencies ?: [])) { |
||
330 | $validator->validationError( |
||
331 | $this->getName(), |
||
332 | _t( |
||
333 | __CLASS__ . '.INVALID_CURRENCY', |
||
334 | 'Currency {currency} is not in the list of allowed currencies', |
||
335 | ['currency' => $currency] |
||
336 | ) |
||
337 | ); |
||
338 | return false; |
||
339 | } |
||
340 | |||
341 | // Field-specific validation |
||
342 | return $this->fieldAmount->validate($validator) && $this->fieldCurrency->validate($validator); |
||
343 | } |
||
344 | |||
345 | public function setForm($form) |
||
350 | } |
||
351 | } |
||
352 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.