Skip to content

Instantly share code, notes, and snippets.

@inxilpro
Created January 5, 2023 23:57
Show Gist options
  • Save inxilpro/35f511eab511d0abe9796bcc3c5dea1f to your computer and use it in GitHub Desktop.
Save inxilpro/35f511eab511d0abe9796bcc3c5dea1f to your computer and use it in GitHub Desktop.
<?php
namespace App\Support;
use InvalidArgumentException;
class Bytes
{
// Most modern systems use base-1000 rather than base-1024 for file size now
protected const BASE = 1000;
protected const UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
protected const PRECISIONS = [0, 0, 2, 2, 3, 3, 4, 4, 5];
// Technically 'KiB'-style suffixes are base-1024, so we could potentially configure that
protected const ALIASES = ['k' => 1, 'kib' => 1, 'm' => 2, 'mib' => 2, 'g' => 3, 'gib' => 3, 't' => 4, 'tib' => 4, 'pib' => 5, 'eib' => 6, 'zib' => 7, 'yib' => 8];
public static function from(int|string|self $value): static
{
return match (get_debug_type($value)) {
'int' => new static($value),
'string' => static::parse($value),
static::class => new static($value->bytes),
default => throw new InvalidArgumentException('Invalid value passed to Bytes::from'),
};
}
public static function parse(string $value): static
{
if (! preg_match('/([0-9,.]+)\s*([a-z]+)/i', $value, $matches)) {
throw new InvalidArgumentException("Unable to parse '{$value}' as a file size.");
}
$size = (float) str_replace(',', '', $matches[1]);
$exponent = static::suffixToExponent($matches[2]);
$multiplier = static::BASE ** $exponent;
return static::from($size * $multiplier);
}
protected static function suffixToExponent(string $suffix): int
{
$lookup = collect(static::UNITS)
->combine(array_keys(static::UNITS))
->union(static::ALIASES);
return $lookup->first(fn($exponent, $key) => 0 === strcasecmp($key, $suffix), function() use ($suffix) {
throw new InvalidArgumentException("Unknown suffix: {$suffix}");
});
}
public function __construct(
public int $bytes
) {
}
public function toKb(): float
{
return $this->bytes / static::BASE;
}
public function toMb(): float
{
return $this->bytes / (static::BASE ** 2);
}
public function toGb(): float
{
return $this->bytes / (static::BASE ** 3);
}
public function toTb(): float
{
return $this->bytes / (static::BASE ** 4);
}
public function toPb(): float
{
return $this->bytes / (static::BASE ** 5);
}
public function __toString(): string
{
// Don't go over the largest configured unit
$highest_allowed_exponent = count(static::UNITS) - 1;
// Ensure we have bytes
$bytes = max($this->bytes, 1);
// Shoehorn bytes into one of our groups based on 1000's of bytes
// ie. 100 bytes is 0, 1000 bytes is 1, 1000000 bytes is 2, etc
$exponent = min(floor(log($bytes) / log(static::BASE)), $highest_allowed_exponent);
// Now convert bytes to our chosen size, and display
$size = $bytes / (static::BASE ** $exponent);
$unit = static::UNITS[$exponent];
$precision = static::PRECISIONS[$exponent] ?? 3;
return round($size, $precision).' '.$unit;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment