Skip to content

Commit

Permalink
✨ Add do notation
Browse files Browse the repository at this point in the history
  • Loading branch information
matyo91 committed Sep 26, 2023
1 parent d1ae85a commit 9f30dfc
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 28 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

## v1.1.x


- Add Flow\FlowInterface::do notation from https://github.com/fp4php/functional
- Update Flow\FlowInterface::fn to accept as first argument
- Closure : it's the job itself
- array : constructor arguments for Flow instanciation
- array (view as shape) : configuration for Flow instanciation
- FlowInterface : the FlowInterface instance itself

## v1.1.4

Expand Down
22 changes: 16 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Flow concept aims to solve

## Installation

PHP 8.2 is the minimal version to use Flow
PHP 8.2 is the minimal version to use Flow
The recommended way to install it through [Composer](http://getcomposer.org) and execute

```bash
Expand All @@ -29,19 +29,29 @@ composer require darkwood/flow
use Flow\Flow\Flow;
use Flow\Ip;

$flow = (new Flow(fn (object $data) => $data['number'] += 1))
->fn(new Flow(fn (object $data) => $data['number'] *= 2));
class D1 {
public function __construct(public int $n1) {}
}

$ip = new Ip(new ArrayObject(['number' => 4]));
$flow($ip, fn ($ip) => printf("my number %d\n", $ip->data['number'])); // display 'my number 10'
class D2 {
public function __construct(public int $n2) {}
}

$flow = Flow::do(static function() {
yield fn (D1 $data1) => new D2($data1->n1 += 1);
yield fn (D2 $data2) => $data2->n2 * 2;
});

$ip = new Ip(new D1(4));
$flow($ip, fn ($ip) => printf("my number %d\n", $ip->data->n2)); // display 'my number 10'
```

## Examples

A working script is available in the bundled `examples` directory

- Run Flow : `php examples/flow.php`
- Start Server : `php examples/server.php`
- Start Server : `php examples/server.php`
Start Client(s) : `php examples/client.php`

## Documentation
Expand Down
7 changes: 6 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,10 @@
"phpunit": "Launch PHPUnit test suite",
"psalm": "Run Psalm"
},
"minimum-stability": "stable"
"minimum-stability": "stable",
"config": {
"allow-plugins": {
"mcaskill/composer-exclude-files": true
}
}
}
7 changes: 4 additions & 3 deletions examples/flow.php
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,10 @@
$ip->data->number = null;
};

$flow = (new Flow($job1, $errorJob1, new MaxIpStrategy(2), $driver))
->fn(new Flow($job2, $errorJob2, new MaxIpStrategy(2), $driver))
;
$flow = Flow::do(static function () use ($job1, $job2, $errorJob1, $errorJob2) {
yield [$job1, $errorJob1, new MaxIpStrategy(2)];
yield [$job2, $errorJob2, new MaxIpStrategy(2)];
}, ['driver' => $driver]);

$ipPool = new SplObjectStorage();

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

declare(strict_types=1);

namespace Flow\Exception;

use Flow\ExceptionInterface;
use LogicException as NativeLogicException;

class LogicException extends NativeLogicException implements ExceptionInterface
{
}
96 changes: 90 additions & 6 deletions src/Flow/Flow.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@
use Closure;
use Flow\Driver\FiberDriver;
use Flow\DriverInterface;
use Flow\Exception\LogicException;
use Flow\ExceptionInterface;
use Flow\FlowInterface;
use Flow\Ip;
use Flow\IpStrategy\LinearIpStrategy;
use Flow\IpStrategyInterface;
use Generator;
use SplObjectStorage;

use function array_key_exists;
use function count;
use function is_array;

Expand Down Expand Up @@ -81,13 +84,39 @@ public function __invoke(Ip $ip, Closure $callback = null): void
$this->nextIpJob();
}

