triplepoint/php-units-of-measure
A PHP library for representing and converting units of measure. Work with physical quantities, perform arithmetic across compatible units, and add dimensional safety to applications that need reliable measurement calculations.
composer require triplepoint/php-units-of-measure
There is no service provider, config file, or facade — it is a plain Composer library, so it works identically in Laravel, plain PHP, or a queue worker.
The first practical use case is boundary conversion: accept a measurement from a user/API/import, convert it to a canonical unit immediately, and convert back to a display unit only when rendering.
use PhpUnitsOfMeasure\PhysicalQuantity\Length;
$package = new Length(12, 'in');
$package->toUnit('cm'); // 30.48
$package->toNativeUnit(); // 0.3048 (Length's native unit is the meter)
Where to look first:
src/PhysicalQuantity/ — the authoritative list of supported quantities and, inside each class, the exact unit names and aliases. This is your cheat sheet; the README does not enumerate everything.src/PhysicalQuantity.php — the core API: toUnit(), toNativeUnit(), add(), subtract(), isEquivalentTo(), getUnit().src/UnitOfMeasure.php — conversion closures. Look here to understand why temperature behaves differently from length.tests/ — the best documentation of edge cases, aliases, and offset units.Pick one storage convention (usually the quantity's native unit) and do all conversion at the application boundary. Do not persist a mixture of units in the same column.
final class MeasurementNormalizer
{
public function lengthInMeters(float $value, string $unit): float
{
return (new Length($value, $unit))->toNativeUnit();
}
}
Type-hint concrete quantity classes rather than passing float $value, string $unit pairs around. It makes illegal states harder to represent and keeps conversion logic in one place.
function shippingCost(Length $length, Mass $mass): Money
{
// ...
}
add() and subtract() operate in native units and mutate the receiver. Clone first if you want immutable semantics.
$total = new Length(1, 'm');
$total->add(new Length(50, 'cm')); // 1.5 m
$total->subtract(new Length(2, 'in'));
echo $total->toUnit('cm');
The constructor throws for unknown units and non-numeric input, which makes it a natural validation primitive.
use PhpUnitsOfMeasure\Exception\UnknownUnitOfMeasure;
try {
$length = new Length($request->input('value'), $request->input('unit'));
} catch (UnknownUnitOfMeasure $e) {
abort(422, 'Unsupported unit.');
}
A Laravel validation rule can wrap the same try/catch:
public function passes($attribute, $value): bool
{
try {
new Length((float) ($value['value'] ?? 0), (string) ($value['unit'] ?? ''));
return true;
} catch (\Throwable) {
return false;
}
}
Store a native-unit float plus the original unit string (or just the native float if you never need to redisplay the original). Convert in an accessor/mutator. Keep the accessor's unit fixed so DB data is unambiguous.
Never serialize the object directly. Emit an explicit shape:
return [
'value' => round($length->toUnit('ft'), 3),
'unit' => 'ft',
];
Use isEquivalentTo() rather than == on converted floats.
$length->isEquivalentTo(new Length(100, 'cm')); // true
Built-ins cover a wide range but not everything. The package supports registering UnitOfMeasure objects per quantity class, using to-native/from-native callables; a custom quantity means extending the base PhysicalQuantity and defining its unit table. The exact method name and UnitOfMeasure constructor have shifted between major versions, so mirror the pattern in the installed src/PhysicalQuantity/Length.php. Put all custom registrations in one bootstrap location (e.g. a service provider's register()), never scattered through the codebase.
There is no dimensional-analysis engine: you cannot multiply Length by Length and get an Area. Compute derived values yourself in native units, then wrap them.
$speed = new Speed(
(new Length(100, 'm'))->toNativeUnit()
/ (new Time(9.58, 's'))->toNativeUnit(),
'm/s'
);
isEquivalentTo() or assertEqualsWithDelta() in tests.20°C + 10°C does not produce 30°C under the hood, because values are converted through Kelvin first. Model temperature differences separately; this library models point temperatures.add()/subtract() change the receiver. If you inject a quantity into multiple services, clone it or use immutable wrappers.add() accepts any PhysicalQuantity. Type hints do not enforce that the argument is the same quantity class. Adding a Mass to a Length fails as an unknown unit or nonsense number, not a clean type error. Guard at API boundaries.C is Celsius in Temperature but could be coulomb in a charge quantity; m is meter in Length but minute in Time. Always pass the unit through the correct quantity class.toNativeUnit() and confirm before writing DB migrations or comparing values across systems.toUnit() returns a raw float. Formatting, thousands separators, and rounding are your responsibility.json_encode($length) will not produce a useful payload. Use getValue()/__toString() or map to explicit arrays.UnitOfMeasure constructor, registration method names). Pin a version in composer.json and read the installed source rather than trusting blog snippets.float plus an enum is simpler. Reach for this library when units are genuinely dynamic — imports, multi-region catalogs, user-configurable display units, or scientific data.How can I help you explore Laravel packages today?