Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ $builder
Once the model bas been built, it can be executed by creating a new instance. At this point it is possible to pass some data that could be made available throughout the process. The data can be any object which could be also updated as part of the process.

``` php
$workflow = $builder->getWorkflow();
$instance = new WorkflowInstance($workflow, $data);
$engine = new Engine();
$instance = $engine->createInstance($builder->getWorkflow(), $input);
$instance->execute();
```

Expand Down
3 changes: 2 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ During the execution, information is exchanged between each Workflow Node. In pa
Here is a short example to get you started:

``` php
$instance = new WorkflowInstance($workflow, $input);
$engine = new Engine();
$instance = $engine->createInstance($workflow, $input);
$output = $instance->execute();
```

Expand Down
5 changes: 3 additions & 2 deletions docs/conditional-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The following example demonstrates how to apply conditions and branching in your
``` php
require __DIR__.'/../vendor/autoload.php';

$flow = (new \Phlow\Model\WorkflowBuilder())
$workflow = (new \Phlow\Model\WorkflowBuilder())
->start()
->choice()
->when('number < 100')
Expand All @@ -18,7 +18,8 @@ $flow = (new \Phlow\Model\WorkflowBuilder())
->endAll()
->getWorkflow();

$instance = new \Phlow\Engine\WorkflowInstance($flow, ['number' => 99]);
$engine = new \Phlow\Engine\Engine();
$instance = $engine->createInstance($workflow, ['number' => 99]);
$instance->execute();

print $flow->render(new \Phlow\Renderer\PlainTextRenderer());
Expand Down
5 changes: 3 additions & 2 deletions docs/sequence-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The following example demonstrates how to execute steps in sequence. As part of
``` php
require __DIR__.'/../vendor/autoload.php';

