Completed
Push — master ( 5e016a...19cfc9 )
by Daniel
22s
created

UnboundedQuantityValue::asDecimalValue()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 23
rs 6.1403
cc 8
eloc 13
nc 8
nop 3
1
<?php
2
3
namespace DataValues;
4
5
use InvalidArgumentException;
6
7
/**
8
 * Class representing a quantity with associated unit.
9
 * The amount is stored as a @see DecimalValue object.
10
 *
11
 * @see QuantityValue for quantities with a known uncertainty interval.
12
 * For simple numeric amounts use @see NumberValue.
13
 *
14
 * @note UnboundedQuantityValue and QuantityValue both use the value type ID "quantity".
15
 * The fact that we use subclassing to model the bounded vs the unbounded case should be
16
 * considered an implementation detail.
17
 *
18
 * @since 0.8
19
 *
20
 * @license GPL-2.0+
21
 * @author Daniel Kinzler
22
 */
23
class UnboundedQuantityValue extends DataValueObject {
24
25
	/**
26
	 * The quantity's amount
27
	 *
28
	 * @var DecimalValue
29
	 */
30
	protected $amount;
31
32
	/**
33
	 * The quantity's unit identifier (use "1" for unitless quantities).
34
	 *
35
	 * @var string
36
	 */
37
	protected $unit;
38
39
	/**
40
	 * @param DecimalValue $amount
41
	 * @param string $unit A unit identifier. Must not be empty, use "1" for unit-less quantities.
42
	 *
43
	 * @throws IllegalValueException
44
	 */
45
	public function __construct( DecimalValue $amount, $unit ) {
46
		if ( !is_string( $unit ) || $unit === '' ) {
47
			throw new IllegalValueException( '$unit must be a non-empty string. Use "1" for unit-less quantities.' );
48
		}
49
50
		$this->amount = $amount;
51
		$this->unit = $unit;
52
	}
53
54
	/**
55
	 * Returns a QuantityValue representing the given amount.
56
	 *
57
	 * This is a convenience wrapper around the constructor that accepts native values
58
	 * instead of DecimalValue objects.
59
	 *
60
	 * @note: if the amount or a bound is given as a string, the string must conform
61
	 * to the rules defined by @see DecimalValue.
62
	 *
63
	 * @param string|int|float|DecimalValue $amount
64
	 * @param string $unit A unit identifier. Must not be empty, use "1" for unit-less quantities.
65
	 *
66
	 * @return self
67
	 * @throws IllegalValueException
68
	 */
69
	public static function newFromNumber( $amount, $unit = '1' ) {
70
		$amount = self::asDecimalValue( 'amount', $amount );
71
72
		return new self( $amount, $unit );
73
	}
74
75
	/**
76
	 * Converts $number to a DecimalValue if possible and necessary.
77
	 *
78
	 * @note: if the $number is given as a string, it must conform to the rules
79
	 *        defined by @see DecimalValue.
80
	 *
81
	 * @param string $name The variable name to use in exception messages
82
	 * @param string|int|float|DecimalValue|null $number
83
	 * @param DecimalValue|null $default
84
	 *
85
	 * @throws IllegalValueException
86
	 * @throws InvalidArgumentException
87
	 * @return DecimalValue
88
	 */
89
	protected static function asDecimalValue( $name, $number, DecimalValue $default = null ) {
90
		if ( !is_string( $name ) ) {
91
			throw new InvalidArgumentException( '$name must be a string' );
92
		}
93
94
		if ( $number === null ) {
95
			if ( $default === null ) {
96
				throw new InvalidArgumentException( '$' . $name . ' must not be null' );
97
			}
98
99
			$number = $default;
100
		}
101
102
		if ( $number instanceof DecimalValue ) {
0 ignored issues
show
Unused Code introduced by
This if statement is empty and can be removed.

This check looks for the bodies of if statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.

These if bodies can be removed. If you have an empty if but statements in the else branch, consider inverting the condition.

if (rand(1, 6) > 3) {
//print "Check failed";
} else {
    print "Check succeeded";
}

could be turned into

if (rand(1, 6) <= 3) {
    print "Check succeeded";
}

This is much more concise to read.

Loading history...
103
			// nothing to do
104
		} elseif ( is_int( $number ) || is_float( $number ) || is_string( $number ) ) {
105
			$number = new DecimalValue( $number );
106
		} else {
107
			throw new IllegalValueException( '$' . $name . '  must be a string, int, or float' );
108
		}
109
110
		return $number;
111
	}
112
113
	/**
114
	 * @see Serializable::serialize
115
	 *
116
	 * @return string
117
	 */
118
	public function serialize() {
119
		return serialize( array(
120
			$this->amount,
121
			$this->unit
122
		) );
123
	}
124
125
	/**
126
	 * @see Serializable::unserialize
127
	 *
128
	 * @param string $data
129
	 */
130
	public function unserialize( $data ) {
131
		list( $amount, $unit ) = unserialize( $data );
132
		$this->__construct( $amount, $unit );
133
	}
134
135
	/**
136
	 * @see DataValue::getType
137
	 *
138
	 * @return string
139
	 */
140
	public static function getType() {
141
		return 'quantity';
142
	}
143
144
	/**
145
	 * @see DataValue::getSortKey
146
	 *
147
	 * @return float
148
	 */
149
	public function getSortKey() {
150
		return $this->amount->getValueFloat();
151
	}
152
153
	/**
154
	 * Returns the quantity object.
155
	 * @see DataValue::getValue
156
	 *
157
	 * @return self
158
	 */
159
	public function getValue() {
160
		return $this;
161
	}
162
163
	/**
164
	 * Returns the amount represented by this quantity.
165
	 *
166
	 * @return DecimalValue
167
	 */
168
	public function getAmount() {
169
		return $this->amount;
170
	}
171
172
	/**
173
	 * Returns the unit held by this quantity.
174
	 * Unit-less quantities should use "1" as their unit.
175
	 *
176
	 * @return string
177
	 */
178
	public function getUnit() {
179
		return $this->unit;
180
	}
181
182
	/**
183
	 * Returns a transformed value derived from this QuantityValue by applying
184
	 * the given transformation to the amount and the upper and lower bounds.
185
	 * The resulting amount and bounds are rounded to the significant number of
186
	 * digits. Note that for exact quantities (with at least one bound equal to
187
	 * the amount), no rounding is applied (since they are considered to have
188
	 * infinite precision).
189
	 *
190
	 * The transformation is provided as a callback, which must implement a
191
	 * monotonously increasing, fully differentiable function mapping a DecimalValue
192
	 * to a DecimalValue. Typically, it will be a linear transformation applying a
193
	 * factor and an offset.
194
	 *
195
	 * @param string $newUnit The unit of the transformed quantity.
196
	 *
197
	 * @param callable $transformation A callback that implements the desired transformation.
198
	 *        The transformation will be called three times, once for the amount, once
199
	 *        for the lower bound, and once for the upper bound. It must return a DecimalValue.
200
	 *        The first parameter passed to $transformation is the DecimalValue to transform
201
	 *        In addition, any extra parameters passed to transform() will be passed through
202
	 *        to the transformation callback.
203
	 *
204
	 * @param mixed ... Any extra parameters will be passed to the $transformation function.
205
	 *
206
	 * @todo Should be factored out into a separate QuantityMath class.
207
	 *
208
	 * @throws InvalidArgumentException
209
	 * @return self
210
	 */
211
	public function transform( $newUnit, $transformation ) {
212
		if ( !is_callable( $transformation ) ) {
213
			throw new InvalidArgumentException( '$transformation must be callable.' );
214
		}
215
216
		if ( !is_string( $newUnit ) || $newUnit === '' ) {
217
			throw new InvalidArgumentException( '$newUnit must be a non-empty string. Use "1" for unit-less quantities.' );
218
		}
219
220
		// Apply transformation by calling the $transform callback.
221
		// The first argument for the callback is the DataValue to transform. In addition,
222
		// any extra arguments given for transform() are passed through.
223
		$args = func_get_args();
224
		array_shift( $args );
225
226
		$args[0] = $this->amount;
227
		$amount = call_user_func_array( $transformation, $args );
228
229
		return new self( $amount, $newUnit );
230
	}
231
232
	public function __toString() {
233
		return $this->amount->getValue()
234
			. ( $this->unit === '1' ? '' : $this->unit );
235
	}
236
237
	/**
238
	 * @see DataValue::getArrayValue
239
	 *
240
	 * @return array
241
	 */
242
	public function getArrayValue() {
243
		return array(
244
			'amount' => $this->amount->getArrayValue(),
245
			'unit' => $this->unit,
246
		);
247
	}
248
249
	/**
250
	 * Constructs a new instance of the DataValue from the provided data.
251
	 * This can round-trip with @see getArrayValue
252
	 *
253
	 * @param mixed $data
254
	 *
255
	 * @return self
256
	 * @throws IllegalValueException
257
	 */
258
	public static function newFromArray( $data ) {
259
		self::requireArrayFields( $data, array( 'amount', 'unit' ) );
260
261
		return new static(
262
			DecimalValue::newFromArray( $data['amount'] ),
263
			$data['unit']
264
		);
265
	}
266
267
	/**
268
	 * @see Comparable::equals
269
	 *
270
	 * @param mixed $target
271
	 *
272
	 * @return bool
273
	 */
274
	public function equals( $target ) {
275
		if ( $this === $target ) {
276
			return true;
277
		}
278
279
		return $target instanceof self
280
			&& $this->toArray() === $target->toArray();
281
	}
282
283
}
284