khaledalam / unit
A lightweight, type-safe PHP library for units, quantities, and dimensional analysis with automatic conversion.
Requires
- php: ^8.2
Requires (Dev)
- doctrine/dbal: ^4.0
- friendsofphp/php-cs-fixer: ^3.64
- illuminate/database: ^11.0 || ^12.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^11.4
Suggests
- doctrine/dbal: For the Doctrine 'QuantityType' (usable in Symfony and any Doctrine project).
- illuminate/database: For the Laravel service provider and Eloquent 'AsQuantity' cast.
This package is auto-updated.
Last update: 2026-07-10 17:50:26 UTC
README
Type-safe units, quantities & dimensional analysis for PHP
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.
▶️ Try the live demo — an interactive converter running in your browser.
Why Unit?
| Unit | Hand-rolled * factor |
Raw floats | |
|---|---|---|---|
Blocks nonsensical math (m + s) |
✅ | ❌ | ❌ |
| Auto-converts compatible units | ✅ | ❌ | ❌ |
| Affine temperature scales (°C/°F/K) | ✅ | ⚠️ easy to get wrong | ❌ |
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.
Installation
composer require khaledalam/unit
Requires PHP 8.2+.
Quick start
<?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"
Features
- ✅ 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
Usage
Conversion
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"
Arithmetic
$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.
Scalar math
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"
Dimension names
Quantity::of(1, 'm/s')->dimensionName(); // "velocity" Quantity::of(1, 'N')->dimensionName(); // "force" Quantity::of(1, 'Pa')->dimensionName(); // "pressure"
Temperature (affine scales)
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"
Parsing strings
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)
Humanize (auto-pick the best unit)
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"
Comparison
Quantity::of(1, 'm')->isGreaterThan(Quantity::of(90, 'cm')); // true Quantity::of(1, 'm')->equals(Quantity::of(100, 'cm')); // true
Precision & formatting
echo Quantity::of(1, 'm')->divide(Quantity::of(3, 's'))->format(2); // "0.33 m/s"
JSON
echo json_encode(Quantity::of(2, 'm')); // {"value":2,"unit":"m"}
Custom units
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"
Incompatible operations throw
Quantity::of(5, 'kg')->add(Quantity::of(3, 's')); // InvalidArgumentException
Supported units
| 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.
Laravel integration
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 in
Register 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).
Symfony / Doctrine integration
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 / bootstrap
use 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.
Examples
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.php
Testing & quality
composer 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).
Contributing
Contributions are welcome! Please read CONTRIBUTING.md and the Code of Conduct. Adding a unit is usually a three-line change.
License
MIT © Khaled Alam