Skip to content

Instantly share code, notes, and snippets.

@beaverusiv
Last active May 21, 2019 20:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beaverusiv/67705e289e3451fbf33abcf3958ace21 to your computer and use it in GitHub Desktop.
Save beaverusiv/67705e289e3451fbf33abcf3958ace21 to your computer and use it in GitHub Desktop.
<?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;
}
}
<?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
];
}
}
<?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