$flow = (new \Phlow\Model\WorkflowBuilder())
$workflow = (new \Phlow\Model\WorkflowBuilder())
->start()
->callback(function ($data) {
$data['a'] = rand(1, 100);
Expand All @@ -22,7 +22,8 @@ $flow = (new \Phlow\Model\WorkflowBuilder())
->end()
->getWorkflow();

$instance = new \Phlow\Engine\WorkflowInstance($flow, []);
$engine = new \Phlow\Engine\Engine();
$instance = $engine->createInstance($workflow, []);
$instance->execute();

print $flow->render(new \Phlow\Renderer\PlainTextRenderer());
Expand Down
6 changes: 4 additions & 2 deletions docs/workflow-engine.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ During the execution, information is exchanged between each Workflow Node. In pa
Here is a short example to get you started:

``` php
$instance = new WorkflowInstance($workflow, $input);
$engine = new Engine();
$instance = $engine->createInstance($workflow, $input);
$output = $instance->execute();
```

It is also possible to advance the workflow for only one node. In this case, the execution will proceed to the next node and return the generated outbound message.

``` php
$instance = new WorkflowInstance($workflow, $input);
$engine = new Engine();
$instance = $engine->createInstance($workflow, $input);
$output = $instance->advance();
```

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

namespace Phlow\Engine;

use Phlow\Model\Workflow;
use Phlow\Node\Choice;
use Phlow\Node\Conditional;
use Phlow\Node\Error;
use Phlow\Node\Executable;
use Phlow\Node\Filter;
use Phlow\Node\Sort;
use Phlow\Node\Start;
use Phlow\Processor\CallbackProcessor;
use Phlow\Processor\ChildConnectionProcessor;
use Phlow\Processor\NextConnectionProcessor;
use Phlow\Processor\Repository;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Phlow\Node\Callback;
use Phlow\Node\Find;
use Phlow\Node\First;
use Phlow\Node\Last;
use Phlow\Node\Map;
use Psr\Log\NullLogger;

class Engine implements LoggerAwareInterface
{
use LoggerAwareTrait;

/**
* @var array List of registered Workflows
*/
private $workflows = [];

/**
* @var Repository Mapping between Workflow Nodes and Processors
*/
private $processorRepository = [];

/**
* Engine constructor.
*/
public function __construct()
{
$this->logger = new NullLogger();
$this->processorRepository = new Repository();

// $this->processorsRepository->register(Node::class, , NextConnectionProcessor::class);
// $this->processorsRepository->register(Event::class, NextConnectionProcessor::class);
$this->processorRepository->register(Start::class, NextConnectionProcessor::class);
$this->processorRepository->register(Error::class, NextConnectionProcessor::class);

// $this->processorsRepository->register(Conditional::class, ChildConnectionProcessor::class);
$this->processorRepository->register(Choice::class, ChildConnectionProcessor::class);

// $this->processorsRepository->register(Executable::class, CallbackProcessor::class);
$this->processorRepository->register(Callback::class, CallbackProcessor::class);
$this->processorRepository->register(Filter::class, CallbackProcessor::class);
$this->processorRepository->register(First::class, CallbackProcessor::class);
$this->processorRepository->register(Find::class, CallbackProcessor::class);
$this->processorRepository->register(Last::class, CallbackProcessor::class);
$this->processorRepository->register(Sort::class, CallbackProcessor::class);
$this->processorRepository->register(Map::class, CallbackProcessor::class);
}

/**
* Registers the provided Workflow in the engine
* @param Workflow $workflow
*/
public function add(Workflow $workflow): void
{
$id = $workflow->getId();
if (empty($id)) {
throw new \InvalidArgumentException();
}

if (array_key_exists($id, $this->workflows)) {
throw new \InvalidArgumentException();
}

$this->workflows[$id] = $workflow;
}

/**
* Returns the workflow identified by the provided $id
* @param string $id
* @return Workflow
*/
public function get(string $id): Workflow
{
if (!array_key_exists($id, $this->workflows)) {
throw new \OutOfBoundsException();
}

return $this->workflows[$id];
}

/**
* Creates and returns a new Instance for the given Workflow
* @param Workflow $workflow
* @param $input
* @return WorkflowInstance
*/
public function createInstance($workflow, $input): WorkflowInstance
{
$instance = new WorkflowInstance($this, $workflow, $input);
$instance->setLogger($this->logger);

return $instance;
}

/**
* Executes the current node and moves the node pointer to the next node
* @param WorkflowInstance $instance
*/
public function processInstance(WorkflowInstance $instance): void
{
$node = $instance->current();
$nodeClass = get_class($node);
$this->logger->info(sprintf('Workflow execution reached %s', $node));
if ($this->getProcessorRepository()->has($nodeClass)) {
$processor = $this->getProcessorRepository()->getInstance($nodeClass);
$connection = $processor->process($node, $instance->getExchange());
$instance->followConnection($connection);
$this->logger->info(sprintf('Workflow execution completed for %s', $node));
// } else {
// throw new \Exception('Processor not found');
}
}

/**
* @return Repository
*/
public function getProcessorRepository(): Repository
{
return $this->processorRepository;
}
}
87 changes: 36 additions & 51 deletions src/Engine/WorkflowInstance.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,11 @@

namespace Phlow\Engine;

use Phlow\Node\Callback;
use Phlow\Connection\Connection;
use Phlow\Node\Error;
use Phlow\Node\Find;
use Phlow\Node\First;
use Phlow\Node\Last;
use Phlow\Node\Map;
use Phlow\Node\RecursiveIterator;
use Phlow\Node\Sort;
use Phlow\Processor\ChildConnectionProcessor;
use Phlow\Processor\Processor;
use Phlow\Processor\NextConnectionProcessor;
use Phlow\Processor\CallbackProcessor;
use Phlow\Node\End;
use Phlow\Node\Start;
use Phlow\Node\Choice;
use Phlow\Node\Filter;
use Phlow\Model\Workflow;
use Phlow\Node\Node;
use Phlow\Renderer\Renderer;
Expand Down Expand Up @@ -60,28 +49,18 @@ class WorkflowInstance implements LoggerAwareInterface
private $executionPath;

/**
* @var array Mapping between Workflow Nodes and Processors
* @var Engine|null The Engine created this instance
*/
private $processors = [
Start::class => NextConnectionProcessor::class,
Error::class => NextConnectionProcessor::class,
Callback::class => CallbackProcessor::class,
Choice::class => ChildConnectionProcessor::class,
Filter::class => CallbackProcessor::class,
First::class => CallbackProcessor::class,
Find::class => CallbackProcessor::class,
Last::class => CallbackProcessor::class,
Sort::class => CallbackProcessor::class,
Map::class => CallbackProcessor::class,
];
private $engine;

/**
* WorkflowInstance constructor.
* @param Workflow $workflow
* @param $inbound
*/
public function __construct(Workflow $workflow, $inbound)
public function __construct(Engine $engine, Workflow $workflow, $inbound)
{
$this->engine = $engine;
$this->workflow = $workflow;
$this->exchange = new Exchange($inbound);
$this->setLogger(new NullLogger());
Expand Down Expand Up @@ -123,7 +102,7 @@ public function advance($howMany = 1)
// Retrieve and execute the next node
$this->initNodes();
try {
$this->handleCurrentNode();
$this->engine->processInstance($this);
} catch (\Exception $e) {
$this->handleException($e);
}
Expand Down Expand Up @@ -151,29 +130,6 @@ private function prepareExchange()
}
}

/**
* Executes the current node and moves the node pointer to the next node
*/
private function handleCurrentNode(): void
{
$this->executionPath->add($this->current());

$nodeClass = get_class($this->current());
$this->logger->info(sprintf('Workflow execution reached %s', $nodeClass));
if (array_key_exists($nodeClass, $this->processors)) {
$processorClass = $this->processors[$nodeClass];

/** @var Processor $processor */
$processor = new $processorClass;

$connection = $processor->process($this->current(), $this->exchange);
$this->executionPath->add($connection);
$this->nextNode = $connection->getTarget();

$this->logger->info(sprintf('Workflow execution completed for %s', $nodeClass));
}
}

/**
* Handles a raised exception by moving the flow to an error event
* If no error handling was configured, another Exception will be thrown halting the execution
Expand All @@ -192,7 +148,7 @@ private function handleException(\Exception $exception): void
while (!empty($exceptionClass)) {
if (array_key_exists($exceptionClass, $errorEvents)) {
$this->currentNode = $errorEvents[$exceptionClass];
$this->handleCurrentNode();
$this->engine->processInstance($this);
return;
}

Expand Down Expand Up @@ -223,6 +179,7 @@ private function initNodes(): void
}

$this->currentNode = $startEvents[0];
$this->executionPath->add($this->currentNode);
$this->nextNode = null;
}

Expand Down Expand Up @@ -308,4 +265,32 @@ function ($workflowObject) use ($executionPath) {
);
return (string) $viewer->render($itr);
}

/**
* @return null|Engine
*/
public function getEngine(): Engine
{
return $this->engine;
}

public function followConnection(Connection $connection)
{
$this->executionPath->add($connection);
$this->moveTo($connection->getTarget());
}

public function moveTo(Node $node)
{
$this->executionPath->add($node);
$this->nextNode = $node;
}

/**
* @return Exchange
*/
public function getExchange(): Exchange
{
return $this->exchange;
}
}
32 changes: 32 additions & 0 deletions src/Processor/Repository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Phlow\Processor;

class Repository
{
/**
* @var array Mapping between Workflow Nodes and Processors
*/
private $processors = [];

public function register(string $nodeClass, string $processorClass): void
{
$this->processors[$nodeClass] = $processorClass;
}

public function has(string $nodeClass): string
{
return array_key_exists($nodeClass, $this->processors);
}

public function get(string $nodeClass): string
{
return $this->processors[$nodeClass];
}

public function getInstance(string $nodeClass): Processor
{
$processor = $this->get($nodeClass);
return new $processor();
}
}
Loading