From 6539737b8eebfddaf19f8c14b06339a5b95def65 Mon Sep 17 00:00:00 2001 From: Mike Web Date: Sat, 21 Jun 2025 16:48:50 -0400 Subject: [PATCH] conjunction concept --- src/Conjunctions/AndConjuntion.php | 13 +++ src/Conjunctions/Conjunction.php | 102 ++++++++++++++++++++++++ src/Conjunctions/NorConjunction.php | 13 +++ src/Conjunctions/OrConjunction.php | 13 +++ src/Interfaces/ConjunctionInterface.php | 25 ++++++ src/Job.php | 6 +- src/Runner.php | 92 +++++++++++++++++---- src/functions.php | 35 ++++++++ 8 files changed, 281 insertions(+), 18 deletions(-) create mode 100644 src/Conjunctions/AndConjuntion.php create mode 100644 src/Conjunctions/Conjunction.php create mode 100644 src/Conjunctions/NorConjunction.php create mode 100644 src/Conjunctions/OrConjunction.php create mode 100644 src/Interfaces/ConjunctionInterface.php diff --git a/src/Conjunctions/AndConjuntion.php b/src/Conjunctions/AndConjuntion.php new file mode 100644 index 0000000..5d426b9 --- /dev/null +++ b/src/Conjunctions/AndConjuntion.php @@ -0,0 +1,13 @@ +putConditions(...$conditions); + } + + public function conditions(): VectorInterface + { + return $this->_conditions; + } + + public function toArray(): array + { + return array_values( + $this->conditions()->toArray() + ); + } + + public function getIterator(): Iterator + { + return $this + ->conditions() + ->getIterator(); + } + + public function withPut( + VariableInterface|ResponseReferenceInterface|ConjunctionInterface|callable ...$conditions + ): static + { + return (clone $this) + ->putConditions(...$conditions); + } + + /** + * @return array + */ + public function keys(): array + { + return array_values( + $this->conditions()->keys() + ); + } + + protected function putConditions( + VariableInterface|ResponseReferenceInterface|ConjunctionInterface|callable ...$conditions + ): static + { + $known = new Vector(); + $this->_conditions = new Vector(); + + foreach ($conditions as $condition) { + $id = match (true) { + $condition instanceof ResponseReferenceInterface, + $condition instanceof VariableInterface => $condition->__toString(), + $condition instanceof ConjunctionInterface => 'conjunction#' . spl_object_id($condition), + $condition instanceof Closure => 'callable#' . spl_object_id($condition), + default => null, + }; + + if ( $id !== null && $known->contains($id) ) { + throw new OverflowException( + (string) message( + 'Condition `%condition%` is already defined', + condition: $id + ) + ); + } + + if ($id !== null) { + $known = $known + ->withPush($id); + + $this->_conditions = $this + ->conditions() + ->withPush($condition); + } + } + + return $this; + } +} \ No newline at end of file diff --git a/src/Conjunctions/NorConjunction.php b/src/Conjunctions/NorConjunction.php new file mode 100644 index 0000000..b6adae0 --- /dev/null +++ b/src/Conjunctions/NorConjunction.php @@ -0,0 +1,13 @@ +runIf = new Vector(); @@ -106,6 +109,7 @@ public function withRunIf(ResponseReferenceInterface|VariableInterface|callable $itemString = match (true) { $item instanceof ResponseReferenceInterface, $item instanceof VariableInterface => $item->__toString(), + $item instanceof ConjunctionInterface => 'conjunction#' . spl_object_id($item), $item instanceof Closure => 'callable#' . spl_object_id($item), default => null, }; diff --git a/src/Runner.php b/src/Runner.php index c54c134..92e0a3e 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -14,17 +14,22 @@ namespace Chevere\Workflow; use Amp\Parallel\Worker\Execution; +use Chevere\Parameter\Interfaces\BoolParameterInterface; use Chevere\Parameter\Interfaces\CastInterface; +use Chevere\Workflow\Conjunctions\NorConjunction; use Chevere\Workflow\Exceptions\RunnerException; +use Chevere\Workflow\Interfaces\ConjunctionInterface; use Chevere\Workflow\Interfaces\JobInterface; use Chevere\Workflow\Interfaces\ResponseReferenceInterface; use Chevere\Workflow\Interfaces\RunInterface; use Chevere\Workflow\Interfaces\RunnerInterface; use Chevere\Workflow\Interfaces\VariableInterface; +use InvalidArgumentException; use OutOfBoundsException; use Throwable; use function Amp\Future\await; use function Amp\Parallel\Worker\submit; +use function Chevere\Message\message; use function Chevere\Parameter\cast; final class Runner implements RunnerInterface @@ -104,15 +109,37 @@ public function withRunJob(string $name): RunnerInterface return $new; } - private function getRunIfCondition(VariableInterface|ResponseReferenceInterface|callable $runIf): bool + private function getRunIfCondition( + ConjunctionInterface|VariableInterface|ResponseReferenceInterface|callable $runIf + ): bool { + if ($runIf instanceof ConjunctionInterface) { + $results = []; + + foreach ($runIf->getIterator() as $runIfCondition) { + $results[] = $this + ->getRunIfCondition($runIfCondition); + } + + $filter = array_filter($results); + + return match ($runIf->conjunction()) { + ConjunctionInterface::CONJUNCTION_NOR => empty($filter), + ConjunctionInterface::CONJUNCTION_OR => !empty($filter), + ConjunctionInterface::CONJUNCTION_AND => ($results == $filter), + default => false + }; + } + /** @var boolean */ return match(true) { $runIf instanceof VariableInterface => $this->run->arguments()->required($runIf->__toString())->bool(), $runIf instanceof ResponseReferenceInterface => - $this->run->getReturn($runIf->job())->array()[$runIf->key()], + ($this->run->workflow()->jobs()->get($runIf->job())->action()::return() instanceof BoolParameterInterface) ? + $this->run->response($runIf->job())->bool() : + $this->run->response($runIf->job())->array()[$runIf->key()], default => call_user_func($runIf, $this->run()) @@ -126,30 +153,61 @@ private function getJobArguments(JobInterface $job): array { $arguments = []; foreach ($job->arguments() as $name => $value) { - $isResponseReference = $value instanceof ResponseReferenceInterface; - $isVariable = $value instanceof VariableInterface; - if (! ($isResponseReference || $isVariable)) { - $arguments[$name] = $value; + if ($value instanceof VariableInterface || + $value instanceof ResponseReferenceInterface || + $value instanceof ConjunctionInterface + ) { + try { + $arguments[$name] = $this + ->processJobArgument($value); + + } catch (Throwable $e) { + throw new RunnerException($name, $job, $e); + } continue; } - if ($isVariable) { - /** @var VariableInterface $value */ - $arguments[$name] = $this->run->arguments() - ->get($value->__toString()); - continue; + $arguments[$name] = $value; + } + + return $arguments; + } + + private function processJobArgument( + ConjunctionInterface|VariableInterface|ResponseReferenceInterface|callable $value + ): mixed + { + if ($value instanceof ConjunctionInterface) { + if ($value->conjunction() !== ConjunctionInterface::CONJUNCTION_OR) { + throw new RunnerException(''); } - /** @var ResponseReferenceInterface $value */ - if ($value->key() !== null) { - $arguments[$name] = $this->run->response($value->job())->array()[$value->key()]; - continue; + /** @var ConjunctionInterface|VariableInterface|ResponseReferenceInterface|callable $conditional */ + foreach ($value->getIterator() as $conditional) { + $val = $this + ->processJobArgument($conditional); + + if ($val !== null) { + return $val; + } } - $arguments[$name] = $this->run->response($value->job())->mixed(); + + throw new InvalidArgumentException(); } - return $arguments; + return match(true) { + $value instanceof VariableInterface => + $this->run->arguments()->get($value->__toString()), + + $value instanceof ResponseReferenceInterface => + ($value->key() !== null) ? + $this->run->response($value->job())->array()[$value->key()] : + $this->run->response($value->job())->mixed(), + + default => + call_user_func($value, $this->run()) + }; } private function addJobResponse(string $name, CastInterface $response): void diff --git a/src/functions.php b/src/functions.php index 52c759d..29f687b 100644 --- a/src/functions.php +++ b/src/functions.php @@ -14,6 +14,11 @@ namespace Chevere\Workflow; use Chevere\Action\Interfaces\ActionInterface; +use Chevere\Workflow\Conjunctions\AndConjuntion; +use Chevere\Workflow\Conjunctions\Conjunction; +use Chevere\Workflow\Conjunctions\NorConjunction; +use Chevere\Workflow\Conjunctions\OrConjunction; +use Chevere\Workflow\Interfaces\ConjunctionInterface; use Chevere\Workflow\Interfaces\JobInterface; use Chevere\Workflow\Interfaces\ResponseReferenceInterface; use Chevere\Workflow\Interfaces\RunInterface; @@ -101,6 +106,36 @@ function runnerForJob(RunnerInterface $runner, string $job): RunnerInterface return $runner->withRunJob($job); } +/** + * Creates an AND style Conjunction + * @param ConjunctionInterface|ResponseReferenceInterface|VariableInterface|callable ...$conditions + * @return ConjunctionInterface + */ +function cand(ConjunctionInterface|ResponseReferenceInterface|VariableInterface|callable ...$conditions): ConjunctionInterface +{ + return new AndConjuntion(...$conditions); +} + +/** + * Creates an OR style Conjunction + * @param ConjunctionInterface|ResponseReferenceInterface|VariableInterface|callable ...$conditions + * @return ConjunctionInterface + */ +function cor(ConjunctionInterface|ResponseReferenceInterface|VariableInterface|callable ...$conditions): ConjunctionInterface +{ + return new OrConjunction(...$conditions); +} + +/** + * Creates a NOR style Conjunction + * @param ConjunctionInterface|ResponseReferenceInterface|VariableInterface|callable ...$conditions + * @return ConjunctionInterface + */ +function cnor(ConjunctionInterface|ResponseReferenceInterface|VariableInterface|callable ...$conditions): ConjunctionInterface +{ + return new NorConjunction(...$conditions); +} + /** * Creates a RunInterface instance for the given workflow and named variables . */