jobmetric/laravel-unit
Laravel Unit is a package for working with measurement units in your Laravel app. It helps you define, manage, and convert units with a clean, extendable structure. Lightweight and easy to integrate, useful for storing and displaying quantities consistently.
Install and publish. The package is jobmetric/laravel-unit, MIT, last released 2024-08-13 (1.2.0). Standard Laravel flow:
composer require jobmetric/laravel-unit
php artisan vendor:publish --tag=unit # or --provider="JobMetric\Unit\UnitServiceProvider"
php artisan migrate
If your Laravel version's package discovery picks it up, you are done; older jobmetric releases expected the provider registered manually in config/app.php under providers. Confirm which applies by checking composer.json → extra.laravel.providers in vendor/jobmetric/laravel-unit.
Where to look first. The published config/unit.php is the whole mental model: it should carry the unit types (length, mass, volume, time…), their base unit, and each unit's factor or conversion definition. Read it before writing a line of code — the package's behaviour is config-driven, so the config is the documentation. Second: the migration (table shape, unique indexes, whether units are keyed by id or a stable code). Third: the service provider's bindings, which tell you what is container-resolved and therefore overridable.
First use case. Pick a single entity that carries a measurement — a product with weight, a shipment with volume, a recipe with quantity — store the canonical value plus its unit, and render it converted on display. That exercises the three things you will use daily: unit lookup, conversion, and formatting.
// persist canonically
$product->weight = 1250; // grams
$product->weight_unit = 'g';
// present in whatever the user reads
$display = unit_convert($product->weight, 'g', 'lb'); // or Unit::convert(...)
Verify the actual entry point in src/ — jobmetric packages variously expose a helper (unit_convert(), unit()), a facade (Unit::convert()), or a service class bound in the container. Whichever it is, wrap it once in your own Measure value object so the rest of your app does not reach into the vendor API directly.
Store canonical, convert at the edges. Keep one base unit per dimension in the database and convert only in form requests (input) and API resources / Blade (output). This kills the class of bug where three screens each store a differently-normalised number, and it means adding a new display unit is a config change, not a data migration.
Model the dimension, not just the unit. A unit only means something relative to its type. If the package exposes UnitType, use it; otherwise enforce it in your own layer — a length value must never silently convert into a mass unit. Validate unit.type == stored.type in the FormRequest and again in a domain guard, because the package will happily convert numerically if it only receives numeric input.
Define factors once, against the base. Pairwise conversion tables are N² and rot. If the config lets you declare each unit's factor to the type's base unit, do that and never add pairwise rules. If it does not, express pairwise helpers in terms of the base yourself.
Form handling. Accept { value, unit } pairs as input, validate the unit against an allow-list (Rule::in(array_keys(config('unit.units'))) or an exists against the published units table), then normalise in prepareForValidation() so validation rules see the canonical number.
API resources. Return both the canonical value and the display form, and be explicit about units — an unlabelled "weight": 1250 is a future incident.
return [
'weight' => ['value' => $this->weight, 'unit' => $this->weight_unit, 'display' => unit_format($this->weight, $this->weight_unit)],
];
Formatting is a view concern. Put format() / abbreviation in an accessor, a Blade component, or a small presenter — never bake the string into the model and never persist the formatted string.
Seed or sync units from config. Treat config/unit.php as the source of truth and upsert into the units table on deploy, keyed on a stable code. Idempotent upsert(), not truncate-and-insert, so admin-added rows and foreign keys survive.
Translations. jobmetric packages lean on jobmetric/laravel-translation; unit labels and abbreviations are likely translatable. If so, resolve labels via __() and keep the symbol ('kg', '°C') as a non-translated column. Do not send translated unit names down an API consumed by another system.
Testing. Write a small Pest/PHPUnit helper that asserts round-trips (convert(convert($v, 'g', 'lb'), 'lb', 'g') ≈ $v) for every pair you actually support. Property-based-ish round-trip tests catch factor typos faster than eyeballing the config.
jobmetric/laravel-translation and the jobmetric core package. Treat the composer.lock diff as the real changelog: review changes to laravel/framework, php, and the core package before merging, and check whether the minimum Laravel/PHP version moved. composer why-not jobmetric/laravel-unit 1.2.0 will tell you what is blocking.php artisan config:clear && php artisan cache:clear, and restart queue workers — long-running processes hold the old container bindings and the old unit config.env() in config/unit.php. After config:cache it returns null. Put selection in config, read it via config().round($v, $precision) from config rather than ad-hoc rounding scattered through views. For money-adjacent or regulatory values, use bcmath or brick/math in your own wrapper.id, store the unit code ('kg') on your domain rows and resolve the id at write time. Renaming or re-seeding then cannot silently re-label historical rows.'m' where 'kg' was expected and the package only receives numbers, you get a plausible-looking wrong answer. Guard in your own layer.code / (type, code). Without it, a re-run seeder or a race in an admin UI gives you duplicate units and non-deterministic lookups. Add the index in a follow-up migration if the published one lacks it.°, µ, Ω. Use utf8mb4 on the units table and make sure your API serialises UTF-8, or those get mangled at the boundary.units()->keyBy('code'), resolved once via a singleton or once()) is usually enough; do not hit the table per line item.is_active flag over hard deletes, or stored values become un-resolvable when rendering old records.Unit model or facade are a cheap way to add app-specific helpers (Unit::forSelect()) without touching vendor code.src/, the config, and the migration before assuming behaviour; keep a thin adapter in your codebase so a future replacement is a one-file change.unit.length.meter, that is a translation miss, not a conversion bug — check the driver/config for the translation package before debugging arithmetic.1.2.0 can still change effective behaviour via the upstreams. Run your round-trip conversion tests and a locale smoke test after every update; pin exact versions in production lockfiles and bump deliberately.How can I help you explore Laravel packages today?