diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dfee1a7..26caf7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,11 @@ ## 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 : configuration for FlowInterface instanciation + - FlowInterface : the FlowInterface instance itself ## v1.1.4 diff --git a/README.md b/README.md index 1536d7f8..192edd2e 100644 --- a/README.md +++ b/README.md @@ -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 @@ -29,11 +29,21 @@ 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 @@ -41,7 +51,7 @@ $flow($ip, fn ($ip) => printf("my number %d\n", $ip->data['number'])); // displa 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 diff --git a/examples/flow.php b/examples/flow.php index d079a74c..91d412bb 100644 --- a/examples/flow.php +++ b/examples/flow.php @@ -82,9 +82,14 @@ $ip->data->number = null; }; -$flow = (new Flow($job1, $errorJob1, 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]); + +/*$flow = (new Flow($job1, $errorJob1, new MaxIpStrategy(2), $driver)) ->fn(new Flow($job2, $errorJob2, new MaxIpStrategy(2), $driver)) -; +;*/ $ipPool = new SplObjectStorage(); diff --git a/src/Exception/LogicException.php b/src/Exception/LogicException.php new file mode 100644 index 00000000..9e3ef4a6 --- /dev/null +++ b/src/Exception/LogicException.php @@ -0,0 +1,12 @@ +callbacks = new SplObjectStorage(); } + public static function do(callable $callable, ?array $config = null): FlowInterface { + $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 $flow; + } + + return self::flowUnwrap($generator, $config); + } + public function __invoke(Ip $ip, Closure $callback = null): void { $this->callbacks->offsetSet($ip, $callback); @@ -81,13 +108,10 @@ public function __invoke(Ip $ip, Closure $callback = null): void $this->nextIpJob(); } - /** - * @param FlowInterface $flow - * - * @return FlowInterface - */ - public function fn(FlowInterface $flow): FlowInterface + public function fn(callable|FlowInterface $flow): FlowInterface { + $flow = self::flowUnwrap($flow); + if ($this->fnFlow) { $this->fnFlow->fn($flow); } else { @@ -133,4 +157,35 @@ private function nextIpJob(): void })($ip->data); } } + + /** + * @param Closure|array|FlowInterface $flow + * @param ?array{ + * "jobs"?: Closure|array, + * "errorJobs"?: Closure|array, + * "ipStrategy"?: IpStrategyInterface + * "driver"?: DriverInterface + * } $config + * @return FlowInterface + */ + private static function flowUnwrap($flow, ?array $config = null): FlowInterface + { + if($flow instanceof Closure) { + return new Flow(...["jobs" => $flow], ...($config ?? [])); + } else if(is_array($flow)) { + if(array_key_exists("jobs", $flow)) { + return new Flow(...$flow, ...($config ?? [])); + } + + if(count($flow) === 0) { + throw new LogicException(sprintf('Flow is empty')); + } + + foreach($flow as $flowIt) { + + } + } + + return $flow; + } } diff --git a/src/FlowInterface.php b/src/FlowInterface.php index 3fa89f5c..35422422 100644 --- a/src/FlowInterface.php +++ b/src/FlowInterface.php @@ -20,9 +20,39 @@ public function __invoke(Ip $ip, Closure $callback = null): void; /** * @template T2 * - * @param FlowInterface $flow + * @param Closure|array|FlowInterface $flow can be Closure as Job, array configuration for Flow instanciation or FlowInterface instance * * @return FlowInterface */ public function fn(self $flow): self; + + /** + * Do-notation a.k.a. for-comprehension. + * + * Syntax sugar for sequential {@see FlowInterface::fn()} calls + * + * Syntax "$unwrappedValue = yield $box" mean: + * 1) unwrap the $box + * 2) if there is nothing in the box then short-circuit (stop) the callable + * 3) place contained in $box value into $unwrappedValue variable + * + * ```php + * >>> Option::do(function() { + * $a = 1; + * $b = yield new Flow(fn() => ); + * $c = yield Option::some(3); + * $d = yield Option::none(); // short circuit here + * $e = 5; // not executed + * return [$a, $b, $c, $d, $e]; // not executed + * }); + * => None + * ``` + * + * @template TS + * @template TO + * + * @param callable(): Generator, TS, TO> $callable + * @return Option + */ + public static function do(callable $callable, ?array $config = null): FlowInterface; } diff --git a/tests/Flow/FlowTest.php b/tests/Flow/FlowTest.php index 71ed71f5..19247107 100644 --- a/tests/Flow/FlowTest.php +++ b/tests/Flow/FlowTest.php @@ -87,6 +87,16 @@ public function testJobs(DriverInterface $driver): void $driver->start(); } + /** + * @dataProvider jobProvider + * + * @param DriverInterface $driver + */ + public function testDo(): void + { + + } + /** * @return array> */ @@ -114,4 +124,12 @@ public static function jobProvider(): iterable }], 0], ]); } + + /** + * @return array> + */ + public static function doProvider(): iterable + { + + } }