/**
* @param FlowInterface<T2> $flow
*
* @return FlowInterface<T1>
*/
public function fn(FlowInterface $flow): FlowInterface
public static function do(callable $callable, ?array $config = null): FlowInterface
{
/**
* @var Closure|Generator $generator
*/
$generator = $callable();

if ($generator instanceof Generator) {
$flows = [];

while ($generator->valid()) {
$flow = self::flowUnwrap($generator->current(), $config);

$generator->send($flow);

$flows[] = $flow;
}

$return = $generator->getReturn();
if (!empty($return)) {
$flows[] = self::flowUnwrap($return, $config);
}

return self::flowMap($flows);
}

return self::flowUnwrap($generator, $config);
}

public function fn(array|Closure|FlowInterface $flow): FlowInterface
{
$flow = self::flowUnwrap($flow);

if ($this->fnFlow) {
$this->fnFlow->fn($flow);
} else {
Expand Down Expand Up @@ -133,4 +162,59 @@ private function nextIpJob(): void
})($ip->data);
}
}

/**
* @template TI
*
* @param array<mixed>|Closure|FlowInterface<TI> $flow
* @param ?array<mixed> $config
*
* @return FlowInterface<mixed>
*
* #param ?array{
* 0: Closure|array,
* 1?: Closure|array,
* 2?: IpStrategyInterface
* 3?: DriverInterface
* }|array{
* "jobs"?: Closure|array,
* "errorJobs"?: Closure|array,
* "ipStrategy"?: IpStrategyInterface
* "driver"?: DriverInterface
* } $config
*/
private static function flowUnwrap($flow, ?array $config = null): FlowInterface
{
if ($flow instanceof Closure) {
return new self(...[...['jobs' => $flow], ...($config ?? [])]);
}
if (is_array($flow)) {
if (array_key_exists(0, $flow) || array_key_exists('jobs', $flow)) {
return new self(...[...$flow, ...($config ?? [])]);
}

return self::flowMap($flow);
}

return $flow;
}

/**
* @param array<FlowInterface<mixed>> $flows
*
* @return FlowInterface<mixed>
*/
private static function flowMap(array $flows)
{
$flow = array_shift($flows);
if (null === $flow) {
throw new LogicException('Flow is empty');
}

foreach ($flows as $flowIt) {
$flow = $flow->fn($flowIt);
}

return $flow;
}
}
15 changes: 6 additions & 9 deletions src/Flow/FlowDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,18 @@ public function __construct(private FlowInterface $flow)
{
}

/**
* @param Ip<T1> $ip
*/
public function __invoke(Ip $ip, Closure $callback = null): void
{
($this->flow)($ip, $callback);
}

/**
* @param FlowInterface<T2> $flow
*
* @return FlowInterface<T1>
*/
public function fn(FlowInterface $flow): FlowInterface
public function fn(array|Closure|FlowInterface $flow): FlowInterface
{
return $this->flow->fn($flow);
}

public static function do(callable $callable, ?array $config = null): FlowInterface
{
return Flow::do($callable, $config);
}
}
55 changes: 53 additions & 2 deletions src/FlowInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Flow;

use Closure;
use Generator;

/**
* @template T1
Expand All @@ -20,9 +21,59 @@ public function __invoke(Ip $ip, Closure $callback = null): void;
/**
* @template T2
*
* @param FlowInterface<T2> $flow
* @param array<mixed>|Closure|FlowInterface<T2> $flow can be Closure as Job, array constructor arguments for Flow instanciation, array configuration for Flow instanciation or FlowInterface instance
* #param ?array{
* 0: Closure|array,
* 1?: Closure|array,
* 2?: IpStrategyInterface,
* 3?: DriverInterface
* }|array{
* "jobs"?: Closure|array,
* "errorJobs"?: Closure|array,
* "ipStrategy"?: IpStrategyInterface,
* "driver"?: DriverInterface
* }|Closure|FlowInterface<T2> $config
*
* @return FlowInterface<T1>
*/
public function fn(self $flow): self;
public function fn(array|Closure|self $flow): self;

