Skip to content

Commit

Permalink
Merge pull request #8 from brewerwall/feature/rates
Browse files Browse the repository at this point in the history
Start of Rates structure with Flow
  • Loading branch information
griffithben authored Oct 3, 2023
2 parents 8a97af8 + fded471 commit d059c9e
Show file tree
Hide file tree
Showing 20 changed files with 389 additions and 61 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
}
],
"require": {
"php": "^8.1"
"php": "^8.1",
"doctrine/inflector": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^10.2"
Expand Down
96 changes: 94 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/AbstractUnitz.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public function setValue(float $value): self
* @param array $values
* @return bool
*/
protected function hasOnlyOneValue(array $values): bool
protected function hasOneOrNoneValue(array $values): bool
{
$count = 0;
foreach ($values as $value) {
Expand All @@ -52,7 +52,7 @@ protected function hasOnlyOneValue(array $values): bool
}
}

return $count === 1;
return $count <= 1;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/Color.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public function __construct(
?float $userValue = null,
array $preferences = []
) {
if (!$this->hasOnlyOneValue([$srm, $ebc, $lovibond, $userValue])) {
if (!$this->hasOneOrNoneValue([$srm, $ebc, $lovibond, $userValue])) {
throw new InvalidArgumentException('Only one Color type can be set at a time.');
}

Expand Down
2 changes: 1 addition & 1 deletion src/Distillate.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function __construct(
?float $userValue = null,
array $preferences = []
) {
if (!$this->hasOnlyOneValue([$proof, $percentAlcohol, $userValue])) {
if (!$this->hasOneOrNoneValue([$proof, $percentAlcohol, $userValue])) {
throw new InvalidArgumentException('Only one Distillate type can be set at a time.');
}

Expand Down
2 changes: 1 addition & 1 deletion src/Gravity.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public function __construct(
?float $userValue = null,
array $preferences = []
) {
if (!$this->hasOnlyOneValue([$plato, $specificGravity, $brix, $userValue])) {
if (!$this->hasOneOrNoneValue([$plato, $specificGravity, $brix, $userValue])) {
throw new InvalidArgumentException('Only one Gravity type can be set at a time.');
}

Expand Down
2 changes: 1 addition & 1 deletion src/Pressure.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function __construct(
?float $userValue = null,
array $preferences = []
) {
if (!$this->hasOnlyOneValue([$bar, $psi, $userValue])) {
if (!$this->hasOneOrNoneValue([$bar, $psi, $userValue])) {
throw new InvalidArgumentException('Only one Pressure type can be set at a time.');
}

Expand Down
130 changes: 130 additions & 0 deletions src/Rate/AbstractRate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

namespace Unitz\Rate;

use Doctrine\Inflector\Inflector;
use Doctrine\Inflector\InflectorFactory;
use ReflectionClass;
use RuntimeException;
use Unitz\BaseUnitz;

abstract class AbstractRate
{
private const UNIT_NAME_NUMERATOR = 0;
private const UNIT_NAME_DENOMINATOR = 1;

private Inflector $inflector;
private BaseUnitz $numerator;
private BaseUnitz $denominator;

public function __construct()
{
$this->inflector = InflectorFactory::create()->build();
}

/**
* @param \Unitz\BaseUnitz $numerator
* @return void
*/
protected function setNumerator(BaseUnitz $numerator): void
{
$this->numerator = $numerator;
}

/**
* @param \Unitz\BaseUnitz $denominator
* @return void
*/
protected function setDenominator(BaseUnitz $denominator): void
{
$this->denominator = $denominator;
}

/**
* @param string $methodName
* @param array $arguments
* @return \Unitz\BaseUnitz
* @throws \RuntimeException
*/
public function __call(string $methodName, array $arguments): BaseUnitz
{
$unitNames = $this->getUnitNames($this->checkAndRemoveGetPrefix($methodName));

$numeratorName = $this->processUnitName($unitNames[self::UNIT_NAME_NUMERATOR]);
$denominatorName = $this->processUnitName($unitNames[self::UNIT_NAME_DENOMINATOR]);

$numerator = $this->getUnitValue($this->numerator, $numeratorName);
$denominator = $this->getUnitValue($this->denominator, $denominatorName);

$numeratorReflection = new ReflectionClass($this->numerator);
$unitsClassName = $numeratorReflection->getName();
$unitsClassShortName = $numeratorReflection->getShortName();

return (new $unitsClassName(preferences: [$unitsClassShortName => $numeratorName]))->{"set$numeratorName"}(
$numerator / $denominator
);
}

/**
* @param string $name
* @return string
* @throws \RuntimeException
*/
private function checkAndRemoveGetPrefix(string $name): string
{
$getPosition = strpos($name, 'get');
if ($getPosition === false || $getPosition !== 0) {
throw new RuntimeException('All methods must start with "get"');
}

return str_replace('get', '', $name);
}

/**
* @param string $name
* @return array
* @throws \RuntimeException
*/
private function getUnitNames(string $name): array
{
$perPosition = strpos($name, 'Per');
if ($perPosition === false) {
throw new RuntimeException('All methods must contain "Per" between the two units');
}

$units = array_filter(explode('Per', $name));
if (count($units) !== 2) {
throw new RuntimeException(
'All methods must contain two units separated by "Per", get' . $name . '() has ' . count(
$units
)
);
}

return $units;
}

/**
* @param string $unitName
* @return string
*/
private function processUnitName(string $unitName): string
{
return $this->inflector->singularize(ucfirst(strtolower($unitName)));
}

/**
* @param \Unitz\BaseUnitz $unit
* @param string $unitName
* @return float
* @throws \RuntimeException
*/
private function getUnitValue(BaseUnitz $unit, string $unitName): float
{
if (!method_exists($unit, "get$unitName")) {
throw new RuntimeException("Method get$unitName does not exist on " . get_class($unit));
}

return $unit->{"get$unitName"}();
}
}
81 changes: 81 additions & 0 deletions src/Rate/Flow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php

namespace Unitz\Rate;

use Unitz\Time;
use Unitz\Volume;

/** Magic methods available through AbstractRate::__call()
* @method Volume getOuncesPerMillisecond()
* @method Volume getOuncesPerSecond()
* @method Volume getOuncesPerMinute()
* @method Volume getOuncesPerHour()
* @method Volume getOuncesPerDay()
* @method Volume getOuncesPerWeek()
* @method Volume getOuncesPerMonth()
* @method Volume getOuncesPerYear()
* @method Volume getGallonsPerMillisecond
* @method Volume getGallonsPerSecond()
* @method Volume getGallonsPerMinute()
* @method Volume getGallonsPerHour()
* @method Volume getGallonsPerDay()
* @method Volume getGallonsPerWeek()
* @method Volume getGallonsPerMonth()
* @method Volume getGallonsPerYear()
* @method Volume getLitersPerMillisecond()
* @method Volume getLitersPerSecond()
* @method Volume getLitersPerMinute()
* @method Volume getLitersPerHour()
* @method Volume getLitersPerDay()
* @method Volume getLitersPerWeek()
* @method Volume getLitersPerMonth()
* @method Volume getLitersPerYear()
* @method Volume getMillilitersPerMillisecond()
* @method Volume getMillilitersPerSecond()
* @method Volume getMillilitersPerMinute()
* @method Volume getMillilitersPerHour()
* @method Volume getMillilitersPerDay()
* @method Volume getMillilitersPerWeek()
* @method Volume getMillilitersPerMonth()
* @method Volume getMillilitersPerYear()
* @method Volume getHectolitersPerMillisecond
* @method Volume getHectolitersPerSecond()
* @method Volume getHectolitersPerMinute()
* @method Volume getHectolitersPerHour()
* @method Volume getHectolitersPerDay()
* @method Volume getHectolitersPerWeek()
* @method Volume getHectolitersPerMonth()
* @method Volume getHectolitersPerYear()
* @method Volume getBarrelsPerSecond()
* @method Volume getBarrelsPerMinute()
* @method Volume getBarrelsPerHour()
* @method Volume getBarrelsPerDay()
* @method Volume getBarrelsPerWeek()
* @method Volume getBarrelsPerMonth()
* @method Volume getBarrelsPerYear()
*/
class Flow extends AbstractRate
{
public function __construct(private readonly Volume $volume, private readonly Time $time)
{
parent::__construct();
$this->setNumerator($volume);
$this->setDenominator($time);
}

/**
* @return \Unitz\Volume
*/
public function getVolume(): Volume
{
return $this->volume;
}

/**
* @return \Unitz\Time
*/
public function getTime(): Time
{
return $this->time;
}
}
2 changes: 1 addition & 1 deletion src/Temperature.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public function __construct(
?float $userValue = null,
array $preferences = []
) {
if (!$this->hasOnlyOneValue([$fahrenheit, $celsius, $userValue])) {
if (!$this->hasOneOrNoneValue([$fahrenheit, $celsius, $userValue])) {
throw new InvalidArgumentException('Only one Temperature type can be set at a time.');
}

Expand Down
2 changes: 1 addition & 1 deletion src/Time.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function __construct(
?float $userValue = null,
array $preferences = []
) {
if (!$this->hasOnlyOneValue(
if (!$this->hasOneOrNoneValue(
[$millisecond, $second, $minute, $hour, $day, $week, $month, $year, $userValue]
)) {
throw new InvalidArgumentException('Only one Time type can be set at a time.');
Expand Down
Loading

0 comments on commit d059c9e

Please sign in to comment.