Skip to content

Commit

Permalink
initial code
Browse files Browse the repository at this point in the history
  • Loading branch information
nhlm committed Mar 13, 2017
1 parent a86c7fd commit 2a30f9b
Show file tree
Hide file tree
Showing 13 changed files with 872 additions and 2 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea
*.iml
out
gen
dev
245 changes: 243 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,243 @@
# pipechain
Pipeline Pattern Implementation
# PipeChain
A linear chainable pipeline pattern implementation with fallbacks.

## What is a linear pipeline

A linear pipeline is a object that stacks callbacks and provides a
processing logic to process a payload thru the callback stack,
form the first to the last.

## Processors

PipeChain comes out of the box with 4 different processors:

### The common invoker processor

The common invoker processor implements a processing of the
queue that considers the provided fallback callback as the
successor of a failed stage callback.

The common invoker processor is the default processor.

Example:

```php
<?php declare(strict_types=1);

use PipeChain\{
PipeChain
};

$pipeline = new PipeChain();

$pipeline->pipe(
# the stage callback
function(string $inbound) {
return 'something';
},
# the fallback callback
function(int $inbound) {
return ++$inbound;
}
);

var_dump(
$pipeline->process(100) // int(3) => 101
);

```

### The naive invoker processor

The naive invoker processor implements a processing of the
queue that ignores the provided fallback callback.

Example:

```php
<?php declare(strict_types=1);

use PipeChain\{
PipeChain,
InvokerProcessors\NaiveInvokerProcessor
};

$pipeline = new PipeChain(new NaiveInvokerProcessor());

$pipeline->pipe(function(int $inbound) {
return ++$inbound;
});

var_dump(
$pipeline->process(100) // int(3) => 101
);

```

### The loyal interface invoker processor

The loyal interface invoker processor implements a processing
of the queue that acts like a common invoker processor with
an additional interface check of each payload change.

The interface check has 2 modes:
- `LoyalInterfaceInvokerProcessor::INTERRUPT_ON_MISMATCH` forces
the processor to pass the throwables to the next scope.
- `LoyalInterfaceInvokerProcessor::RESET_ON_MISMATCH` forces
the processor to use the previous payload instead.

Example:

```php
<?php declare(strict_types=1);

use PipeChain\{
PipeChain,
InvokerProcessors\LoyalInterfaceInvokerProcessor
};

$pipeline = new PipeChain(
new LoyalInterfaceInvokerProcessor(
DateTimeInterface::class,
LoyalInterfaceInvokerProcessor::RESET_ON_MISMATCH
)
);

$prior = function($inbound) {
if ( ! $inbound instanceof DateTime ) {
throw new Exception('Whats this?');
}

return $inbound->modify('+10 hours');
};

$failure = function (string $inbound) use ($prior) {
return $prior(date_create($inbound));
};

$pipeline->pipe($prior, $failure);

var_dump(
$pipeline
->process('2017-10-12 18:00:00')
->format('Y-m-d') // string(10) "2017-10-13"
);

```

### The loyal type invoker processor

The loyal type invoker processor implements a processing
of the queue that acts like the loyal interface invoker
processor but with type checking of each payload change.

The type check has 2 modes:
- `LoyalInterfaceInvokerProcessor::INTERRUPT_ON_MISMATCH` forces
the processor to pass the throwables to the next scope.
- `LoyalInterfaceInvokerProcessor::RESET_ON_MISMATCH` forces
the processor to use the previous payload instead.

Example:

```php
<?php declare(strict_types=1);

use PipeChain\{
PipeChain,
InvokerProcessors\LoyalTypeInvokerProcessor
};

$pipeline = new PipeChain(
new LoyalTypeInvokerProcessor(
'string',
LoyalTypeInvokerProcessor::RESET_ON_MISMATCH
)
);

$pipeline->pipe(function(string $name) {
return strtolower($name);
});
$pipeline->pipe(function(string $name) {
return ucfirst($name);
});

var_dump(
$pipeline->process('jOhN') // string(4) "John"
);

```

## Booting own implementations

