Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Recipes

Write the covenant upon fresh clay and place it in the public kiln beside the vessels of common use. If the words blister while the cups endure, summon the oath-givers again; for no promise is strengthened by the fire it was fashioned to escape. But if the tablet darken without division, carry it warm between the households, and let neither claim the colder edge.

Ordinances of the Synthetic Dawn 12:44

A clay covenant tablet enduring a public kiln among household vessels beneath cyan stormlight

These short examples show common application tasks. They assume the Composer autoloader and PHPStan extension are already configured as described in Getting Started. Follow the links after each recipe for the complete semantics and limitations.

Protect An Existing API

Brand incoming data where its unit becomes known, then convert explicitly before calling an API that expects another unit:

<?php

use function jbboehr\Yumemi\unit;
use function jbboehr\Yumemi\unit_to;

/** @param unit_float<'meter'> $height */
function setRideHeight(float $height): void {}

$measuredHeight = unit(6.0, 'foot');

// @akashi-phpstan-error argument.type: unit_float<'meter'>, 6.0&unit_float<'international_foot'> given
setRideHeight($measuredHeight);

setRideHeight(unit_to($measuredHeight, 'foot', 'meter'));

See Branded Native Types and Boundary Helpers.

Keep Unit Setup Outside Hot Loops

When an external invariant already guarantees the input unit, declare that contract once and let repeated work remain ordinary native arithmetic. Compute a conversion factor before the loop so the loop itself performs only float multiplication:

<?php

use function jbboehr\Yumemi\unit_factor;

/** @var list<unit_float<'international_foot'>> $surveyLengths */
$surveyLengths = [1.0, 5.0, 10.0];

$footToMeter = unit_factor('international_foot', 'meter');
$metricLengths = [];

foreach ($surveyLengths as $surveyLength) {
    $metricLengths[] = $surveyLength * $footToMeter;
}

/** @param list<unit_float<'meter'>> $lengths */
function saveMetricSurveyLengths(array $lengths): void {}

saveMetricSurveyLengths($metricLengths);
assert(abs($metricLengths[2] - 3.048) < 1e-12);

The @var declaration asserts that the source data is measured in feet; it does not validate its provenance. Use unit() instead when parsing the unit expression against the runtime catalog is valuable. See Native Values At Trusted Boundaries and Constant Unit Expressions.

Preserve Exact Conversion

Use Quantity when conversion must retain an exact decimal or fraction rather than immediately becoming a float:

<?php

use jbboehr\Yumemi\Units;

$length = Units::default()->quantity(1, 'foot')->to('meter');

assert($length->valueToString() === '381/1250');
assert($length->exactDecimalValueIn('meter') === '0.3048');
assert($length->unitToString() === 'meter');

See Conversion and Comparison and Native Numeric Output.

Convert Temperatures

Temperature scales with different zero points require a full value conversion. Use PointQuantity when the coordinate must remain attached to the value, and use a generated delta unit for temperature differences:

<?php

use jbboehr\Yumemi\Units;

use function jbboehr\Yumemi\unit_to;

$units = Units::default();
$freezing = $units->point(0, 'celsius');
$rise = $units->deltaQuantity(18, 'fahrenheit');

assert(abs(unit_to(98.6, 'fahrenheit', 'celsius') - 37.0) < 1e-12);
assert($freezing->valueIn('kelvin')->toString() === '5463/20');
assert($freezing->add($rise)->valueToString() === '10');
assert($units->point(100, 'celsius')->difference($freezing)->toString() === '100 * delta_celsius');

Do not use celsius itself in products or quotients. delta_celsius is multiplicative, and symbol formatting renders it as Δ°C. See Affine Conversion.

Define Application Units

Put project-specific definitions in one factory, then use that factory for both PHPStan and the runtime context. This prevents one layer from accepting a unit that the other cannot resolve:

<?php

namespace App\Units;

use jbboehr\Yumemi\PHPStan\UnitRegistryFactory;
use jbboehr\Yumemi\Registry\UnitRegistry;
use jbboehr\Yumemi\Registry\UnitRegistryBuilder;
use jbboehr\Yumemi\Units;

use function jbboehr\Yumemi\unit;
use function jbboehr\Yumemi\unit_to;

final class ApplicationUnitRegistryFactory implements UnitRegistryFactory
{
    public static function create(): UnitRegistry
    {
        return UnitRegistryBuilder::default()
            ->define('shipping_pallet = 48 * inch')
            ->alias('shipping_pallets', 'shipping_pallet')
            ->build();
    }
}

$units = new Units(ApplicationUnitRegistryFactory::create());
$width = $units->quantity(2, 'shipping_pallets');

assert($width->exactDecimalValueIn('meter') === '2.4384');

$previous = Units::setDefault($units);

try {
    $nativeWidth = unit(2, 'shipping_pallets');

    assert(abs(unit_to($nativeWidth, 'shipping_pallets', 'meter') - 2.4384) < 1e-12);
} finally {
    Units::setDefault($previous);
}

Select the same factory for PHPStan:

parameters:
    yumemi:
        registryFactory: App\Units\ApplicationUnitRegistryFactory

When an application unit is not derived from the seven SI axes, declare one canonical base with baseUnit() and derive the remaining units through exact definitions. The custom-registry reference shows this pattern for an application-owned currency-rate snapshot.

Instance methods use the registry attached to their Units context. Native helpers use the process-wide default instead; an application may install that context once during bootstrap, while tests and scoped workers should restore the previous context in finally. See Registry Configuration, Custom Registries, and Contexts And Construction for the complete lifecycle and overlay rules.

Format Units For Display

Formatting changes spelling and typography without converting or normalizing the underlying unit:

<?php

use jbboehr\Yumemi\Formatter\FormatOptions;
use jbboehr\Yumemi\Formatter\Typography;
use jbboehr\Yumemi\Formatter\UnitNameStyle;
use jbboehr\Yumemi\Units;

$options = FormatOptions::create()
    ->withUnitNameStyle(UnitNameStyle::Symbol)
    ->withTypography(Typography::Unicode);

assert(Units::default()->format('kilogram * meter / second^2', $options) === 'kg · m / s²');

See Formatting for division styles, dimensionless output, and reusable formatters.