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.