277 lines
8.3 KiB
PHP
277 lines
8.3 KiB
PHP
<?php
|
|
namespace PhpUnitsOfMeasure;
|
|
|
|
abstract class AbstractPhysicalQuantity implements PhysicalQuantityInterface
|
|
{
|
|
/**
|
|
* The collection of units in which this quantity can be represented.
|
|
*
|
|
* Commented out to ensure that each child class defines its own instance of this static.
|
|
*
|
|
* @var UnitOfMeasureInterface[]
|
|
*/
|
|
// protected static $unitDefinitions;
|
|
|
|
/**
|
|
* Static cache for unit lookups.
|
|
*
|
|
* @var UnitOfMeasureInterface[]
|
|
*/
|
|
private static $unitCache = [];
|
|
|
|
/**
|
|
* Create a cache key for the unit lookup cache.
|
|
*
|
|
* @var UnitOfMeasureInterface[]
|
|
*/
|
|
private static function buildUnitCacheKey($unit)
|
|
{
|
|
return get_called_class() . '#' . $unit;
|
|
}
|
|
|
|
/**
|
|
* Register a new unit of measure for all instances of this this physical quantity.
|
|
*
|
|
* @throws Exception\DuplicateUnitNameOrAlias If the unit name or any alias already exists
|
|
*
|
|
* @param UnitOfMeasureInterface $unit The new unit of measure
|
|
*/
|
|
public static function addUnit(UnitOfMeasureInterface $unit)
|
|
{
|
|
if (static::unitNameOrAliasesAlreadyRegistered($unit)) {
|
|
throw new Exception\DuplicateUnitNameOrAlias([
|
|
':labels' => implode(', ', array_merge([$unit->getName()], $unit->getAliases()))
|
|
]);
|
|
}
|
|
|
|
static::$unitDefinitions[] = $unit;
|
|
}
|
|
|
|
/**
|
|
* Get the unit of measure that matches the given name by either name or alias.
|
|
*
|
|
* @param string $unit A name or alias by which the unit is known.
|
|
*
|
|
* @throws Exception\UnknownUnitOfMeasure when an unknown unit of measure is given
|
|
*
|
|
* @return UnitOfMeasureInterface
|
|
*/
|
|
public static function getUnit($unit)
|
|
{
|
|
// If this class hasn't been initialized yet, do so now
|
|
if (!is_array(static::$unitDefinitions)) {
|
|
static::$unitDefinitions = [];
|
|
static::initialize();
|
|
}
|
|
|
|
$key = static::buildUnitCacheKey($unit);
|
|
if (isset(self::$unitCache[$key])) {
|
|
return self::$unitCache[$key];
|
|
}
|
|
|
|
foreach (static::$unitDefinitions as $unitOfMeasure) {
|
|
if ($unit === $unitOfMeasure->getName() || $unitOfMeasure->isAliasOf($unit)) {
|
|
return self::$unitCache[$key] = $unitOfMeasure;
|
|
}
|
|
}
|
|
|
|
throw new Exception\UnknownUnitOfMeasure([':unit' => $unit]);
|
|
}
|
|
|
|
/**
|
|
* Given a unit of measure, determine if its name or any of its aliases conflict
|
|
* with the set of already-known unit names and aliases.
|
|
*
|
|
* @param UnitOfMeasureInterface $unit The unit in question
|
|
*
|
|
* @return boolean true if there is a conflict, false if there is not
|
|
*/
|
|
protected static function unitNameOrAliasesAlreadyRegistered(UnitOfMeasureInterface $unit)
|
|
{
|
|
// If this class hasn't been initialized yet, do so now
|
|
if (!is_array(static::$unitDefinitions)) {
|
|
static::$unitDefinitions = [];
|
|
static::initialize();
|
|
}
|
|
|
|
$currentUnitNamesAndAliases = [];
|
|
foreach (static::$unitDefinitions as $unitOfMeasure) {
|
|
$currentUnitNamesAndAliases[] = $unitOfMeasure->getName();
|
|
$currentUnitNamesAndAliases = array_merge($currentUnitNamesAndAliases, $unitOfMeasure->getAliases());
|
|
}
|
|
|
|
$newUnitNamesAndAliases = array_merge([$unit->getName()], $unit->getAliases());
|
|
|
|
return count(array_intersect($currentUnitNamesAndAliases, $newUnitNamesAndAliases)) > 0;
|
|
}
|
|
|
|
/**
|
|
* Initialize the static properties of this quantity class, such as the set of
|
|
* default units of measure.
|
|
*/
|
|
protected static function initialize()
|
|
{
|
|
}
|
|
|
|
|
|
/**
|
|
* The scalar value, in the original unit of measure.
|
|
*
|
|
* @var float
|
|
*/
|
|
protected $originalValue;
|
|
|
|
/**
|
|
* The original unit of measure's string representation.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $originalUnit;
|
|
|
|
/**
|
|
* Store the value and its original unit.
|
|
*
|
|
* @param float $value The scalar value of the measurement
|
|
* @param string $unit The unit of measure in which this value is provided
|
|
*
|
|
* @throws Exception\NonNumericValue If the value is not numeric
|
|
* @throws Exception\NonStringUnitName If the unit is not a string
|
|
*/
|
|
public function __construct($value, $unit)
|
|
{
|
|
if (!is_numeric($value)) {
|
|
throw new Exception\NonNumericValue([':value' => $value]);
|
|
}
|
|
|
|
if (!is_string($unit)) {
|
|
throw new Exception\NonStringUnitName([':name' => $unit]);
|
|
}
|
|
|
|
$this->originalValue = $value;
|
|
$this->originalUnit = $unit;
|
|
}
|
|
|
|
/**
|
|
* @see \PhpUnitsOfMeasure\PhysicalQuantityInterface::toUnit
|
|
*/
|
|
public function toUnit($toUnit)
|
|
{
|
|
return $this->toUnitOfMeasure(static::getUnit($toUnit));
|
|
}
|
|
|
|
/**
|
|
* Convert this quantity to the given unit of measure.
|
|
*
|
|
* @param UnitOfMeasureInterface $unit The object representing the target unit of measure.
|
|
*
|
|
* @return float This quantity's value in the given unit of measure.
|
|
*/
|
|
private function toUnitOfMeasure(UnitOfMeasureInterface $unit)
|
|
{
|
|
$thisValueInNativeUnit = $this->toNativeUnit();
|
|
return $unit->convertValueFromNativeUnitOfMeasure($thisValueInNativeUnit);
|
|
}
|
|
|
|
/**
|
|
* @see \PhpUnitsOfMeasure\PhysicalQuantityInterface::toNativeUnit
|
|
*/
|
|
public function toNativeUnit()
|
|
{
|
|
return static::getUnit($this->originalUnit)
|
|
->convertValueToNativeUnitOfMeasure($this->originalValue);
|
|
}
|
|
|
|
/**
|
|
* @see \PhpUnitsOfMeasure\PhysicalQuantityInterface::__toString
|
|
*/
|
|
public function __toString()
|
|
{
|
|
return trim($this->originalValue . ' ' . static::getUnit($this->originalUnit)->getName());
|
|
}
|
|
|
|
/**
|
|
* @see \PhpUnitsOfMeasure\PhysicalQuantityInterface::add
|
|
*/
|
|
public function add(PhysicalQuantityInterface $quantity)
|
|
{
|
|
if (!$this->isEquivalentQuantity($quantity)) {
|
|
throw new Exception\PhysicalQuantityMismatch([
|
|
':lhs' => (string) $this,
|
|
':rhs' => (string) $quantity
|
|
]);
|
|
}
|
|
|
|
$quantityValueInThisOriginalUnit = $quantity->toUnitOfMeasure(static::getUnit($this->originalUnit));
|
|
$newValue = $this->originalValue + $quantityValueInThisOriginalUnit;
|
|
|
|
return new static($newValue, static::getUnit($this->originalUnit)->getName());
|
|
}
|
|
|
|
/**
|
|
* @see \PhpUnitsOfMeasure\PhysicalQuantityInterface::subtract
|
|
*/
|
|
public function subtract(PhysicalQuantityInterface $quantity)
|
|
{
|
|
if (!$this->isEquivalentQuantity($quantity)) {
|
|
throw new Exception\PhysicalQuantityMismatch([
|
|
':lhs' => (string) $this,
|
|
':rhs' => (string) $quantity
|
|
]);
|
|
}
|
|
|
|
$quantityValueInThisOriginalUnit = $quantity->toUnitOfMeasure(static::getUnit($this->originalUnit));
|
|
$newValue = $this->originalValue - $quantityValueInThisOriginalUnit;
|
|
|
|
return new static($newValue, static::getUnit($this->originalUnit)->getName());
|
|
}
|
|
|
|
/**
|
|
* @see \PhpUnitsOfMeasure\PhysicalQuantityInterface::isEquivalentQuantity
|
|
*/
|
|
public function isEquivalentQuantity(PhysicalQuantityInterface $testQuantity)
|
|
{
|
|
return get_class($this) === get_class($testQuantity);
|
|
}
|
|
|
|
/**
|
|
* @see \PhpUnitsOfMeasure\PhysicalQuantityInterface::isUnitDefined
|
|
*/
|
|
public static function isUnitDefined($name)
|
|
{
|
|
$units = static::getUnitDefinitions();
|
|
foreach ($units as $unit) {
|
|
if ($name === $unit->getName() || $unit->isAliasOf($name)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* @see \PhpUnitsOfMeasure\PhysicalQuantityInterface::listAllUnits
|
|
*/
|
|
public static function listAllUnits()
|
|
{
|
|
$return = [];
|
|
$units = static::getUnitDefinitions();
|
|
foreach ($units as $unit) {
|
|
$return[$unit->getName()] = $unit->getAliases();
|
|
}
|
|
return $return;
|
|
}
|
|
|
|
/**
|
|
* Get the unit definition array
|
|
* @return Array $unitDefinitions
|
|
*/
|
|
public static function getUnitDefinitions()
|
|
{
|
|
if (!is_array(static::$unitDefinitions)) {
|
|
static::$unitDefinitions = [];
|
|
static::initialize();
|
|
}
|
|
|
|
return static::$unitDefinitions;
|
|
}
|
|
}
|