UnboundedQuantityValue   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 280
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 88.33%

Importance

Changes 0
Metric Value
wmc 32
lcom 1
cbo 4
dl 0
loc 280
ccs 53
cts 60
cp 0.8833
rs 9.84
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 3
A newFromNumber() 0 5 1
B asDecimalValue() 0 23 8
A serialize() 0 6 1
A unserialize() 0 4 1
A getType() 0 3 1
A getSortKey() 0 3 1
A getValue() 0 3 1
A getAmount() 0 3 1
A getUnit() 0 3 1
A transform() 0 20 4
A __toString() 0 4 2
A getArrayValue() 0 6 1
A newFromArray() 0 19 3
A equals() 0 8 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 32
	public function __construct( DecimalValue $amount, $unit ) {
46 32
		if ( !is_string( $unit ) || $unit === '' ) {
47 2
			throw new IllegalValueException( '$unit must be a non-empty string. Use "1" for unit-less quantities.' );
48
		}
49
50 30
		$this->amount = $amount;
51 30
		$this->unit = $unit;
52 30
	}
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 InvalidArgumentException
68
	 */
69 7
	public static function newFromNumber( $amount, $unit = '1' ) {
70 7
		$amount = self::asDecimalValue( 'amount', $amount );
71
72 7
		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 InvalidArgumentException
86
	 * @return DecimalValue
87
	 */
88 7
	protected static function asDecimalValue( $name, $number, DecimalValue $default = null ) {
89 7
		if ( !is_string( $name ) ) {
90
			throw new InvalidArgumentException( '$name must be a string' );
91
		}
92
93 7
		if ( $number === null ) {
94
			if ( $default === null ) {
95
				throw new InvalidArgumentException( '$' . $name . ' must not be null' );
96
			}
97
98
			$number = $default;
99
		}
100
101 7
		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...
102
			// nothing to do
103 6
		} elseif ( is_int( $number ) || is_float( $number ) || is_string( $number ) ) {
104 6
			$number = new DecimalValue( $number );
105
		} else {
106
			throw new IllegalValueException( '$' . $name . '  must be a string, int, or float' );
107
		}
108
109 7
		return $number;
110
	}
111
112
	/**
113
	 * @see Serializable::serialize
114
	 *
115
	 * @return string
116
	 */
117 9
	public function serialize() {
118 9
		return serialize( [
119 9
			$this->amount,
120 9
			$this->unit
121
		] );
122
	}
123
124
	/**
125
	 * @see Serializable::unserialize
126
	 *
127
	 * @param string $data
128
	 */
129 9
	public function unserialize( $data ) {
130 9
		list( $amount, $unit ) = unserialize( $data );
131 9
		$this->__construct( $amount, $unit );
132 9
	}
133
134
	/**
135
	 * @see DataValue::getType
136
	 *
137
	 * @return string
138
	 */
139 15
	public static function getType() {
140 15
		return 'quantity';
141
	}
142
143
	/**
144
	 * @see DataValue::getSortKey
145
	 *
146
	 * @return float
147
	 */
148 3
	public function getSortKey() {
149 3
		return $this->amount->getValueFloat();
150
	}
151
152
	/**
153
	 * Returns the quantity object.
154
	 * @see DataValue::getValue
155
	 *
156
	 * @return self
157
	 */
158 6
	public function getValue() {
159 6
		return $this;
160
	}
161
162
	/**
163
	 * Returns the amount represented by this quantity.
164
	 *
165
	 * @return DecimalValue
166
	 */
167 20
	public function getAmount() {
168 20
		return $this->amount;
169
	}
170
171
	/**
172
	 * Returns the unit held by this quantity.
173
	 * Unit-less quantities should use "1" as their unit.
174
	 *
175
	 * @return string
176
	 */
177 10
	public function getUnit() {
178 10
		return $this->unit;
179
	}
180
181
	/**
182
	 * Returns a transformed value derived from this QuantityValue by applying
183
	 * the given transformation to the amount and the upper and lower bounds.
184
	 * The resulting amount and bounds are rounded to the significant number of
185
	 * digits. Note that for exact quantities (with at least one bound equal to
186
	 * the amount), no rounding is applied (since they are considered to have
187
	 * infinite precision).
188
	 *
189
	 * The transformation is provided as a callback, which must implement a
190
	 * monotonously increasing, fully differentiable function mapping a DecimalValue
191
	 * to a DecimalValue. Typically, it will be a linear transformation applying a
192
	 * factor and an offset.
193
	 *
194
	 * @param string $newUnit The unit of the transformed quantity.
195
	 *
196
	 * @param callable $transformation A callback that implements the desired transformation.
197
	 *        The transformation will be called three times, once for the amount, once
198
	 *        for the lower bound, and once for the upper bound. It must return a DecimalValue.
199
	 *        The first parameter passed to $transformation is the DecimalValue to transform
200
	 *        In addition, any extra parameters passed to transform() will be passed through
201
	 *        to the transformation callback.
202
	 *
203
	 * @param mixed ... Any extra parameters will be passed to the $transformation function.
204
	 *
205
	 * @todo Should be factored out into a separate QuantityMath class.
206
	 *
207
	 * @throws InvalidArgumentException
208
	 * @return self
209
	 */
210 7
	public function transform( $newUnit, $transformation ) {
211 7
		if ( !is_callable( $transformation ) ) {
212
			throw new InvalidArgumentException( '$transformation must be callable.' );
213
		}
214
215 7
		if ( !is_string( $newUnit ) || $newUnit === '' ) {
216
			throw new InvalidArgumentException( '$newUnit must be a non-empty string. Use "1" for unit-less quantities.' );
217
		}
218
219
		// Apply transformation by calling the $transform callback.
220
		// The first argument for the callback is the DataValue to transform. In addition,
221
		// any extra arguments given for transform() are passed through.
222 7
		$args = func_get_args();
223 7
		array_shift( $args );
224
225 7
		$args[0] = $this->amount;
226 7
		$amount = call_user_func_array( $transformation, $args );
227
228 7
		return new self( $amount, $newUnit );
229
	}
230
231 2
	public function __toString() {
232 2
		return $this->amount->getValue()
233 2
			. ( $this->unit === '1' ? '' : $this->unit );
234
	}
235
236
	/**
237
	 * @see DataValue::getArrayValue
238
	 *
239
	 * @return array
240
	 */
241 15
	public function getArrayValue() {
242
		return [
243 15
			'amount' => $this->amount->getArrayValue(),
244 15
			'unit' => $this->unit,
245
		];
246
	}
247
248
	/**
249
	 * Static helper capable of constructing both unbounded and bounded quantity value objects,
250
	 * depending on the serialization provided. Required for @see DataValueDeserializer.
251
	 * This is expected to round-trip with both @see getArrayValue as well as
252
	 * @see QuantityValue::getArrayValue.
253
	 *
254
	 * @deprecated since 0.8.3. Static DataValue::newFromArray constructors like this are
255
	 *  underspecified (not in the DataValue interface), and misleadingly named (should be named
256
	 *  newFromArrayValue). Instead, use DataValue builder callbacks in @see DataValueDeserializer.
257
	 *
258
	 * @param mixed $data Warning! Even if this is expected to be a value as returned by
259
	 *  @see getArrayValue, callers of this specific newFromArray implementation can not guarantee
260
	 *  this. This is not even guaranteed to be an array!
261
	 *
262
	 * @throws IllegalValueException if $data is not in the expected format. Subclasses of
263
	 *  InvalidArgumentException are expected and properly handled by @see DataValueDeserializer.
264
	 * @return self|QuantityValue Either an unbounded or bounded quantity value object.
265
	 */
266
	public static function newFromArray( $data ) {
267
		self::requireArrayFields( $data, [ 'amount', 'unit' ] );
268
269
		if ( !isset( $data['upperBound'] ) && !isset( $data['lowerBound'] ) ) {
270
			return new self(
271
				DecimalValue::newFromArray( $data['amount'] ),
0 ignored issues
show
Deprecated Code introduced by
The method DataValues\DecimalValue::newFromArray() has been deprecated with message: since 0.8.3. Static DataValue::newFromArray constructors like this are underspecified (not in the DataValue interface), and misleadingly named (should be named newFromArrayValue). Instead, use DataValue builder callbacks in @see DataValueDeserializer.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
272
				$data['unit']
273
			);
274
		} else {
275
			self::requireArrayFields( $data, [ 'upperBound', 'lowerBound' ] );
276
277
			return new QuantityValue(
278
				DecimalValue::newFromArray( $data['amount'] ),
0 ignored issues
show
Deprecated Code introduced by
The method DataValues\DecimalValue::newFromArray() has been deprecated with message: since 0.8.3. Static DataValue::newFromArray constructors like this are underspecified (not in the DataValue interface), and misleadingly named (should be named newFromArrayValue). Instead, use DataValue builder callbacks in @see DataValueDeserializer.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
279
				$data['unit'],
280
				DecimalValue::newFromArray( $data['upperBound'] ),
0 ignored issues
show
Deprecated Code introduced by
The method DataValues\DecimalValue::newFromArray() has been deprecated with message: since 0.8.3. Static DataValue::newFromArray constructors like this are underspecified (not in the DataValue interface), and misleadingly named (should be named newFromArrayValue). Instead, use DataValue builder callbacks in @see DataValueDeserializer.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
281
				DecimalValue::newFromArray( $data['lowerBound'] )
0 ignored issues
show
Deprecated Code introduced by
The method DataValues\DecimalValue::newFromArray() has been deprecated with message: since 0.8.3. Static DataValue::newFromArray constructors like this are underspecified (not in the DataValue interface), and misleadingly named (should be named newFromArrayValue). Instead, use DataValue builder callbacks in @see DataValueDeserializer.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
282
			);
283
		}
284
	}
285
286
	/**
287
	 * @see Comparable::equals
288
	 *
289
	 * @param mixed $target
290
	 *
291
	 * @return bool
292
	 */
293 12
	public function equals( $target ) {
294 12
		if ( $this === $target ) {
295 3
			return true;
296
		}
297
298 12
		return $target instanceof self
299 12
			&& $this->toArray() === $target->toArray();
300
	}
301
302
}
303