-
-
Save beaverusiv/67705e289e3451fbf33abcf3958ace21 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Cherry\Platform\Services; | |
use HaydenPierce\ClassFinder\ClassFinder; | |
class LabTestManagerService | |
{ | |
/** | |
* @var array $classes Array of strings of class names | |
*/ | |
private $classes; | |
/** | |
* @var array $map | |
*/ | |
private $map; | |
/** | |
* @param string $namespace | |
* | |
* @throws \Exception | |
*/ | |
public function loadProcessors(string $namespace): void | |
{ | |
$this->classes = ClassFinder::getClassesInNamespace($namespace); | |
foreach ($this->classes as $class) { | |
foreach ($class::$provides as $provided) { | |
if (!isset($this->map[$provided])) { | |
$this->map[$provided] = [$class]; | |
} else { | |
$this->map[$provided][] = $class; | |
} | |
} | |
} | |
} | |
/** | |
* @param array $inputs | |
* @param array $outputs | |
* | |
* @throws \Exception | |
* | |
* @return array | |
*/ | |
public function process(array $inputs, array $outputs): array | |
{ | |
$result = $inputs; | |
foreach ($outputs as $output) { | |
// base case - it is a pass-through | |
if (!array_key_exists($output, $result)) { | |
$processedOutput = $this->processOutput($result, $output, []); | |
if (!$processedOutput) { | |
throw new \Exception('didnt find a valid path for output: ' . $output); | |
} else { | |
$result = array_merge($processedOutput, $result); | |
} | |
} | |
} | |
return $result; | |
} | |
/** | |
* @param $inputs | |
* @param $output | |
* @param $callStack | |
* | |
* @throws \Exception | |
* | |
* @return null | |
*/ | |
private function processOutput($inputs, $output, $callStack) | |
{ | |
if (!isset($this->map[$output])) { | |
// come to the end of the line, return and check another branch | |
return null; | |
} | |
foreach ($this->map[$output] as $class) { | |
// if we've already called a class, don't call it again to prevent circular reference | |
if (in_array($class, $callStack) || !in_array($inputs['labDataFormat'], $class::$formats)) { | |
continue; | |
} | |
$params = []; | |
$requires = $class::$requires; | |
$requires = array_filter($requires, function ($require) use ($inputs, &$params) { | |
// don't search for requires which we have an input for | |
if (array_key_exists($require, $inputs)) { | |
$params[$require] = $inputs[$require]; | |
return false; | |
} | |
return true; | |
}); | |
foreach ($requires as $require) { | |
$newStack = $callStack; | |
$newStack[] = $class; | |
$processedOutput = $this->processOutput($inputs, $require, $newStack); | |
if (!$processedOutput) { | |
continue 2; | |
} else { | |
$params = array_merge($processedOutput, $params); | |
} | |
} | |
if (empty(array_diff($class::$requires, array_keys($params)))) { | |
return $class::process(array_merge($params, $inputs)); | |
} | |
} | |
return null; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Tests\Fixtures\LabTests; | |
use Cherry\Platform\Services\LabTests\LabTestProcessor; | |
class Boron extends LabTestProcessor | |
{ | |
public static $requires = ['boron']; | |
public static $provides = ['cobalt']; | |
public static function process(array $payload): array | |
{ | |
$payload['cobalt'] = $payload['boron']; | |
return $payload; | |
} | |
} | |
class Cobalt extends LabTestProcessor | |
{ | |
public static $requires = ['cobalt']; | |
public static $provides = ['cobaltDeficit']; | |
public static function process(array $payload): array | |
{ | |
$deficit = 10 - $payload['cobalt']; | |
return [ | |
'cobaltDeficit' => $deficit > 0 ? $deficit : 0 | |
]; | |
} | |
} | |
class Iron extends LabTestProcessor | |
{ | |
public static $requires = ['iron']; | |
public static $provides = ['cobalt']; | |
public static function process(array $payload): array | |
{ | |
$payload['cobalt'] = 2 * $payload['iron']; | |
return $payload; | |
} | |
} | |
class Magnesium extends LabTestProcessor | |
{ | |
public static $requires = ['magnesium']; | |
public static $provides = ['magnesiumDeficit']; | |
public static function process(array $payload): array | |
{ | |
$deficit = 10 - $payload['magnesium']; | |
return [ | |
'magnesiumDeficit' => $deficit > 0 ? $deficit : 0 | |
]; | |
} | |
} | |
class MagnesiumDeficit extends LabTestProcessor | |
{ | |
public static $requires = ['magnesiumDeficit']; | |
public static $provides = ['magnesium']; | |
public static function process(array $payload): array | |
{ | |
$deficit = 10 - $payload['magnesiumDeficit']; | |
return [ | |
'magnesium' => $deficit > 0 ? $deficit : 0 | |
]; | |
} | |
} | |
class Zinc extends LabTestProcessor | |
{ | |
public static $requires = ['magnesium']; | |
public static $provides = ['zinc']; | |
public static function process(array $payload): array | |
{ | |
$deficit = 10 - $payload['magnesium']; | |
return [ | |
'zinc' => $deficit > 0 ? $deficit : 0 | |
]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Tests\Unit\Services; | |
use Cherry\Platform\Services\LabTestManagerService; | |
use Tests\TestCase; | |
class LabTestManagerServiceTest extends TestCase | |
{ | |
/** | |
* @var LabTestManagerService $service | |
*/ | |
private $service; | |
public function setUp() : void | |
{ | |
$this->service = new LabTestManagerService(); | |
$this->service->loadProcessors('Tests\Fixtures\LabTests'); | |
} | |
public function testProcess_simpleSingleProcessor() | |
{ | |
$result = $this->service->process(['labDataFormat' => 'perry', 'cobalt' => 12], ['cobaltDeficit']); | |
$this->assertEquals( | |
['labDataFormat' => 'perry', 'cobaltDeficit' => 0, 'cobalt' => 12], | |
$result | |
); | |
} | |
public function testProcess_notAllProvidersAvailable() | |
{ | |
$this->expectException(\Exception::class); | |
$result = $this->service->process(['labDataFormat' => 'perry', 'cobalt' => 12], ['ironDeficit']); | |
} | |
public function testProcess_notAllRequiresAvailable() | |
{ | |
$this->expectException(\Exception::class); | |
$result = $this->service->process(['labDataFormat' => 'perry', 'sulphur' => 12], ['cobaltDeficit']); | |
} | |
public function testProcess_circularReference() | |
{ | |
$this->expectException(\Exception::class); | |
$result = $this->service->process(['labDataFormat' => 'perry', 'sulphur' => 12], ['zinc']); | |
} | |
public function testProcess_chainMultipleProcessors() | |
{ | |
$result = $this->service->process(['labDataFormat' => 'perry', 'boron' => 12], ['cobaltDeficit']); | |
$this->assertEquals( | |
['labDataFormat' => 'perry', 'cobaltDeficit' => 0, 'boron' => 12], | |
$result | |
); | |
} | |
public function testProcess_pickInputOverProcessor() | |
{ | |
$result = $this->service->process(['labDataFormat' => 'perry', 'boron' => 7, 'cobalt' => 12], ['cobaltDeficit']); | |
$this->assertEquals( | |
['labDataFormat' => 'perry', 'cobaltDeficit' => 0, 'boron' => 7, 'cobalt' => 12], | |
$result | |
); | |
} | |
public function testProcess_pickCorrectProcessorFromMultipleProcessors() | |
{ | |
$result = $this->service->process(['labDataFormat' => 'perry', 'iron' => 7], ['cobaltDeficit']); | |
$this->assertEquals( | |
['labDataFormat' => 'perry', 'cobaltDeficit' => 0, 'iron' => 7], | |
$result | |
); | |
} | |
public function testProcess_passThroughNulls() | |
{ | |
$this->markTestIncomplete( | |
'TODO: implement null pass-through test.' | |
); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment