diff --git a/CHANGELOG.md b/CHANGELOG.md index 501807a5..47d8ab80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## v1.1.x +- Add generics templating from + - https://gpb.moe/doc/slides/Gestion_Erreur_Toutes_Ses_Couleurs_FR.pdf + - https://slides.com/kpn13/les-generiques-en-php - Add Flow\Driver\RevoltDriver - Add more quality tools from https://github.com/IngeniozIT/php-skeleton diff --git a/examples/generic-errors.php b/examples/generic-errors.php new file mode 100644 index 00000000..85e66c0a --- /dev/null +++ b/examples/generic-errors.php @@ -0,0 +1,108 @@ + $f + * + * @return Wrapper + */ + public function bind($f): self + { + if ($this->isErr) { + // @var Wrapper Shut up Psalm + return $this; + } + + return $f($this->value); + } +} + +enum OpenFileErrors +{ + case FileDoesNotExist; + case AccessDenied; + case IsDirectory; +} + +class File +{ +} + +enum GetContentErrors +{ + case E1; + case E2; +} + +enum ParseContentErrors +{ + case E1; + case E2; +} + +class UnparsedOutput +{ +} +class ParsedOutput +{ +} + +/** @return Wrapper */ +function open_file(string $path): Wrapper +{ + return new Wrapper(new File(), OpenFileErrors::FileDoesNotExist); +} + +/** @return Wrapper */ +function get_content_file(File $f): Wrapper +{ + return new Wrapper(new UnparsedOutput(), GetContentErrors::E1); +} +/** @return Wrapper */ +function parse_content(UnparsedOutput $f): Wrapper +{ + return new Wrapper(new ParsedOutput(), ParseContentErrors::E1); +} + +$data = open_file('') + ->bind(get_content_file(...)) + ->bind(parse_content(...)) +; +$result = match ($data->isErr) { + true => match ($data->err) { + OpenFileErrors::FileDoesNotExist => 1, + OpenFileErrors::AccessDenied => 2, + OpenFileErrors::IsDirectory => 3, + GetContentErrors::E1 => 4, + GetContentErrors::E2 => 5, + ParseContentErrors::E1 => 6, + ParseContentErrors::E2 => 7, + }, + false => $data->value, +}; diff --git a/src/DriverInterface.php b/src/DriverInterface.php index cec2ccc4..2e3075a2 100644 --- a/src/DriverInterface.php +++ b/src/DriverInterface.php @@ -6,10 +6,13 @@ use Closure; +/** + * @template T of object + */ interface DriverInterface { /** - * @param Closure $onResolve called on resolved and first argument is $callback return or Flow\Exception on Exception + * @param callable(ExceptionInterface|?T): void * * @return Closure when called, this start async $callback */ diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php index 8fc86d6f..60e48dbd 100644 --- a/src/Exception/RuntimeException.php +++ b/src/Exception/RuntimeException.php @@ -4,6 +4,7 @@ namespace Flow\Exception; +use Flow\ExceptionInterface; use RuntimeException as NativeRuntimeException; class RuntimeException extends NativeRuntimeException implements ExceptionInterface diff --git a/src/Exception/ExceptionInterface.php b/src/ExceptionInterface.php similarity index 79% rename from src/Exception/ExceptionInterface.php rename to src/ExceptionInterface.php index 6fcb1557..74b93f26 100644 --- a/src/Exception/ExceptionInterface.php +++ b/src/ExceptionInterface.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Flow\Exception; +namespace Flow; use Throwable; diff --git a/src/Flow/Flow.php b/src/Flow/Flow.php index 1b12038c..8f9ed891 100644 --- a/src/Flow/Flow.php +++ b/src/Flow/Flow.php @@ -7,7 +7,7 @@ use Closure; use Flow\Driver\ReactDriver; use Flow\DriverInterface; -use Flow\Exception\RuntimeException; +use Flow\ExceptionInterface; use Flow\FlowInterface; use Flow\Ip; use Flow\IpStrategy\LinearIpStrategy; @@ -88,12 +88,12 @@ private function nextIpJob(): void foreach ($this->jobs as $i => $job) { $this->driver->async($job, function (mixed $value) use ($ip, &$count, $i, $callback) { $count--; - if ($count === 0 || $value instanceof RuntimeException) { + if ($count === 0 || $value instanceof ExceptionInterface) { $count = 0; $this->ipStrategy->done($ip); $this->nextIpJob(); - if ($value instanceof RuntimeException) { + if ($value instanceof ExceptionInterface) { if (isset($this->errorJobs[$i])) { $this->errorJobs[$i]($ip->data, $value->getPrevious()); } else { diff --git a/src/FlowInterface.php b/src/FlowInterface.php index 62a21185..766717a7 100644 --- a/src/FlowInterface.php +++ b/src/FlowInterface.php @@ -6,9 +6,23 @@ use Closure; +/** + * @template T + */ interface FlowInterface { + /** + * @param Ip $ip + */ public function __invoke(Ip $ip, Closure $callback = null): void; + /** + * @template T1 of IP + * @template T2 of IP + * + * @param FlowInterface $flow + * + * @return FlowInterface + */ public function fn(self $flow): self; } diff --git a/src/Ip.php b/src/Ip.php index 4dd9dc4a..f1390e4f 100644 --- a/src/Ip.php +++ b/src/Ip.php @@ -4,8 +4,14 @@ namespace Flow; +/** + * @template T of object + */ final readonly class Ip { + /** + * @param ?T $data + */ public function __construct(public ?object $data = null) { } diff --git a/tools/php-cs-fixer/.php-cs-fixer.php b/tools/php-cs-fixer/.php-cs-fixer.php index 3ac3aa32..63b441f5 100644 --- a/tools/php-cs-fixer/.php-cs-fixer.php +++ b/tools/php-cs-fixer/.php-cs-fixer.php @@ -33,7 +33,7 @@ 'import_functions' => true, 'import_classes' => true, ], - 'logical_operators' => false, // https://cs.symfony.com/doc/rules/operator/logical_operators.html we keep or and by design + 'logical_operators' => false, // https://cs.symfony.com/doc/rules/operator/logical_operators.html prefer use 'or' and 'and' operators by design 'yoda_style' => false, // https://cs.symfony.com/doc/rules/control_structure/yoda_style.html 'increment_style' => ['style' => 'post'], ])