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
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.