midnightluke/php-units-of-measure-bundle
Symfony bundle integrating the php-units-of-measure library, providing unit definitions and services to work with quantities, conversions, and formatting in your app. Adds simple configuration and DI wiring for unit-of-measure support.
Installation
composer require midnightluke/php-units-of-measure-bundle
Add to config/bundles.php:
MidnightLuke\UnitsOfMeasureBundle\UnitsOfMeasureBundle::class => ['all' => true],
First Use Case: Database Storage Define a Doctrine entity with a unit-aware field:
use MidnightLuke\UnitsOfMeasureBundle\Doctrine\Types\UnitType;
use MidnightLuke\UnitsOfMeasureBundle\Doctrine\Types\QuantityType;
#[ORM\Entity]
class Product {
#[ORM\Column(type: QuantityType::NAME, options: ['unit' => 'kg'])]
private Quantity $weight;
}
Run migrations (php bin/console doctrine:migrations:diff).
Form Integration
Use the UnitType and QuantityType in Symfony forms:
$builder->add('weight', QuantityType::class, [
'unit' => 'kg',
'scale' => 2,
]);
Define Units in Entities
#[ORM\Column(type: QuantityType::NAME, options: ['unit' => 'm'])]
private Quantity $length;
kg, m, s) or custom units via Unit::create().Form Handling
UnitType for dropdowns of allowed units:
$builder->add('unit', UnitType::class, [
'units' => ['kg', 'g', 'mg'], // Restrict to specific units
]);
Business Logic
$quantity = new Quantity(10, 'kg');
$grams = $quantity->convertTo('g'); // Returns 10000
$weight1 = new Quantity(5, 'kg');
$weight2 = new Quantity(5000, 'g');
$weight1->equals($weight2); // true
API/Serialization
JsonSerializable or custom serializers:
$quantity->jsonSerialize(); // ["value" => 10, "unit" => "kg"]
Doctrine Lifecycle Callbacks
Validate units on prePersist/preUpdate:
#[ORM\PrePersist]
#[ORM\PreUpdate]
public function validateUnits() {
if (!$this->weight->getUnit()->isValid()) {
throw new \InvalidArgumentException('Invalid unit');
}
}
Custom Units
Extend the Unit class for domain-specific units (e.g., Unit::create('story_points')).
Symfony Validator Constraints Add custom validation:
use MidnightLuke\UnitsOfMeasureBundle\Validator\Constraints\Unit;
#[Assert\Unit(['units' => ['kg', 'g']])]
private Quantity $weight;
Unit Mismatches
5 kg vs 5 m) throws UnitMismatchException.$kgValue = $quantity->convertTo('kg')->getValue();
Doctrine Type Configuration
unit in QuantityType defaults to Unit::NONE, which may not behave as expected.#[ORM\Column(type: QuantityType::NAME, options: ['unit' => 'm'])]
Form Data Binding
{"value": "10", "unit": "kg"} but the entity expects m, binding fails.UnitType with units option to restrict allowed units.Precision Loss
10 kg + 0.1 kg = 10.099999999999998 kg).scale option in QuantityType:
$builder->add('weight', QuantityType::class, ['scale' => 3]);
Dumping Quantities
Use var_dump($quantity->getValue()) and var_dump($quantity->getUnit()) for debugging.
var_dump($quantity->__toString()); // "10 kg"
Doctrine DBAL Exceptions If migrations fail, check:
unit column exists in the database (Doctrine generates it automatically).value column is of type decimal (recommended for precision).Custom Unit Providers Override the default unit provider:
// config/packages/midnight_luke_units_of_measure.yaml
midnight_luke_units_of_measure:
unit_provider: App\Service\CustomUnitProvider
Quantity Listeners Add logic before/after quantity operations:
use MidnightLuke\UnitsOfMeasureBundle\Event\QuantityEvent;
$dispatcher->addListener(QuantityEvent::PRE_CONVERT, function (QuantityEvent $event) {
if ($event->getQuantity()->getUnit() === 'm') {
$event->setTargetUnit('cm'); // Force conversion to cm
}
});
Custom Serialization
Extend Quantity for JSON/API responses:
class ApiQuantity extends Quantity implements JsonSerializable {
public function jsonSerialize(): array {
return [
'value' => $this->getValue(),
'unit' => $this->getUnit()->getSymbol(),
'full_unit' => $this->getUnit()->__toString(),
];
}
}
Default Unit The bundle does not enforce a default unit globally. Always specify units in:
options: ['unit' => '...']).unit option).Quantity instantiation (new Quantity(10, 'kg')).Case Sensitivity
Units are case-sensitive (e.g., kg ≠ KG). Use constants from Unit class for consistency:
$quantity = new Quantity(10, Unit::KILOGRAM);
How can I help you explore Laravel packages today?