From d1ae85a6aaa5cca5739fb1c606746e3f3cfd8714 Mon Sep 17 00:00:00 2001 From: Mathieu Ledru Date: Sun, 17 Sep 2023 20:31:02 +0200 Subject: [PATCH 1/5] :memo: Prepare next version --- CHANGELOG.md | 4 ++++ docs/package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53f30892..4dfee1a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.1.x + + + ## v1.1.4 - Add generic templating diff --git a/docs/package.json b/docs/package.json index fa310e1a..41b8fd81 100644 --- a/docs/package.json +++ b/docs/package.json @@ -12,7 +12,7 @@ "keywords": [ "flow" ], - "version": "1.1.4", + "version": "1.1.x", "browserslist": [ "defaults" ], From 9b6e090e9d9d57c3f5b4bdfa1d4d5d201536209f Mon Sep 17 00:00:00 2001 From: Mathieu Ledru Date: Thu, 7 Sep 2023 21:36:50 +0200 Subject: [PATCH 2/5] :sparkles: Add do notation --- .editorconfig | 4 +- CHANGELOG.md | 7 ++- README.md | 18 ++++-- examples/flow.php | 7 ++- src/Exception/LogicException.php | 12 ++++ src/Flow/Flow.php | 96 ++++++++++++++++++++++++++++++-- src/Flow/FlowDecorator.php | 15 ++--- src/FlowInterface.php | 55 +++++++++++++++++- tests/Flow/FlowTest.php | 42 ++++++++++++++ 9 files changed, 229 insertions(+), 27 deletions(-) create mode 100644 src/Exception/LogicException.php diff --git a/.editorconfig b/.editorconfig index 2723870b..598c9a0e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,10 +3,10 @@ root = true # https://editorconfig.org [*] end_of_line = lf insert_final_newline = true -trim_trailing_whitespace = true +trim_trailing_whitespace = false indent_style = space indent_size = 4 charset = utf-8 [*.md] -max_line_length = 80 \ No newline at end of file +max_line_length = 80 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4dfee1a7..fb344aed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 1536d7f8..c213f525 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/examples/flow.php b/examples/flow.php index d079a74c..8564ce44 100644 --- a/examples/flow.php +++ b/examples/flow.php @@ -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(); 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 @@ +nextIpJob(); } - /** - * @param FlowInterface $flow - * - * @return FlowInterface - */ - 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 { @@ -133,4 +162,59 @@ private function nextIpJob(): void })($ip->data); } } + + /** + * @template TI + * + * @param array|Closure|FlowInterface $flow + * @param ?array $config + * + * @return FlowInterface + * + * #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> $flows + * + * @return FlowInterface + */ + 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; + } } diff --git a/src/Flow/FlowDecorator.php b/src/Flow/FlowDecorator.php index 006e3650..78af675c 100644 --- a/src/Flow/FlowDecorator.php +++ b/src/Flow/FlowDecorator.php @@ -23,21 +23,18 @@ public function __construct(private FlowInterface $flow) { } - /** - * @param Ip $ip - */ public function __invoke(Ip $ip, Closure $callback = null): void { ($this->flow)($ip, $callback); } - /** - * @param FlowInterface $flow - * - * @return FlowInterface - */ - 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); + } } diff --git a/src/FlowInterface.php b/src/FlowInterface.php index 3fa89f5c..0f05b95e 100644 --- a/src/FlowInterface.php +++ b/src/FlowInterface.php @@ -5,6 +5,7 @@ namespace Flow; use Closure; +use Generator; /** * @template T1 @@ -20,9 +21,59 @@ public function __invoke(Ip $ip, Closure $callback = null): void; /** * @template T2 * - * @param FlowInterface $flow + * @param array|Closure|FlowInterface $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 $config * * @return FlowInterface */ - 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 $config + * + * #param ?array{ + * 0: Closure|array, + * 1?: Closure|array, + * 2?: IpStrategyInterface, + * 3?: DriverInterface + * }|array{ + * "jobs"?: Closure|array, + * "errorJobs"?: Closure|array, + * "ipStrategy"?: IpStrategyInterface, + * "driver"?: DriverInterface + * } $config + * + * @return FlowInterface + */ + public static function do(callable $callable, ?array $config = null): self; } diff --git a/tests/Flow/FlowTest.php b/tests/Flow/FlowTest.php index 71ed71f5..7a7a7e7b 100644 --- a/tests/Flow/FlowTest.php +++ b/tests/Flow/FlowTest.php @@ -87,6 +87,31 @@ public function testJobs(DriverInterface $driver): void $driver->start(); } + /** + * @dataProvider provideDoCases + * + * @param DriverInterface $driver + * @param IpStrategyInterface $ipStrategy + * @param array $config + */ + public function testDo(DriverInterface $driver, IpStrategyInterface $ipStrategy, callable $callable, ?array $config, int $resultNumber): void + { + $ip = new Ip(new ArrayObject(['number' => 0])); + $flow = Flow::do($callable, [ + ...['driver' => $driver, 'ipStrategy' => $ipStrategy], + ...($config ?? []), + ]); + + ($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> */ @@ -114,4 +139,21 @@ public static function jobProvider(): iterable }], 0], ]); } + + /** + * @return array> + */ + 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], + ]); + } } From 104a7106fd5837ee18683657bc9cf69d1d86d657 Mon Sep 17 00:00:00 2001 From: Mathieu Ledru Date: Tue, 17 Oct 2023 11:37:47 +0200 Subject: [PATCH 3/5] :lipstick: Use tab instead space --- .editorconfig | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.editorconfig b/.editorconfig index 598c9a0e..2c6d1981 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,10 +3,13 @@ root = true # https://editorconfig.org [*] end_of_line = lf insert_final_newline = true -trim_trailing_whitespace = false -indent_style = space +trim_trailing_whitespace = true +indent_style = tab indent_size = 4 charset = utf-8 [*.md] max_line_length = 80 + +[{*.yml,*.yaml}] +indent_style = space From 58e08169809c9165066fbf3718ee0109d05e13ed Mon Sep 17 00:00:00 2001 From: Mathieu Ledru Date: Fri, 1 Dec 2023 17:24:42 +0100 Subject: [PATCH 4/5] :arrow_up: Bump to symfony 7.0 --- CHANGELOG.md | 1 + composer.json | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb344aed..5bf8e5b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - array : constructor arguments for Flow instanciation - array (view as shape) : configuration for Flow instanciation - FlowInterface : the FlowInterface instance itself +- Update to Symfony 7.0 ## v1.1.4 diff --git a/composer.json b/composer.json index e4dbe47f..9326c175 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,12 @@ }, "require-dev": { "amphp/amp": "^3.0", - "openswoole/ide-helper": "^22.0", - "react/async": "^4.1", - "symfony/doctrine-messenger": "^6.3", - "symfony/messenger": "^6.3", + "openswoole/ide-helper": "^22.0.1", + "react/async": "^4.2", + "symfony/doctrine-messenger": "^7.0", + "symfony/messenger": "^7.0", "symfony/orm-pack": "^2.4", - "revolt/event-loop": "^1.0", + "revolt/event-loop": "^1.0.6", "spatie/async": "^1.6" }, "suggest": { From 43eefcbf244a4a3bad2616c8fed9437887cb6fbb Mon Sep 17 00:00:00 2001 From: Mathieu Ledru Date: Fri, 1 Dec 2023 17:28:04 +0100 Subject: [PATCH 5/5] :memo: Release v1.1.5 --- CHANGELOG.md | 2 +- docs/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bf8e5b5..2c1dec13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v1.1.x +## v1.1.5 - Add Flow\FlowInterface::do notation from https://github.com/fp4php/functional - Update Flow\FlowInterface::fn to accept as first argument diff --git a/docs/package.json b/docs/package.json index 41b8fd81..e421f807 100644 --- a/docs/package.json +++ b/docs/package.json @@ -12,7 +12,7 @@ "keywords": [ "flow" ], - "version": "1.1.x", + "version": "1.1.5", "browserslist": [ "defaults" ],