/**
* Do-notation a.k.a. for-comprehension.
*
* Syntax sugar for sequential {@see FlowInterface::fn()} calls
*
* Syntax "$flow = yield $wrapedFlow" mean:
* 1) $wrapedFlow can be Closure as Job, array constructor arguments for Flow instanciation, array configuration for Flow instanciation or FlowInterface instance
* 2) $flow is assigned as FlowInterface instance
* 3) optionnaly you can return another wrapedFlow
*
* ```php
* $flow = Flow::do(static function() {
* yield new Flow(fn($a) => $a + 1);
* $flow = yield fn($b) => $b * 2;
* $flow = yield $flow->fn([fn($c) => $c * 4])
* return [$flow, [fn($d) => $d - 8]];
* });
* ```
* $config if provided will be the fallback array configuration for Flow instanciation
*
* @param callable(): Generator|Closure $callable
* @param ?array<mixed> $config
*
* #param ?array{
* 0: Closure|array,
* 1?: Closure|array,
* 2?: IpStrategyInterface<mixed>,
* 3?: DriverInterface
* }|array{
* "jobs"?: Closure|array,
* "errorJobs"?: Closure|array,
* "ipStrategy"?: IpStrategyInterface<mixed>,
* "driver"?: DriverInterface
* } $config
*
* @return FlowInterface<mixed>
*/
public static function do(callable $callable, ?array $config = null): self;
}
38 changes: 38 additions & 0 deletions tests/Flow/FlowTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,27 @@ public function testJobs(DriverInterface $driver): void
$driver->start();
}

/**
* @dataProvider provideDoCases
*/
public function testDo(DriverInterface $driver, IpStrategyInterface $ipStrategy, callable $callable, ?array $config, int $resultNumber): void

Check failure on line 93 in tests/Flow/FlowTest.php

View workflow job for this annotation

GitHub Actions / Execute PHPStan analysis (8.2)

Method Flow\Test\Flow\FlowTest::testDo() has parameter $config with no value type specified in iterable type array.

Check failure on line 93 in tests/Flow/FlowTest.php

View workflow job for this annotation

GitHub Actions / Execute PHPStan analysis (8.2)

Method Flow\Test\Flow\FlowTest::testDo() has parameter $driver with generic interface Flow\DriverInterface but does not specify its types: TArgs, TReturn

Check failure on line 93 in tests/Flow/FlowTest.php

View workflow job for this annotation

GitHub Actions / Execute PHPStan analysis (8.2)

Method Flow\Test\Flow\FlowTest::testDo() has parameter $ipStrategy with generic interface Flow\IpStrategyInterface but does not specify its types: T
{
$ip = new Ip(new ArrayObject(['number' => 0]));
$flow = Flow::do($callable, [

Check failure on line 96 in tests/Flow/FlowTest.php

View workflow job for this annotation

GitHub Actions / Execute PHPStan analysis (8.2)

Static method Flow\Flow\Flow<mixed,mixed>::do() invoked with 3 parameters, 1-2 required.
...['driver' => $driver, 'ipStrategy' => $ipStrategy],
...($config ?? []),
], $resultNumber);

($flow)($ip, static function (Ip $ip) use ($driver, $resultNumber) {
$driver->stop();

self::assertSame(ArrayObject::class, $ip->data::class);
self::assertSame($resultNumber, $ip->data['number']);
});

$driver->start();
}

/**
* @return array<array<mixed>>
*/
Expand Down Expand Up @@ -114,4 +135,21 @@ public static function jobProvider(): iterable
}], 0],
]);
}

/**
* @return array<array<mixed>>
*/
public static function provideDoCases(): iterable
{
return self::matrix(static fn (DriverInterface $driver) => [
'simpleGenerator' => [static function () {
yield static function (ArrayObject $data) {
$data['number'] = 5;
};
yield static function (ArrayObject $data) {
$data['number'] = 10;
};
}, null, 10],
]);
}
}

0 comments on commit 9f30dfc

Please sign in to comment.