Skip to main content

Adding new quantities

A new quantity type is a class that extends PhysicalQuantity and implements initialise() to register its units.

Basic quantity

<?php

declare(strict_types=1);

namespace Renfordt\UnitLib;

class Length extends PhysicalQuantity
{
use HasSIUnits;

protected function initialise(): void
{
// The first unit registered becomes the native unit (factor 1.0).
$meter = new UnitOfMeasurement('m', 1);
$meter->addAlias('meter');
$meter->addAlias('meters');
$this->addUnit($meter);

// Other units give their conversion factor to the native unit.
$foot = new UnitOfMeasurement('ft', 0.3048); // 1 ft = 0.3048 m
$foot->addAlias('foot');
$foot->addAlias('feet');
$this->addUnit($foot);
}
}

Key rules:

  • The first unit added in initialise() is the native unit and must have a conversion factor of 1.0.
  • A unit's conversion factor expresses how many native units it equals.
  • Add aliases for common spellings and abbreviations with addAlias().

SI prefix support

To accept metric prefixes, use the HasSIUnits trait and override getSIBaseUnit() to return the base unit symbol. The default base unit is m, so override it whenever the native symbol differs:

class Mass extends PhysicalQuantity
{
use HasSIUnits;

protected function getSIBaseUnit(): string
{
return 'g';
}

protected function initialise(): void
{
$gram = new UnitOfMeasurement('g', 1);
$this->addUnit($gram);
// ...
}
}

With the trait in place, prefixed units such as kg, mg, and μg resolve automatically without being registered. See SI units.

Offset-based conversions

If the quantity's scales involve an offset (not just a ratio), override convertToNativeUnit() and toUnit(). The constructor calls convertToNativeUnit() when it is defined, and toUnit() converts back from the native unit. Temperature is the reference implementation:

protected function convertToNativeUnit(float $value, string $unitName): float
{
return match ($unitName) {
'K', 'kelvin', 'Kelvin' => $value,
'°C', 'C', 'celsius', 'Celsius' => $value + 273.15,
// ...
};
}

See Temperature for the full pattern.

Derived quantities and factory methods

If the new quantity is the product or quotient of existing ones, add it to the relevant match arms in PhysicalQuantity::multiply() or divide() so those operations return it. A static factory method is a convenient wrapper around the operation:

public static function fromLengthAndTime(Length $length, Time $time): self
{
/** @var self */
return $length->divide($time);
}

Tests

Add a test class with the #[CoversClass(ClassName::class)] attribute. The PHPUnit configuration is strict and fails on warnings, risky tests, and deprecations, so every test class must declare the class it covers.

To make the type available to PhysicalQuantity::parse() auto-detection, add it to the $quantityClasses list in PhysicalQuantity::parse().

Checklist

  1. Extend PhysicalQuantity.
  2. Implement initialise(); register the native unit first with factor 1.0.
  3. Use HasSIUnits and override getSIBaseUnit() if metric prefixes apply.
  4. Override convertToNativeUnit() / toUnit() for offset-based scales.
  5. Add aliases for common unit names.
  6. Wire up derived-unit operations and factory methods if applicable.
  7. Add a test class with #[CoversClass].
  8. Register the class in parse() for auto-detection.