Test Failed
Push — develop ( b920e1...351362 )
by Remco
04:33
created

src/Money.php (6 issues)

1
<?php
2
/**
3
 * Money
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2021 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Money
9
 */
10
11
namespace Pronamic\WordPress\Money;
12
13
use JsonSerializable;
14
use Pronamic\WordPress\Number\Number;
15
16
/**
17
 * Money
18
 *
19
 * @author Remco Tolsma
20
 * @version 1.2.5
21
 * @since   1.0.0
22
 */
23
class Money implements JsonSerializable {
24
	/**
25
	 * Number.
26
	 *
27
	 * @var Number
28
	 */
29
	private $amount;
30
31
	/**
32
	 * Currency.
33
	 *
34
	 * @var Currency
35
	 */
36
	private $currency;
37
38
	/**
39
	 * Calculator.
40
	 *
41
	 * @var Calculator|null
0 ignored issues
show
The type Pronamic\WordPress\Money\Calculator was not found. Did you mean Calculator? If so, make sure to prefix the type with \.
Loading history...
42
	 */
43
	private static $calculator;
44
45
	/**
46
	 * Calculators.
47
	 *
48
	 * @var array<int, string>
49
	 */
50
	private static $calculators = array(
51
		BcMathCalculator::class,
0 ignored issues
show
The type Pronamic\WordPress\Money\BcMathCalculator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
52
		PhpCalculator::class,
0 ignored issues
show
The type Pronamic\WordPress\Money\PhpCalculator was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
53
	);
54
55
	/**
56
	 * Construct and initialize money object.
57
	 *
58
	 * @param mixed           $value    Amount value.
59
	 * @param Currency|string $currency Currency.
60
	 */
61 95
	public function __construct( $value = 0, $currency = 'EUR' ) {
62 95
		$this->set_value( $value );
63 95
		$this->set_currency( $currency );
64 95
	}
65
66
	/**
67
	 * Get default format.
68
	 *
69
	 * @return string
70
	 */
71 17
	public static function get_default_format() {
72
		/* translators: 1: currency symbol, 2: amount value, 3: currency code, note: use non-breaking space! */
73 17
		$format = _x( '%1$s%2$s %3$s', 'money format', 'pronamic-money' );
74
		// Note:               ↳ Non-breaking space.
75 17
		$format = apply_filters( 'pronamic_money_default_format', $format );
76
77 17
		return $format;
78
	}
79
80
	/**
81
	 * Format i18n.
82
	 *
83
	 * @param string|null $format Format.
84
	 *
85
	 * @return string
86
	 */
87 14
	public function format_i18n( $format = null ) {
88 14
		if ( is_null( $format ) ) {
89 7
			$format = self::get_default_format();
90
		}
91
92 14
		$alphabetic_code = $this->currency->get_alphabetic_code();
93
94 14
		if ( ! empty( $alphabetic_code ) ) {
95 14
			$number_decimals = $this->currency->get_number_decimals();
96
97
			// Handle non trailing zero formatter.
98 14
			if ( false !== strpos( $format, '%2$NTZ' ) ) {
99 7
				$decimals = substr( $this->format(), ( - 1 * $number_decimals ), $number_decimals );
100
101 7
				if ( 0 === (int) $decimals ) {
102 5
					$number_decimals = 0;
103
				}
104
105 7
				$format = str_replace( '%2$NTZ', '%2$s', $format );
106
			}
107
108 14
			return sprintf(
109 14
				$format,
110 14
				(string) $this->currency->get_symbol(),
111 14
				number_format_i18n( $this->get_value(), $number_decimals ),
0 ignored issues
show
$this->get_value() of type string is incompatible with the type double expected by parameter $number of number_format_i18n(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

111
				number_format_i18n( /** @scrutinizer ignore-type */ $this->get_value(), $number_decimals ),
Loading history...
112 14
				strval( $alphabetic_code )
113
			);
114
		}
115
116
		return number_format_i18n( $this->get_value(), 2 );
117
	}
118
119
	/**
120
	 * Format i18n without trailing zeros.
121
	 *
122
	 * @param string|null $format Format.
123
	 *
124
	 * @return string
125
	 */
126 7
	public function format_i18n_non_trailing_zeros( $format = null ) {
127 7
		if ( is_null( $format ) ) {
128 7
			$format = self::get_default_format();
129
		}
130
131 7
		$format = str_replace( '%2$s', '%2$NTZ', $format );
132
133 7
		return $this->format_i18n( $format );
134
	}
135
136
	/**
137
	 * Format.
138
	 *
139
	 * @param string|null $format Format.
140
	 *
141
	 * @return string
142
	 */
143 7
	public function format( $format = null ) {
144 7
		if ( is_null( $format ) ) {
145 7
			$format = '%2$s';
146
		}
147
148 7
		$alphabetic_code = $this->currency->get_alphabetic_code();
149
150 7
		if ( ! empty( $alphabetic_code ) ) {
151 7
			return sprintf(
152 7
				$format,
153 7
				(string) $this->currency->get_symbol(),
154 7
				number_format( $this->amount->get_value(), $this->get_currency()->get_number_decimals(), '.', '' ),
0 ignored issues
show
$this->amount->get_value() of type string is incompatible with the type double expected by parameter $num of number_format(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

154
				number_format( /** @scrutinizer ignore-type */ $this->amount->get_value(), $this->get_currency()->get_number_decimals(), '.', '' ),
Loading history...
155 7
				strval( $alphabetic_code )
156
			);
157
		}
158
159
		return number_format( $this->get_value(), 2, '.', '' );
160
	}
161
162
	/**
163
	 * Get value.
164
	 *
165
	 * @return string Amount value.
166
	 */
167 95
	public function get_value() {
168 95
		return $this->amount->get_value();
169
	}
170
171
	/**
172
	 * Get number.
173
	 *
174
	 * @return Number
175
	 */
176
	public function get_number() {
177
		return $this->amount;
178
	}
179
180
	/**
181
	 * Get amount.
182
	 *
183
	 * @deprecated 1.2.0
184
	 * @return float Amount value.
185
	 */
186
	public function get_amount() {
187
		_deprecated_function( __METHOD__, '1.2.0', 'Money::get_value()' );
188
189
		return $this->get_value();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->get_value() returns the type string which is incompatible with the documented return type double.
Loading history...
190 1
	}
191 1
192
	/**
193
	 * Get cents.
194
	 *
195
	 * @return float
196
	 *
197
	 * @deprecated 1.2.2 Use `Money::get_minor_units()` instead.
198
	 */
199
	public function get_cents() {
200
		return (float) $this->get_minor_units();
201
	}
202
203
	/**
204
	 * Get amount in minor units.
205
	 *
206
	 * Examples for value 10:
207 21
	 *   JPY 0 decimals: 10
208 21
	 *   EUR 2 decimals: 1000
209
	 *   BHD 3 decimals: 10000
210
	 *   NLG 4 decimals: 100000
211 21
	 *
212
	 * @since 1.2.1
213 21
	 *
214
	 * @return int
215 21
	 */
216
	public function get_minor_units() {
217
		$minor_units = $this->amount->multiply( Number::from_mixed( \pow( 10, $this->currency->get_number_decimals() ) ) );
218
219
		return (int) $minor_units->get_value();
220
	}
221
222
	/**
223
	 * Set value.
224 95
	 *
225 95
	 * @param mixed $value Amount value.
226 95
	 * @return void
227
	 */
228
	final public function set_value( $value ) {
229
		$this->amount = Number::from_mixed( $value );
230
	}
231
232
	/**
233
	 * Set amount.
234
	 *
235
	 * @deprecated 1.2.0
236
	 * @param mixed $value Amount value.
237
	 * @return void
238
	 */
239
	public function set_amount( $value ) {
240
		_deprecated_function( __METHOD__, '1.2.0', 'Money::set_value()' );
241
242
		$this->set_value( $value );
243
	}
244
245
	/**
246 7
	 * Get currency.
247 7
	 *
248
	 * @return Currency
249
	 */
250
	public function get_currency() {
251
		return $this->currency;
252
	}
253
254
	/**
255
	 * Set currency.
256 95
	 *
257 95
	 * @param string|Currency $currency Currency.
258
	 * @return void
259
	 */
260
	final public function set_currency( $currency ) {
261
		if ( $currency instanceof Currency ) {
262
			$this->currency = $currency;
263 95
264 95
			return;
265
		}
266
267
		$this->currency = Currency::get_instance( $currency );
268
	}
269
270
	/**
271
	 * Create a string representation of this money object.
272
	 *
273
	 * @return string
274
	 */
275
	public function __toString() {
276
		return $this->format_i18n();
277
	}
278
279
	/**
280
	 * JSON serialize.
281
	 *
282
	 * @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
283 1
	 * @return object
284 1
	 */
285
	public function jsonSerialize() {
286 1
		$properties = array(
287
			'value' => $money->get_value(),
288
		);
289 1
290 1
		if ( null !== $this->currency ) {
291
			$properties['currency'] = $this->currency->jsonSerialize();
292 1
		}
293
294 1
		$object = (object) $properties;
295
296 1
		return $object;
297
	}
298 1
299
	/**
300
	 * Returns a new Money object that represents
301
	 * the sum of this and an other Money object.
302
	 *
303
	 * @param Money $addend Addend.
304
	 *
305
	 * @return Money
306
	 */
307
	public function add( Money $addend ) {
308
		$result = $this->amount->add( $addend->get_number() );
309
310
		return new self( $result, $this->currency );
311
	}
312
313
	/**
314
	 * Returns a new Money object that represents
315
	 * the difference of this and an other Money object.
316
	 *
317
	 * @link https://github.com/moneyphp/money/blob/v3.2.1/src/Money.php#L235-L255
318
	 *
319
	 * @param Money $subtrahend Subtrahend.
320
	 *
321
	 * @return Money
322
	 */
323
	public function subtract( Money $subtrahend ) {
324
		$result = $this->amount->subtract( $subtrahend->get_number() );
325
326
		return new self( $result, $this->currency );
327
	}
328
329
	/**
330
	 * Returns a new Money object that represents
331
	 * the multiplied value of this Money object.
332
	 *
333
	 * @link https://github.com/moneyphp/money/blob/v3.2.1/src/Money.php#L299-L316
334
	 *
335
	 * @param int|float|string $multiplier Multiplier.
336
	 *
337
	 * @return Money
338
	 */
339
	public function multiply( $multiplier ) {
340
		$result = $this->amount->multiply( $multiplier->get_number() );
341
342
		return new self( $result, $this->currency );
343
	}
344
345
	/**
346
	 * Returns a new Money object that represents
347
	 * the divided value of this Money object.
348
	 *
349
	 * @link https://github.com/moneyphp/money/blob/v3.2.1/src/Money.php#L318-L341
350
	 *
351
	 * @param int|float|string $divisor Divisor.
352
	 *
353
	 * @return Money
354
	 */
355
	public function divide( $divisor ) {
356
		$result = $this->amount->divide( $divisor->get_number() );
357
358
		return new self( $result, $this->currency );
359
	}
360
361
	/**
362
	 * Absolute.
363
	 *
364
	 * @link https://github.com/moneyphp/money/blob/v4.0.1/src/Money.php#L411-L417
365
	 * @return Money
366
	 */
367
	public function absolute() {
368
		return new self(
369
			$this->amount->absolute(),
370
			$this->currency
371
		);
372
	}
373
374
	/**
375
	 * Initialize calculator.
376
	 *
377
	 * @return Calculator
378
	 *
379
	 * @throws \RuntimeException If cannot find calculator for money calculations.
380
	 */
381
	private static function initialize_calculator() {
382
		$calculator_classes = self::$calculators;
383
384
		foreach ( $calculator_classes as $calculator_class ) {
385
			if ( $calculator_class::supported() ) {
386
				$calculator = new $calculator_class();
387
388
				if ( $calculator instanceof Calculator ) {
389
					return $calculator;
390
				}
391
			}
392
		}
393
394
		throw new \RuntimeException( 'Cannot find calculator for money calculations' );
395
	}
396 1
397 1
	/**
398
	 * Get calculator.
399 1
	 *
400 1
	 * @return Calculator
401 1
	 */
402
	protected function get_calculator() {
403 1
		if ( null === self::$calculator ) {
404 1
			self::$calculator = self::initialize_calculator();
405
		}
406
407
		return self::$calculator;
408
	}
409
}
410