A lightweight, immutable, type-safe PHP library for working with quantities, units, and dimensional analysis. Define units, convert across compatible ones, and do arithmetic that refuses to add meters to seconds.
use KhaledAlam\Unit\Quantity;
echo Quantity::of(2, 'm')->add(Quantity::of(100, 'cm'))->to('m'); // "3 m"
echo Quantity::of(100, '°C')->to('°F')->format(1); // "212.0 °F"
echo Quantity::of(4, 'm')->multiply(Quantity::of(3, 'm')); // "12 m²"No setup, no registration — common units are ready the moment you install.
| Unit | Hand-rolled * factor |
Raw floats | |
|---|---|---|---|
Blocks nonsensical math (m + s) |
✅ | ❌ | ❌ |
| Auto-converts compatible units | ✅ | ❌ | ❌ |
| Affine temperature scales (°C/°F/K) | ✅ | ❌ | |
Derived units (m/s, m²) |
✅ | ❌ | ❌ |
| Immutable value objects | ✅ | — | ❌ |
| Zero dependencies | ✅ | ✅ | ✅ |
If you've ever shipped a bug because someone stored centimeters in a "meters" column, this library is for you.
composer require khaledalam/unitRequires PHP 8.2+.
<?php
require __DIR__ . '/vendor/autoload.php';
use KhaledAlam\Unit\Quantity;
// Common units are auto-registered — just use them.
$length1 = Quantity::of(2.0, 'm');
$length2 = Quantity::of(100.0, 'cm');
$sum = $length1->add($length2); // auto-converts cm -> m
echo $sum->to('m'); // "3 m"- ✅ Immutable value objects
- ✅ Dimensionally-aware arithmetic (
add,subtract,multiply,divide) - ✅ Automatic conversion between compatible units (e.g. cm → m)
- ✅ Correct affine temperature conversion (°C ↔ °F ↔ K)
- ✅ Derived/compound units rendered as
m/s,m²,m·s - ✅ Scalar math (
times,dividedBy,pow,sqrt,abs,negate) - ✅ Named dimensions (
dimensionName()→"velocity") - ✅ Comparisons (
equals,isGreaterThan,isLessThan) - ✅
JsonSerializableoutput, Laravel cast & Doctrine type - ✅ Enum-powered unit naming and a custom unit registry
- ✅ Zero runtime dependencies
echo Quantity::of(2, 'm')->to('cm'); // "200 cm"
echo Quantity::of(5, 'km')->to('m'); // "5000 m"
echo Quantity::of(1, 'h')->to('s'); // "3600 s"$speed = Quantity::of(10, 'm')->divide(Quantity::of(2, 's')); // "5 m/s"
$area = Quantity::of(2, 'm')->multiply(Quantity::of(3, 'm')); // "6 m²"All operations return new Quantity objects with the proper dimension and unit.
echo Quantity::of(5, 'm')->times(3); // "15 m"
echo Quantity::of(10, 'm')->dividedBy(4); // "2.5 m"
echo Quantity::of(2, 'm')->pow(2); // "4 m²"
echo Quantity::of(4, 'm²')->sqrt(); // "2 m"
echo Quantity::of(-3, 'm')->abs(); // "3 m"Quantity::of(1, 'm/s')->dimensionName(); // "velocity"
Quantity::of(1, 'N')->dimensionName(); // "force"
Quantity::of(1, 'Pa')->dimensionName(); // "pressure"echo Quantity::of(100, '°C')->to('°F')->format(1); // "212.0 °F"
echo Quantity::of(32, '°F')->to('°C')->format(1); // "0.0 °C"
echo Quantity::of(25, '°C')->to('K')->format(2); // "298.15 K"echo Quantity::parse('100 km/h')->to('mph')->format(2); // "62.14 mph"
echo Quantity::parse('-40 °C')->to('°F')->format(1); // "-40.0 °F"
echo Quantity::parse('5 ft 3 in')->to('cm')->format(2); // "160.02 cm" (segments summed)echo Quantity::of(1500, 'm')->humanize(); // "1.5 km"
echo Quantity::of(2500000, 'B')->humanize(); // "2.5 MB"
echo Quantity::of(5400, 's')->humanize(); // "1.5 h"Quantity::of(1, 'm')->isGreaterThan(Quantity::of(90, 'cm')); // true
Quantity::of(1, 'm')->equals(Quantity::of(100, 'cm')); // trueecho Quantity::of(1, 'm')->divide(Quantity::of(3, 's'))->format(2); // "0.33 m/s"echo json_encode(Quantity::of(2, 'm')); // {"value":2,"unit":"m"}Register your own units against a dimension:
use KhaledAlam\Unit\{Unit, Name, Dimension, UnitRegistry};
UnitRegistry::register(new Unit('yard', 'yd', 0.9144, new Dimension(['L' => 1])));
echo Quantity::of(1, 'yd')->to('m'); // "0.9144 m"Quantity::of(5, 'kg')->add(Quantity::of(3, 's')); // InvalidArgumentException| Dimension | Units |
|---|---|
| Length | mm, cm, m, km, in, ft, yd, mi |
| Mass | mg, g, kg, t, lb, oz |
| Time | ms, s, min, h, d, wk |
| Area | cm², m², km², ft², ha, acre |
| Volume | mL, L, m³, gal |
| Temperature | °C, °F, K |
| Speed | m/s, km/h, mph, kn |
| Data | bit, B, KB, MB, GB, TB, KiB, MiB, GiB, TiB |
| Force | N, kN |
| Energy | J, kJ, cal, kcal, Wh, kWh |
| Power | W, kW, hp |
| Pressure | Pa, kPa, bar, atm, psi |
| Frequency | Hz, kHz, MHz, GHz |
| Angle | rad, deg, grad, turn |
Need more? Open an issue or register your own.
The package ships a service provider (auto-discovered) and an Eloquent cast. Store a
quantity in a single column and get a Quantity back on access:
use Illuminate\Database\Eloquent\Model;
use KhaledAlam\Unit\Laravel\AsQuantity;
class Parcel extends Model
{
protected function casts(): array
{
return ['weight' => AsQuantity::class];
}
}
$parcel->weight = Quantity::of(2.5, 'kg'); // stored as "2.5 kg"
$parcel->weight->to('g'); // 2500 g
$parcel->weight = '5 ft 3 in'; // strings are parsed on the way inRegister app-specific units via config/unit.php:
return [
'units' => [
new \KhaledAlam\Unit\Unit('furlong', 'fur', 201.168, new \KhaledAlam\Unit\Dimension(['L' => 1])),
],
];Requires
illuminate/database(already present in any Laravel app).
A Doctrine DBAL type persists a Quantity as a string column. Register it once, then
map any column with it:
use Doctrine\DBAL\Types\Type;
use KhaledAlam\Unit\Doctrine\QuantityType;
Type::addType(QuantityType::NAME, QuantityType::class); // e.g. in a bundle boot / bootstrapuse Doctrine\ORM\Mapping as ORM;
#[ORM\Column(type: 'quantity', nullable: true)]
public ?Quantity $weight = null;Strings assigned to the property are parsed on write, and the column hydrates back into a
Quantity on read.
Requires
doctrine/dbal^4.
Runnable scripts live in examples/:
php examples/basic.php
php examples/temperature.php
php examples/parse-and-humanize.php
php examples/scalar-math.php
php examples/shipping.php
php examples/physics.phpcomposer test # PHPUnit
composer analyse # PHPStan (level max)
composer cs # PHP-CS-Fixer (dry run)The suite combines PHPUnit test classes with .phpt scenario tests and holds 100%
line coverage, verified on PHP 8.2, 8.3, and 8.4 in CI (PHPStan level max, PSR-12).
Contributions are welcome! Please read CONTRIBUTING.md and the Code of Conduct. Adding a unit is usually a three-line change.
MIT © Khaled Alam