Bytes::disablePrecisionTrimming()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 3
nc 1
nop 0
1
<?php
2
namespace nochso\Omni\Format;
3
4
use nochso\Omni\Numeric;
5
6
/**
7
 * Bytes formats a quantity of bytes using different suffixes and binary or decimal base.
8
 *
9
 * By default a binary base and IEC suffixes are used:
10
 *
11
 * ```php
12
 * Bytes::create()->format(1100); // '1.07 KiB'
13
 * ```
14
 *
15
 * You can pick a base and suffix with `create()` or use the specifc setter methods.
16
 */
17
class Bytes {
18
	/**
19
	 * 1024 binary base.
20
	 */
21
	const BASE_BINARY = 1024;
22
	/**
23
	 * 1000 decimal base.
24
	 */
25
	const BASE_DECIMAL = 1000;
26
	/**
27
	 * B, M, G, ...
28
	 */
29
	const SUFFIX_SIMPLE = 0;
30
	/**
31
	 * KiB, MiB, GiB, ... (SHOULD be used with BASE_BINARY).
32
	 */
33
	const SUFFIX_IEC = 1;
34
	/**
35
	 * kibibytes, mebibytes, gibibytes, ... (SHOULD be used with BASE_BINARY).
36
	 */
37
	const SUFFIX_IEC_LONG = 2;
38
	/**
39
	 * kB, MB, GB, ... (SHOULD be used with BASE_DECIMAL).
40
	 */
41
	const SUFFIX_SI = 3;
42
	/**
43
	 * kilobytes, megabytes, gigabytes, ... (SHOULD be used with BASE_DECIMAL).
44
	 */
45
	const SUFFIX_SI_LONG = 4;
46
47
	private static $suffixes = [
48
		self::SUFFIX_SIMPLE => ['B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'],
49
		self::SUFFIX_IEC => ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'],
50
		self::SUFFIX_IEC_LONG => [
51
			'byte(s)',
52
			'kibibyte(s)',
53
			'mebibyte(s)',
54
			'gibibyte(s)',
55
			'tebibyte(s)',
56
			'pebibyte(s)',
57
			'exbibyte(s)',
58
			'zebibyte(s)',
59
			'yobibyte(s)',
60
		],
61
		self::SUFFIX_SI => ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
62
		self::SUFFIX_SI_LONG => [
63
			'byte(s)',
64
			'kilobyte(s)',
65
			'megabyte(s)',
66
			'gigabyte(s)',
67
			'terabyte(s)',
68
			'petabyte(s)',
69
			'exabyte(s)',
70
			'zettabyte(s)',
71
			'yottabyte(s)',
72
		],
73
	];
74
	/**
75
	 * @var int
76
	 */
77
	private $base = self::BASE_BINARY;
78
	/**
79
	 * @var int
80
	 */
81
	private $suffix = self::SUFFIX_IEC;
82
	/**
83
	 * @var int
84
	 */
85
	private $precision = 2;
86
	/**
87
	 * @var bool
88
	 */
89
	private $precisionTrimming = true;
90
91
	/**
92
	 * Create a new Bytes instance.
93
	 *
94
	 * @param int $base   The base to use when converting to different units. Must be one of the `Bytes::BASE_*`
95
	 *                    constants. Optional, defaults to `BASE_BINARY`.
96
	 * @param int $suffix The suffix style for units. Must be one of the `Bytes::SUFFIX_*` constants. Optional,
97
	 *                    defaults to SUFFIX_IEC (KiB, MiB, etc.)
98
	 *
99
	 * @return \nochso\Omni\Bytes
100
	 */
101
	public static function create($base = self::BASE_BINARY, $suffix = self::SUFFIX_IEC) {
102
		$bytes = new self();
103
		$bytes->setBase($base)->setSuffix($suffix);
104
		return $bytes;
105
	}
106
107
	/**
108
	 * setBase to use when converting to different units.
109
	 *
110
	 * @param int $base Must be one of the `Bytes::BASE_*` constants.
111
	 *
112
	 * @return $this
113
	 */
114
	public function setBase($base) {
115
		if ($base !== self::BASE_BINARY && $base !== self::BASE_DECIMAL) {
116
			throw new \InvalidArgumentException('Unknown base. Use either Bytes::BASE_BINARY or Bytes::BASE_DECIMAL');
117
		}
118
		$this->base = $base;
119
		return $this;
120
	}
121
122
	/**
123
	 * setSuffix style for units.
124
	 *
125
	 * @param int $suffix Must be one of the `Bytes::SUFFIX_*` constants.
126
	 *
127
	 * @return $this
128
	 */
129
	public function setSuffix($suffix) {
130
		if (!isset(self::$suffixes[$suffix])) {
131
			throw new \InvalidArgumentException('Unknown suffix. Use one of the Bytes::SUFFIX_* constants.');
132
		}
133
		$this->suffix = $suffix;
134
		return $this;
135
	}
136
137
	/**
138
	 * setPrecision of floating point values after the decimal point.
139
	 *
140
	 * @param int $precision Any non-negative integer.
141
	 *
142
	 * @return $this
143
	 */
144
	public function setPrecision($precision) {
145
		$this->precision = Numeric::ensureInteger($precision);
146
		return $this;
147
	}
148
149
	/**
150
	 * enablePrecisionTrimming to remove trailing zeroes and decimal points.
151
	 *
152
	 * @return $this
153
	 */
154
	public function enablePrecisionTrimming() {
155
		$this->precisionTrimming = true;
156
		return $this;
157
	}
158
159
	/**
160
	 * disablePrecisionTrimming to keep trailing zeroes.
161
	 *
162
	 * @return $this
163
	 */
164
	public function disablePrecisionTrimming() {
165
		$this->precisionTrimming = false;
166
		return $this;
167
	}
168
169
	/**
170
	 * Format a quantity of bytes for human consumption.
171
	 *
172
	 * @param int $bytes
173
	 *
174
	 * @return string
175
	 */
176
	public function format($bytes) {
177
		$bytes = Numeric::ensure($bytes);
178
		if (is_float($bytes) && $bytes > 0.0 && $bytes < 1.0) {
179
			throw new \InvalidArgumentException('Floats smaller than one can not be formatted.');
180
		}
181
		// 0 bytes won't work with log(), so set defaults for this case
182
		$exponent = 0;
183
		$normBytes = 0;
184
		if ($bytes !== 0) {
185
			$exponent = log(abs($bytes), $this->base);
186
			$normBytes = pow($this->base, $exponent - floor($exponent));
187
			// Make bytes negative again if needed
188
			$normBytes *= $bytes >= 0 ? 1 : -1;
189
		}
190
		$suffix = self::$suffixes[$this->suffix][$exponent];
191
		$number = number_format($normBytes, $this->precision, '.', '');
192
		$number = $this->trimPrecision($number);
193
		$suffix = Quantity::format($suffix, $number);
194
		return sprintf('%s %s', $number, $suffix);
195
	}
196
197
	/**
198
	 * @param string $number
199
	 *
200
	 * @return float
201
	 */
202
	private function trimPrecision($number) {
203
		if ($this->precisionTrimming && strpos((string) $number, '.') !== false) {
204
			$number = rtrim($number, '0');
205
			$number = rtrim($number, '.');
206
			$number = (double) $number;
207
		}
208
		return $number;
209
	}
210
}
211