PHP Classes

File: src/Jobs.php

Recommend this page to a friend!
  Classes of Rodolfo Berrios Arce   Workflow   src/Jobs.php   Download  
File: src/Jobs.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Workflow
Create and run action workflows
Author: By
Last change:
Date: 1 month ago
Size: 12,435 bytes
 

Contents

Class file image Download
<?php /* * This file is part of Chevere. * * (c) Rodolfo Berrios <[email protected]> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace Chevere\Workflow; use Chevere\DataStructure\Interfaces\MapInterface; use Chevere\DataStructure\Interfaces\VectorInterface; use Chevere\DataStructure\Map; use Chevere\DataStructure\Traits\MapTrait; use Chevere\DataStructure\Vector; use Chevere\Parameter\Interfaces\BoolParameterInterface; use Chevere\Parameter\Interfaces\MixedParameterInterface; use Chevere\Parameter\Interfaces\ParameterInterface; use Chevere\Parameter\Interfaces\ParametersAccessInterface; use Chevere\Parameter\Interfaces\UnionParameterInterface; use Chevere\Workflow\Exceptions\JobsException; use Chevere\Workflow\Interfaces\GraphInterface; use Chevere\Workflow\Interfaces\JobInterface; use Chevere\Workflow\Interfaces\JobsInterface; use Chevere\Workflow\Interfaces\ResponseReferenceInterface; use Chevere\Workflow\Interfaces\VariableInterface; use InvalidArgumentException; use LogicException; use OutOfBoundsException; use OverflowException; use Throwable; use TypeError; use function Chevere\Action\getParameters; use function Chevere\Message\message; use function Chevere\Parameter\bool; final class Jobs implements JobsInterface { /** * @template-use MapTrait<JobInterface> */ use MapTrait; /** * @var VectorInterface<string> */ private VectorInterface $jobs; private GraphInterface $graph; /** * @var Map<ParameterInterface> */ private MapInterface $variables; /** * @var Map<ParameterInterface> */ private MapInterface $references; /** * @var VectorInterface<string> */ private VectorInterface $jobDependencies; public function __construct(JobInterface ...$jobs) { $this->map = new Map(); $this->jobs = new Vector(); $this->graph = new Graph(); $this->variables = new Map(); $this->references = new Map(); $this->putAdded(...$jobs); } public function graph(): GraphInterface { return $this->graph; } public function variables(): MapInterface { return $this->variables; } public function references(): MapInterface { return $this->references; } public function get(string $job): JobInterface { /** @var JobInterface */ return $this->map->get($job); } public function has(string $job): bool { return $this->map->has($job); } public function withAdded(JobInterface ...$jobs): JobsInterface { $new = clone $this; $new->putAdded(...$jobs); return $new; } private function addMap(string $name, JobInterface $job): void { if ($this->map->has($name)) { throw new OverflowException( (string) message( 'Job name `%name%` has been already added.', name: $name ) ); } $this->map = $this->map->withPut($name, $job); } private function putAdded(JobInterface ...$job): void { foreach ($job as $name => $item) { $this->jobDependencies = $item->dependencies(); $name = strval($name); $this->addMap($name, $item); $this->jobs = $this->jobs->withPush($name); $this->handleArguments($name, $item); foreach ($item->runIf() as $runIf) { $this->handleRunIfReference($runIf); $this->handleRunIfVariable($name, $runIf); } $this->storeReferences($name, $item); $this->assertDependencies($name); $this->graph = $this->graph->withPut($name, $item); } } private function storeReferences(string $job, JobInterface $item): void { $action = $item->action(); $return = $action::return(); if ($return instanceof ParametersAccessInterface && ! ($return instanceof UnionParameterInterface) ) { foreach ($return->parameters() as $key => $parameter) { $this->references = $this->references ->withPut( strval(response($job, $key)), $parameter, ); } } else { $this->references = $this->references ->withPut( strval(response($job)), $return, ); } } private function handleArguments(string $job, JobInterface $item): void { $errors = []; foreach ($item->arguments() as $argument => $value) { $action = $item->action(); $parameters = getParameters($action::class); if ($parameters->has($argument)) { $parameter = $parameters->get($argument); } elseif ($parameters->isVariadic()) { $lastKey = array_key_last($parameters->keys()); $lastName = $parameters->keys()[$lastKey]; $parameter = $parameters->get($lastName); } $collection = match (true) { $value instanceof VariableInterface => 'variables', $value instanceof ResponseReferenceInterface => 'references', default => false }; if (! $collection) { continue; } try { /** * @var VariableInterface|ResponseReferenceInterface $value * @phpstan-ignore-next-line */ $this->mapParameter($argument, $collection, $parameter, $value); } catch (Throwable $e) { throw new JobsException( name: $job, job: $item, throwable: $e ); } } } private function mapParameter( string $argument, string $collection, ParameterInterface $parameter, VariableInterface|ResponseReferenceInterface $value, ): void { /** @var MapInterface<ParameterInterface> $map */ $map = $this->{$collection}; $subject = 'Reference'; $identifier = strval($value); if ($value instanceof VariableInterface) { $subject = 'Variable'; } else { try { /** @var JobInterface $referenceJob */ $referenceJob = $this->map->get($value->job()); /** @var ParameterInterface $accept */ $accept = $referenceJob->action()::return(); if ($value->key() !== null) { if (! $accept instanceof ParametersAccessInterface) { throw new LogicException( (string) message( "Invalid reference **%reference%** as **%job%** doesn't return an object implementing %interface% interface", job: $value->job(), reference: strval($value), interface: ParametersAccessInterface::class ) ); } $accept->parameters()->get($value->key()); } } catch (OutOfBoundsException) { throw new OutOfBoundsException( (string) message( '%subject% **%key%** not found', subject: $subject, key: $identifier ) ); } } if (! $map->has($identifier)) { $map = $map->withPut($identifier, $parameter); $this->{$collection} = $map; return; } /** @var ParameterInterface $stored */ $stored = $map->get($identifier); if ($parameter instanceof UnionParameterInterface) { foreach ($parameter->parameters() as $tryParameter) { try { $stored->assertCompatible($tryParameter); $parameter = $tryParameter; break; } catch (TypeError $e) { } } } if ($stored instanceof MixedParameterInterface || $parameter instanceof MixedParameterInterface ) { return; // @codeCoverageIgnore } if ($stored::class !== $parameter::class) { throw new TypeError( (string) message( '%subject% **%key%** is of type `%type%`, parameter **%parameter%** expects `%expected%`', parameter: $argument, type: $stored->type()->primitive(), expected: $parameter->type()->primitive(), subject: $subject, key: $identifier ) ); } try { $stored->assertCompatible($parameter); } catch (InvalidArgumentException $e) { throw new InvalidArgumentException( (string) message( '%subject% **%key%** conflict for parameter **%parameter%** (%message%).', subject: $subject, key: $identifier, parameter: $argument, message: $e->getMessage() ) ); } } private function handleRunIfReference(mixed $runIf): void { if (! $runIf instanceof ResponseReferenceInterface) { return; } $action = $this->get($runIf->job())->action(); $accept = $action::return(); if ($runIf->key() !== null) { if (! $accept instanceof ParametersAccessInterface) { throw new OutOfBoundsException( (string) message( 'Reference **%reference%** job `%job%` response doesn\'t bind to `%parameter%` parameter', reference: strval($runIf), job: $runIf->job(), parameter: $runIf->key() ) ); } $accept = $accept->parameters()->get($runIf->key()); } if ($accept->type()->primitive() === 'bool') { return; } throw new TypeError( (string) message( 'Reference **%reference%** must be of type `bool`, `%type%` provided', reference: strval($runIf), type: $accept->type()->primitive() ) ); } private function handleRunIfVariable(string $name, mixed $runIf): void { if (! $runIf instanceof VariableInterface) { return; } if (! $this->variables->has($runIf->__toString())) { $this->variables = $this->variables ->withPut( $runIf->__toString(), bool(), ); return; } /** @var ParameterInterface $parameter */ $parameter = $this->variables->get($runIf->__toString()); if (! ($parameter instanceof BoolParameterInterface)) { throw new TypeError( (string) message( 'Variable **%variable%** (previously declared as `%type%`) is not of type `bool` at Job **%job%**', variable: $runIf->__toString(), type: $parameter->type()->primitive(), job: $name, ) ); } } private function assertDependencies(string $job): void { $dependencies = $this->jobDependencies->toArray(); if (! $this->jobs->contains(...$dependencies)) { $missing = array_diff( $dependencies, $this->jobs->toArray() ); throw new OutOfBoundsException( (string) message( 'Job **%job%** has undeclared dependencies: `%dependencies%`', job: $job, dependencies: implode(', ', $missing), ) ); } } }