```php
<?php declare(strict_types=1);

namespace Somewhat;

use PipeChain\{
PipeChain,
InvokerProcessorInterface as Processor,
PipeChainCollectionInterface as Container
};

class MyFirstPipeline extends PipeChain {

protected function boot(Container $container, Processor $processor = null): Processor
{
# pipe something. The signature of PipeChainCollectionInterface::attach() is equal
# to PipeChain::pipe(..).

$container->attach(
# stage callable
function() {

},
# fallback callable (optional)
function() {

}
);

# ...

# Don't forget to call the parent, the parent method ensures
# that a processor will be created when $processor is null.

return parent::boot($container, $processor);
}
}

# later ...

echo MyFirstPipeline::create()->process('I can get no satisfaction!');

```

## Chaining Pipelines

```php
<?php declare(strict_types=1);

$pipeline = MyFirstPipeline::create()->chain(
MySecondPipeline::create()
)->chain(
MyThirdPipeline::create()
);

$pipeline->process('This is awesome.');
```

## Maintainer, State of this Package and License

Those are the Maintainer of this Package:

- [Matthias Kaschubowski](https://github.com/nhlm)

This package is released under the MIT license. A copy of the license
is placed at the root of this repository.

The State of this Package is unstable unless unit tests are added.

## Todo

- Adding Unit Tests
103 changes: 103 additions & 0 deletions src/Collections/PipeChainCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
<?php declare(strict_types=1);
/**
* This file is part of the PIPECHAIN Project.
*
* (c)2017 Matthias Kaschubowski
*
* This code is licensed under the MIT license,
* a copy of the license is stored at the project root.
*/

namespace PipeChain\Collections;


use PipeChain\PipeChainCollectionInterface;
use Traversable;

/**
* Class PipeChainCollection
* @package PipeChain\Collections
*/
class PipeChainCollection implements PipeChainCollectionInterface
{
/**
* @var \SplObjectStorage
*/
protected $storage;

/**
* PipeChainCollection constructor.
*/
public function __construct()
{
$this->storage = new \SplObjectStorage();
}

/**
* Retrieve an external iterator
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return Traversable|\Generator An instance of an object implementing <b>Iterator</b> or
* <b>Traversable</b>
* @since 5.0.0
*/
public function getIterator(): \Generator
{
foreach ( $this->storage as $object ) {
yield $object => $this->storage[$object]['fallback'];
}
}

/**
* Attaches a stage and assigns a fallback to the attached stage.
*
* @param callable $stage
* @param callable|null $fallback
* @return void
*/
public function attach(callable $stage, callable $fallback = null)
{
$this->storage->attach(
$this->marshalClosure($stage),
[
'fallback' => is_callable($fallback) ? $this->marshalClosure($fallback) : null
]
);
}

/**
* Count elements of an object
* @link http://php.net/manual/en/countable.count.php
* @return int The custom count as an integer.
* </p>
* <p>
* The return value is cast to an integer.
* @since 5.1.0
*/
public function count()
{
return $this->storage->count();
}

protected function marshalClosure(callable $callback): \Closure
{
if ( method_exists(\Closure::class, 'fromCallable') ) {
return \Closure::fromCallable($callback);
}

is_callable($callback, true, $target);

if ( false === strpos('::', $target) || $callback instanceof \Closure ) {
return (new \ReflectionFunction($target))->getClosure();
}
else {
list($class, $method) = explode('::', $target);
return (new \ReflectionMethod($class, $method))
->getClosure(
is_array($callback) && is_object($callback[0])
? $callback[0]
: null
)
;
}
}
}
34 changes: 34 additions & 0 deletions src/InvokerProcessorInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types=1);
/**
* This file is part of the PIPECHAIN Project.
*
* (c)2017 Matthias Kaschubowski
*
* This code is licensed under the MIT license,
* a copy of the license is stored at the project root.
*/

namespace PipeChain;


interface InvokerProcessorInterface
{
/**
* processes a PipeChainCollection with the provided payload.
*
* @param mixed $payload
* @param PipeChainCollectionInterface $pipeChainCollection
* @return mixed payload
*/
public function processStack($payload, PipeChainCollectionInterface $pipeChainCollection);

/**
* processes a single stage and its optionally associated fallback with the provided payload.
*
* @param mixed $payload
* @param callable $stage
* @param callable|null $fallback
* @return mixed payload
*/
public function process($payload, callable $stage, callable $fallback = null);
}
Loading

0 comments on commit 2a30f9b

Please sign in to comment.