{"id":24375338,"url":"https://github.com/renfordt/unitlib","last_synced_at":"2026-04-29T05:35:12.826Z","repository":{"id":270951450,"uuid":"911958893","full_name":"renfordt/UnitLib","owner":"renfordt","description":"A PHP package for Units of Measurements","archived":false,"fork":false,"pushed_at":"2025-12-14T13:41:15.000Z","size":161,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-12-27T14:11:18.380Z","etag":null,"topics":["imperial","metric","physics","si-units","unit-conversion","units","units-of-measure","units-of-measurement"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/renfordt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-01-04T10:03:22.000Z","updated_at":"2025-12-14T13:39:57.000Z","dependencies_parsed_at":null,"dependency_job_id":"70503d2c-e7e8-46c5-b982-4fbbb8acac3e","html_url":"https://github.com/renfordt/UnitLib","commit_stats":null,"previous_names":["renfordt/unitlib"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/renfordt/UnitLib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renfordt%2FUnitLib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renfordt%2FUnitLib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renfordt%2FUnitLib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renfordt%2FUnitLib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/renfordt","download_url":"https://codeload.github.com/renfordt/UnitLib/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/renfordt%2FUnitLib/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32412890,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T05:20:56.964Z","status":"ssl_error","status_checked_at":"2026-04-29T05:19:54.749Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["imperial","metric","physics","si-units","unit-conversion","units","units-of-measure","units-of-measurement"],"created_at":"2025-01-19T05:56:30.363Z","updated_at":"2026-04-29T05:35:12.815Z","avatar_url":"https://github.com/renfordt.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# UnitLib\n\n[![Badge](http://img.shields.io/badge/source-renfordt/UnitLib-blue.svg)](https://github.com/renfordt/UnitLib)\n[![Packagist Version](https://img.shields.io/packagist/v/renfordt/unit-lib)](https://packagist.org/packages/renfordt/unit-lib/)\n![Packagist PHP Version](https://img.shields.io/packagist/dependency-v/renfordt/unit-lib/php)\n![GitHub License](https://img.shields.io/github/license/renfordt/UnitLib)\n![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/renfordt/colors/tests.yml?logo=github)\n[![Code Coverage](https://qlty.sh/gh/renfordt/projects/UnitLib/coverage.svg)](https://qlty.sh/gh/renfordt/projects/UnitLib)\n[![Maintainability](https://qlty.sh/gh/renfordt/projects/UnitLib/maintainability.svg)](https://qlty.sh/gh/renfordt/projects/UnitLib)\n\n\n\u003e A modern, type-safe PHP library for handling physical quantities and unit conversions\n\n---\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Features](#features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Supported Quantities](#supported-quantities)\n- [Usage Guide](#usage-guide)\n  - [Creating Quantities](#creating-quantities)\n  - [Converting Units](#converting-units)\n  - [Arithmetic Operations](#arithmetic-operations)\n  - [Derived Units through Operations](#derived-units-through-operations)\n  - [Working with SI Prefixes](#working-with-si-prefixes)\n  - [Temperature Conversions](#temperature-conversions)\n- [Advanced Features](#advanced-features)\n- [API Reference](#api-reference)\n- [Development](#development)\n- [Testing](#testing)\n- [Contributing](#contributing)\n- [License](#license)\n\n---\n\n## Overview\n\nUnitLib is a comprehensive PHP library designed for developers who need to work with physical quantities and perform accurate unit conversions. Built with modern PHP 8.4+ features, it provides a clean, type-safe API for handling measurements across various domains including length, mass, time, energy, and more.\n\n### Why UnitLib?\n\n- **Type Safety**: Leverages PHP 8.4's property hooks for immutable, type-safe quantity objects\n- **Zero Ambiguity**: No confusion about what unit you're working with\n- **Extensive Unit Support**: From metric to imperial, nautical to scientific units\n- **SI Prefix Support**: Automatic handling of kilo-, milli-, micro-, nano-, and more\n- **Chainable API**: Fluent interface for readable, maintainable code\n- **Well Tested**: Comprehensive test suite with strict coverage requirements\n\n---\n\n## Features\n\n✨ **Comprehensive Unit Coverage**\n- **Basic Quantities**: Length, Mass, Time, Temperature\n- **Geometric**: Area, Volume\n- **Mechanical**: Force, Pressure, Velocity, Acceleration, Torque, Density\n- **Energy \u0026 Power**: Energy, Power, Frequency\n- **Electrical**: Current, Voltage, Resistance, Charge, Capacitance, Inductance, MagneticFlux\n- **Photometry**: Luminous Intensity\n- **Chemistry**: Amount of Substance\n- **Angular**: Angle\n\n📐 **Flexible Conversions**\n- Metric (SI) units with automatic prefix handling\n- Imperial and US customary units\n- Specialized units (nautical miles, knots, etc.)\n- CGS units (maxwell, dyne, etc.)\n\n🔒 **Type-Safe Design**\n- Immutable quantity objects\n- Strict type declarations throughout\n- PHPStan level 9 compliant\n\n🧮 **Advanced Mathematical Operations**\n- Addition and subtraction of quantities\n- Multiplication and division for derived units\n- Power operations (e.g., Length² = Area)\n- Automatic unit normalization\n- Factory methods for convenient creation\n- Ambiguity resolution (e.g., Force × Length can be Energy or Torque)\n\n🔍 **Comparison Operations**\n- Compare quantities: `greaterThan()`, `lessThan()`, `equals()`\n- Epsilon-based floating-point equality\n- Type-safe comparisons with automatic unit conversion\n- Sorting support via `compareTo()`\n\n📝 **String Parsing**\n- Parse strings into quantities: `PhysicalQuantity::parse(\"5.5 meters\")`\n- Auto-detection or explicit type specification\n- Support for decimals, negatives, SI prefixes\n\n💾 **Serialization**\n- JSON serialization via `JsonSerializable` interface\n- Round-trip serialization with `fromJson()`\n- Preserves both original and native values\n\n🌡️ **Temperature Support**\n- Handles offset-based conversions (Celsius, Fahrenheit)\n- Kelvin, Rankine support\n- Accurate inter-scale conversions\n\n---\n\n## Requirements\n\n- **PHP**: 8.4 or higher\n- **Composer**: For dependency management\n\n---\n\n## Installation\n\nInstall via Composer:\n\n```bash\ncomposer require renfordt/unit-lib\n```\n\n---\n\n## Quick Start\n\n```php\n\u003c?php\n\nuse Renfordt\\UnitLib\\Length;\nuse Renfordt\\UnitLib\\Mass;\nuse Renfordt\\UnitLib\\Temperature;\nuse Renfordt\\UnitLib\\Velocity;\nuse Renfordt\\UnitLib\\Acceleration;\n\n// Create a length measurement\n$distance = new Length(100, 'km');\n\n// Convert to different units\necho $distance-\u003etoUnit('mi');  // 62.137119 (miles)\necho $distance-\u003etoUnit('m');   // 100000 (meters)\n\n// Use SI prefixes automatically\n$height = new Length(1.75, 'm');\necho $height-\u003etoUnit('cm');    // 175 (centimeters)\n\n// Perform arithmetic operations\n$total = (new Length(5, 'km'))-\u003eadd(new Length(500, 'm'));\necho $total-\u003etoUnit('km');     // 5.5\n\n// Derived units through operations\n$area = (new Length(5, 'm'))-\u003emultiply(new Length(3, 'm'));\necho $area-\u003etoUnit('m²');      // 15 (Area)\n\n$velocity = (new Length(100, 'm'))-\u003edivide(new Time(10, 's'));\necho $velocity-\u003etoUnit('m/s'); // 10 (Velocity)\n\n// Use factory methods for convenience\n$speed = Velocity::fromLengthAndTime(new Length(100, 'm'), new Time(10, 's'));\necho $speed-\u003etoUnit('km/h');   // 36\n\n// Work with different quantity types\n$weight = new Mass(75, 'kg');\necho $weight-\u003etoUnit('lb');    // 165.34675 (pounds)\n\n// Handle temperature conversions\n$temp = new Temperature(0, '°C');\necho $temp-\u003etoUnit('°F');      // 32\necho $temp-\u003etoUnit('K');       // 273.15\n```\n\n---\n\n## Supported Quantities\n\n### 📏 Length\n- **Metric**: meter (m), kilometer (km), centimeter (cm), millimeter (mm), micrometer (μm), nanometer (nm)\n- **Imperial**: inch (in), foot (ft), yard (yd), mile (mi)\n- **Nautical**: nautical mile (nmi)\n\n### ⚖️ Mass\n- **Metric**: gram (g), kilogram (kg), milligram (mg), tonne (t)\n- **Imperial**: ounce (oz), pound (lb), stone (st), ton (US/UK)\n\n### ⏱️ Time\n- **SI**: second (s), millisecond (ms), microsecond (μs), nanosecond (ns)\n- **Common**: minute (min), hour (h/hr), day, week, year\n\n### 🌡️ Temperature\n- **Scales**: Kelvin (K), Celsius (°C), Fahrenheit (°F), Rankine (°R)\n\n### 📐 Area\n- **Metric**: square meter (m²), square kilometer (km²), hectare (ha), are (a)\n- **Imperial**: square foot (ft²), square yard (yd²), square mile (mi²), acre\n\n### 🧊 Volume\n- **Metric**: cubic meter (m³), liter (L), milliliter (mL)\n- **Imperial**: cubic foot (ft³), gallon (gal), quart (qt), pint (pt)\n\n### 💪 Force\n- **SI**: Newton (N), kilonewton (kN)\n- **Other**: pound-force (lbf), dyne\n\n### ⚡ Energy\n- **SI**: Joule (J), kilojoule (kJ), megajoule (MJ)\n- **Other**: calorie (cal), kilocalorie (kcal), Watt-hour (Wh), kilowatt-hour (kWh)\n\n### 🔌 Power\n- **SI**: Watt (W), kilowatt (kW), megawatt (MW)\n- **Other**: horsepower (hp)\n\n### 🌊 Pressure\n- **SI**: Pascal (Pa), kilopascal (kPa), megapascal (MPa)\n- **Other**: bar, atmosphere (atm), psi, torr, mmHg\n\n### 🏃 Velocity\n- **Metric**: meter per second (m/s), kilometer per hour (km/h)\n- **Imperial**: miles per hour (mph), feet per second (ft/s)\n- **Nautical**: knots (kt)\n\n### 🚀 Acceleration\n- **SI**: meter per second squared (m/s²)\n- **Standard**: standard gravity (g)\n- **Imperial**: feet per second squared (ft/s²)\n- **Scientific**: galileo (Gal)\n\n### ⚡ Electric Current\n- **SI**: ampere (A) with automatic SI prefix support (mA, μA, kA, etc.)\n\n### 💡 Luminous Intensity\n- **SI**: candela (cd) with automatic SI prefix support (mcd, kcd, etc.)\n\n### 🔌 Voltage\n- **SI**: volt (V) with automatic SI prefix support (mV, μV, kV, MV, etc.)\n\n### 🔋 Charge\n- **SI**: coulomb (C) with automatic SI prefix support (mC, μC, nC, kC, etc.)\n- **Battery**: ampere-hour (Ah), milliampere-hour (mAh)\n- **Scientific**: elementary charge (e)\n\n### 📻 Frequency\n- **SI**: hertz (Hz) with automatic SI prefix support (kHz, MHz, GHz, THz, etc.)\n- **Mechanical**: revolutions per minute (RPM), beats per minute (BPM)\n- **Angular**: radians per second (rad/s)\n\n### 🔩 Torque\n- **SI**: newton-meter (N·m) with automatic SI prefix support\n- **Imperial**: pound-force foot (lbf·ft), pound-force inch (lbf·in)\n- **CGS**: dyne-centimeter (dyn·cm)\n\n### 🧪 Density\n- **SI**: kilogram per cubic meter (kg/m³)\n- **Metric**: gram per cubic centimeter (g/cm³), gram per liter (g/L)\n- **Imperial**: pound per cubic foot (lb/ft³), pound per gallon (lb/gal)\n\n### 🔌 Capacitance\n- **SI**: farad (F) with automatic SI prefix support (μF, nF, pF, etc.)\n\n### 🧲 Inductance\n- **SI**: henry (H) with automatic SI prefix support (mH, μH, nH, etc.)\n\n### 🧲 Magnetic Flux\n- **SI**: weber (Wb) with automatic SI prefix support (mWb, μWb, etc.)\n- **CGS**: maxwell (Mx)\n\n### 🔬 Amount of Substance\n- **SI**: mole (mol) with automatic SI prefix support (mmol, μmol, kmol, etc.)\n\n### 📐 Angle\n- **SI**: radian (rad)\n- **Common**: degree (°), arcminute ('), arcsecond (\")\n- **Navigation**: gradian (gon)\n\n### ⚙️ Resistance\n- **SI**: ohm (Ω) with automatic SI prefix support (mΩ, kΩ, MΩ, etc.)\n\n---\n\n## Usage Guide\n\n### Creating Quantities\n\nAll physical quantities are created using their respective class constructors:\n\n```php\nuse Renfordt\\UnitLib\\Length;\n\n// Using string unit identifiers\n$distance1 = new Length(100, 'm');\n$distance2 = new Length(5.5, 'km');\n$distance3 = new Length(12, 'ft');\n\n// Using unit aliases\n$distance4 = new Length(1, 'meter');\n$distance5 = new Length(1, 'metre');  // British spelling\n$distance6 = new Length(10, 'inches');\n```\n\n### Converting Units\n\nConvert to any supported unit using the `toUnit()` method:\n\n```php\n$length = new Length(1500, 'm');\n\necho $length-\u003etoUnit('km');   // 1.5\necho $length-\u003etoUnit('mi');   // 0.932057\necho $length-\u003etoUnit('ft');   // 4921.26\n```\n\n### Arithmetic Operations\n\nQuantities can be added or subtracted, even if they're in different units:\n\n```php\n$distance1 = new Length(1, 'km');\n$distance2 = new Length(500, 'm');\n\n// Addition\n$total = $distance1-\u003eadd($distance2);\necho $total-\u003etoUnit('km');  // 1.5\necho $total-\u003etoUnit('m');   // 1500\n\n// Subtraction\n$difference = $distance1-\u003esubtract($distance2);\necho $difference-\u003etoUnit('m');  // 500\n```\n\n**Important**: The result is always in the native unit (the first unit defined in the class). Use `toUnit()` to convert to your desired unit.\n\n### Derived Units through Operations\n\nThe library automatically creates derived quantity types through multiplication, division, and power operations:\n\n```php\nuse Renfordt\\UnitLib\\{Length, Time, Mass, Force, Area, Volume, Velocity, Acceleration, Energy, Power, Pressure};\n\n// Length × Length = Area\n$length1 = new Length(5, 'm');\n$length2 = new Length(3, 'm');\n$area = $length1-\u003emultiply($length2);\necho $area-\u003etoUnit('m²');  // 15\n\n// Area × Length = Volume\n$volume = $area-\u003emultiply(new Length(2, 'm'));\necho $volume-\u003etoUnit('m³');  // 30\n\n// Length² = Area (using power)\n$area = (new Length(5, 'm'))-\u003epower(2);\necho $area-\u003etoUnit('m²');  // 25\n\n// Length³ = Volume (using power)\n$volume = (new Length(3, 'm'))-\u003epower(3);\necho $volume-\u003etoUnit('m³');  // 27\n\n// Length / Time = Velocity\n$distance = new Length(100, 'm');\n$time = new Time(10, 's');\n$velocity = $distance-\u003edivide($time);\necho $velocity-\u003etoUnit('m/s');  // 10\necho $velocity-\u003etoUnit('km/h');  // 36\n\n// Velocity / Time = Acceleration\n$acceleration = $velocity-\u003edivide($time);\necho $acceleration-\u003etoUnit('m/s²');  // 1\n\n// Mass × Acceleration = Force\n$mass = new Mass(10, 'kg');\n$accel = new Acceleration(9.8, 'm/s²');\n$force = $mass-\u003emultiply($accel);\necho $force-\u003etoUnit('N');  // 98\n\n// Force × Length = Energy (default)\n$work = $force-\u003emultiply(new Length(5, 'm'));\necho $work-\u003etoUnit('J');  // 490\n\n// Force × Length = Torque (when specified)\n$torque = $force-\u003emultiply(new Length(0.5, 'm'), Torque::class);\necho $torque-\u003etoUnit('N⋅m');  // 49\n\n// Energy / Time = Power\n$power = $work-\u003edivide($time);\necho $power-\u003etoUnit('W');  // 49\n\n// Force / Area = Pressure\n$pressure = $force-\u003edivide($area);\necho $pressure-\u003etoUnit('Pa');  // Pressure in Pascals\n\n// Reverse operations: Volume / Length = Area\n$derivedArea = $volume-\u003edivide(new Length(2, 'm'));\necho $derivedArea-\u003etoUnit('m²');  // Returns area\n```\n\n#### Factory Methods for Convenience\n\nSome derived quantity classes provide static factory methods for easier creation:\n\n```php\n// Velocity from Length and Time\n$velocity = Velocity::fromLengthAndTime(\n    new Length(100, 'm'),\n    new Time(10, 's')\n);\necho $velocity-\u003etoUnit('km/h');  // 36\n\n// Acceleration from Velocity and Time\n$acceleration = Acceleration::fromVelocityAndTime(\n    new Velocity(50, 'm/s'),\n    new Time(5, 's')\n);\necho $acceleration-\u003etoUnit('m/s²');  // 10\n\n// Acceleration from Length and Time squared\n$acceleration = Acceleration::fromLengthAndTime(\n    new Length(100, 'm'),\n    new Time(10, 's')\n);\necho $acceleration-\u003etoUnit('m/s²');  // 1\n```\n\n### Working with SI Prefixes\n\nFor quantities that support SI units (Length, Mass, Time, etc.), you can use metric prefixes automatically:\n\n```php\n$length = new Length(1, 'm');\n\n// Convert to any SI prefix\necho $length-\u003etoUnit('km');   // 0.001 (kilometer)\necho $length-\u003etoUnit('cm');   // 100 (centimeter)\necho $length-\u003etoUnit('mm');   // 1000 (millimeter)\necho $length-\u003etoUnit('μm');   // 1000000 (micrometer)\necho $length-\u003etoUnit('nm');   // 1000000000 (nanometer)\n\n// Works in reverse too\n$tiny = new Length(500, 'μm');\necho $tiny-\u003etoUnit('mm');     // 0.5\necho $tiny-\u003etoUnit('m');      // 0.0005\n```\n\n**Supported SI Prefixes**:\n- Quetta (Q): 10³⁰\n- Ronna (R): 10²⁷\n- Yotta (Y): 10²⁴\n- Zetta (Z): 10²¹\n- Exa (E): 10¹⁸\n- Peta (P): 10¹⁵\n- Tera (T): 10¹²\n- Giga (G): 10⁹\n- Mega (M): 10⁶\n- Kilo (k): 10³\n- Hecto (h): 10²\n- Deca (da): 10¹\n- Deci (d): 10⁻¹\n- Centi (c): 10⁻²\n- Milli (m): 10⁻³\n- Micro (μ/u): 10⁻⁶\n- Nano (n): 10⁻⁹\n- Pico (p): 10⁻¹²\n- Femto (f): 10⁻¹⁵\n- Atto (a): 10⁻¹⁸\n- Zepto (z): 10⁻²¹\n- Yocto (y): 10⁻²⁴\n- Ronto (r): 10⁻²⁷\n- Quecto (q): 10⁻³⁰\n\n### String Parsing\n\nParse string representations into physical quantities:\n\n```php\nuse Renfordt\\UnitLib\\PhysicalQuantity;\nuse Renfordt\\UnitLib\\Length;\n\n// Auto-detection (tries all quantity types)\n$length = PhysicalQuantity::parse('5.5 meters');\n$mass = PhysicalQuantity::parse('100 kg');\n$temp = PhysicalQuantity::parse('-40 °C');\n\n// With explicit type (faster and type-safe)\n$distance = PhysicalQuantity::parse('100 km', Length::class);\n\n// Supports various formats\nPhysicalQuantity::parse('5.5 m');      // With space\nPhysicalQuantity::parse('100km');      // Without space\nPhysicalQuantity::parse('-10 meters'); // Negative values\nPhysicalQuantity::parse('+5 kg');      // Positive sign\n```\n\n### Comparing Quantities\n\nCompare quantities safely with automatic unit conversion:\n\n```php\nuse Renfordt\\UnitLib\\Length;\n\n$length1 = new Length(1, 'km');\n$length2 = new Length(500, 'm');\n$length3 = new Length(1000, 'm');\n\n// Comparison methods\n$length1-\u003egreaterThan($length2);           // true\n$length1-\u003elessThan($length2);              // false\n$length1-\u003egreaterThanOrEqualTo($length3);  // true\n$length1-\u003elessThanOrEqualTo($length3);     // true\n\n// Equality with epsilon for floating-point safety\n$length1-\u003eequals($length3);                // true (default epsilon: 1e-10)\n$length1-\u003eequals($length3, 0.001);         // true (custom epsilon)\n\n// Sorting with compareTo\n$lengths = [\n    new Length(5, 'm'),\n    new Length(200, 'cm'),\n    new Length(3000, 'mm'),\n];\nusort($lengths, fn($a, $b) =\u003e $a-\u003ecompareTo($b));\n// Result: [2m, 3m, 5m]\n```\n\n### JSON Serialization\n\nSerialize and deserialize quantities for storage or APIs:\n\n```php\nuse Renfordt\\UnitLib\\Length;\nuse Renfordt\\UnitLib\\PhysicalQuantity;\n\n$length = new Length(100, 'cm');\n\n// Serialize to JSON\n$json = json_encode($length);\n// {\"value\":100,\"unit\":\"cm\",\"nativeValue\":1,\"nativeUnit\":\"m\",\"class\":\"Renfordt\\\\UnitLib\\\\Length\"}\n\n// Deserialize from JSON\n$data = json_decode($json, true);\n$restored = PhysicalQuantity::fromJson($data);\n\n// Round-trip serialization preserves values\necho $restored-\u003eoriginalValue;  // 100\necho $restored-\u003etoUnit('cm');   // 100\necho $restored-\u003etoUnit('m');    // 1\n```\n\n### Temperature Conversions\n\nTemperature requires special handling due to offset-based scales:\n\n```php\nuse Renfordt\\UnitLib\\Temperature;\n\n// Freezing point of water\n$freezing = new Temperature(0, '°C');\necho $freezing-\u003etoUnit('°F');  // 32\necho $freezing-\u003etoUnit('K');   // 273.15\n\n// Boiling point of water\n$boiling = new Temperature(100, '°C');\necho $boiling-\u003etoUnit('°F');   // 212\necho $boiling-\u003etoUnit('K');    // 373.15\n\n// Absolute zero\n$absolute = new Temperature(0, 'K');\necho $absolute-\u003etoUnit('°C');  // -273.15\necho $absolute-\u003etoUnit('°F');  // -459.67\n\n// Using aliases\n$temp = new Temperature(72, 'F');        // Fahrenheit\necho $temp-\u003etoUnit('celsius');           // ~22.22\n```\n\n---\n\n## Advanced Features\n\n### Accessing Original Values\n\nEach quantity preserves both the original value/unit and the native (normalized) value:\n\n```php\n$length = new Length(100, 'cm');\n\necho $length-\u003eoriginalValue;        // 100\necho $length-\u003eoriginalUnit-\u003ename;   // \"cm\"\necho $length-\u003enativeValue;          // 1 (converted to meters)\necho $length-\u003enativeUnit-\u003ename;     // \"m\"\n```\n\n### String Representation\n\nQuantities implement `Stringable` for easy display:\n\n```php\n$weight = new Mass(75, 'kg');\necho $weight;  // \"75 kg\"\n\n$distance = new Length(5280, 'ft');\necho $distance;  // \"5280 ft\"\n```\n\n### Converting to Native Unit\n\nGet a new quantity instance in the native unit:\n\n```php\n$length = new Length(100, 'cm');\n$native = $length-\u003etoNativeUnit();\n\necho $native-\u003eoriginalValue;  // 1\necho $native-\u003eoriginalUnit-\u003ename;  // \"m\"\n```\n\n### Unit Aliases\n\nMost units support multiple aliases for flexibility:\n\n```php\n// Length aliases\nnew Length(1, 'm');\nnew Length(1, 'meter');\nnew Length(1, 'metre');\nnew Length(1, 'meters');\nnew Length(1, 'metres');\n\n// Mass aliases\nnew Mass(1, 'kg');\nnew Mass(1, 'kilogram');\nnew Mass(1, 'kilograms');\n\n// Time aliases\nnew Time(1, 's');\nnew Time(1, 'sec');\nnew Time(1, 'second');\nnew Time(1, 'seconds');\n```\n\n---\n\n## API Reference\n\n### PhysicalQuantity (Base Class)\n\nAll quantity classes extend this abstract base.\n\n#### Properties\n\n```php\npublic readonly float $originalValue;     // The value as provided in constructor\npublic readonly UnitOfMeasurement $originalUnit;  // The unit as provided\npublic readonly float $nativeValue;       // Value converted to native unit\npublic readonly UnitOfMeasurement $nativeUnit;    // The native (base) unit\n```\n\n#### Methods\n\n```php\n// Convert to a different unit\npublic function toUnit(string|UnitOfMeasurement $unit): float\n\n// Convert to native unit\npublic function toNativeUnit(): PhysicalQuantity\n\n// Add two quantities (returns new instance in native unit)\npublic function add(PhysicalQuantity $quantity): PhysicalQuantity\n\n// Subtract quantities (returns new instance in native unit)\npublic function subtract(PhysicalQuantity $quantity): PhysicalQuantity\n\n// Multiply two quantities (returns derived quantity type)\n// Examples: Length × Length = Area, Mass × Acceleration = Force\npublic function multiply(PhysicalQuantity $quantity): PhysicalQuantity\n\n// Divide two quantities (returns derived quantity type)\n// Examples: Length / Time = Velocity, Energy / Time = Power\npublic function divide(PhysicalQuantity $quantity): PhysicalQuantity\n\n// Raise quantity to an integer power (returns derived quantity type)\n// Examples: Length² = Area, Length³ = Volume\npublic function power(int $exponent): PhysicalQuantity\n\n// String representation: \"value unit\"\npublic function __toString(): string\n```\n\n### UnitOfMeasurement\n\nRepresents a unit of measurement with conversion factors.\n\n```php\n// Constructor\npublic function __construct(string $name, float $conversionFactor)\n\n// Add alternative names for the unit\npublic function addAlias(string $alias): void\n\n// Check if a string matches this unit or its aliases\npublic function isAlias(string $unitName): bool\n\n// Get the conversion factor to native unit\npublic function getConversionFactor(): float\n```\n\n---\n\n## Development\n\n### Project Structure\n\n```\nsrc/\n├── PhysicalQuantity.php      # Abstract base class\n├── UnitOfMeasurement.php     # Unit representation\n├── HasSIUnits.php            # Trait for SI prefix support\n├── Length.php                # Length quantities\n├── Mass.php                  # Mass quantities\n├── Time.php                  # Time quantities\n├── Temperature.php           # Temperature quantities\n├── Area.php                  # Area quantities\n├── Volume.php                # Volume quantities\n├── Force.php                 # Force quantities\n├── Energy.php                # Energy quantities\n├── Power.php                 # Power quantities\n├── Pressure.php              # Pressure quantities\n├── Velocity.php              # Velocity quantities (with factory methods)\n├── Acceleration.php          # Acceleration quantities (with factory methods)\n├── Current.php               # Electric current quantities\n├── Voltage.php               # Voltage quantities\n└── LuminousIntensity.php     # Luminous intensity quantities\n\ntests/\n├── *Test.php                 # Corresponding test files\n├── ImmutabilityTest.php      # Tests for immutability\n└── DerivedUnitsTest.php      # Tests for derived unit operations\n```\n\n### Code Quality Tools\n\nThe project uses several tools to maintain code quality:\n\n- **Laravel Pint**: PSR-12 code style with strict types\n- **PHPStan**: Static analysis at high strictness level\n- **Rector**: Automated refactoring and code quality improvements\n- **PHPUnit**: Comprehensive test coverage\n\n---\n\n## Testing\n\n### Running Tests\n\n```bash\n# Run all tests (refactoring checks, linting, type analysis, unit tests)\ncomposer test\n\n# Run only unit tests\ncomposer test:unit\n\n# Run static analysis\ncomposer test:types\n\n# Check code style\ncomposer test:lint\n\n# Check refactoring opportunities\ncomposer test:refacto\n```\n\n### Running Specific Tests\n\n```bash\n# Run a specific test method\nvendor/bin/phpunit --filter test_converts_meters_to_feet\n\n# Run a specific test file\nvendor/bin/phpunit tests/LengthTest.php\n```\n\n### Code Coverage\n\nWhen xdebug is enabled, PHPUnit generates HTML coverage reports:\n\n```bash\ncomposer test:unit\n# View coverage at: .phpunit.cache/code-coverage/index.html\n```\n\n### Auto-fixing Issues\n\n```bash\n# Fix code style automatically\ncomposer lint\n\n# Apply automated refactorings\ncomposer refacto\n```\n\n---\n\n## Contributing\n\nContributions are welcome! Here's how you can help:\n\n1. **Fork the repository**\n2. **Create a feature branch**: `git checkout -b feature/amazing-feature`\n3. **Make your changes** and ensure tests pass: `composer test`\n4. **Fix code style**: `composer lint`\n5. **Commit your changes**: `git commit -m 'Add amazing feature'`\n6. **Push to the branch**: `git push origin feature/amazing-feature`\n7. **Open a Pull Request**\n\n### Adding New Physical Quantities\n\nTo add a new quantity type:\n\n1. Create a new class extending `PhysicalQuantity`\n2. Implement the `initialise()` method to define units\n3. Use `HasSIUnits` trait if SI prefixes apply\n4. Override `getSIBaseUnit()` to specify the base unit symbol\n5. Set native unit conversion factor to 1.0\n6. Add aliases for common unit names\n7. Add factory methods if the quantity can be derived from others\n8. Create comprehensive tests with `#[CoversClass]` attribute\n\nExample:\n\n```php\n\u003c?php\n\nnamespace Renfordt\\UnitLib;\n\nclass Density extends PhysicalQuantity\n{\n    use HasSIUnits;\n\n    protected function getSIBaseUnit(): string\n    {\n        return 'kg/m³';  // Base unit for SI prefixes\n    }\n\n    /**\n     * Create Density from Mass and Volume.\n     */\n    public static function fromMassAndVolume(Mass $mass, Volume $volume): self\n    {\n        /** @var self */\n        return $mass-\u003edivide($volume);\n    }\n\n    protected function initialise(): void\n    {\n        // Native unit: kilograms per cubic meter\n        $kgPerM3 = new UnitOfMeasurement('kg/m³', 1);\n        $kgPerM3-\u003eaddAlias('kg/m3');\n        $kgPerM3-\u003eaddAlias('kilogram per cubic meter');\n        $this-\u003eaddUnit($kgPerM3);\n\n        // Grams per cubic centimeter\n        $gPerCm3 = new UnitOfMeasurement('g/cm³', 1000);\n        $gPerCm3-\u003eaddAlias('g/cm3');\n        $this-\u003eaddUnit($gPerCm3);\n\n        // Pounds per cubic foot\n        $lbPerFt3 = new UnitOfMeasurement('lb/ft³', 16.0185);\n        $lbPerFt3-\u003eaddAlias('lb/ft3');\n        $this-\u003eaddUnit($lbPerFt3);\n    }\n}\n```\n\n---\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n\n---\n\n## Acknowledgments\n\n- Built with modern PHP 8.4 features\n- Original concept inspired by [PhpUnitsOfMeasure/php-units-of-measure](https://github.com/PhpUnitsOfMeasure/php-units-of-measure)\n- Redesigned with property hooks, immutability, and derived unit operations\n- Conversion factors sourced from NIST and other scientific standards\n\n---\n\n**Made with ❤️ for the PHP community**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenfordt%2Funitlib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frenfordt%2Funitlib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frenfordt%2Funitlib/lists"}